diff --git a/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.jsx b/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.jsx index 4c898e2f17afd254d38108e910fe70debe7f827c..0b8f7a0ae976b3a286341c994ddbe95b98e63b9f 100644 --- a/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.jsx +++ b/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.jsx @@ -1,31 +1,64 @@ import React from "react"; import PropTypes from "prop-types"; +import { t } from "ttag"; +import CheckBox from "metabase/components/CheckBox"; import { SegmentedControl, optionShape, } from "metabase/components/SegmentedControl"; +import { AUTHORITY_LEVELS } from "../constants"; +import { FormFieldRoot, Label } from "./FormCollectionAuthorityLevel.styled"; + const propTypes = { field: PropTypes.shape({ value: PropTypes.any, + initialValue: PropTypes.any, onChange: PropTypes.func.isRequired, }).isRequired, options: PropTypes.arrayOf(optionShape).isRequired, values: PropTypes.shape({ + id: PropTypes.number, authority_level: PropTypes.oneOf(["official"]), update_collection_tree_authority_level: PropTypes.bool, }), onChangeField: PropTypes.func.isRequired, }; -export function FormCollectionAuthorityLevel({ field, options }) { +export function FormCollectionAuthorityLevel({ + field, + options, + values, + onChangeField, +}) { + const isNewCollection = !values.id; + const selectedAuthorityLevel = + AUTHORITY_LEVELS[field.value] || AUTHORITY_LEVELS.regular; + const shouldSuggestToUpdateChildren = + !isNewCollection && field.initialValue !== field.value; return ( - <SegmentedControl - value={field.value} - onChange={field.onChange} - options={options} - /> + <FormFieldRoot> + <SegmentedControl + value={field.value} + onChange={field.onChange} + options={options} + /> + {shouldSuggestToUpdateChildren && ( + <CheckBox + label={ + <Label>{t`Make all sub-collections ${selectedAuthorityLevel.name}, too.`}</Label> + } + checked={values.update_collection_tree_authority_level} + onChange={e => + onChangeField( + "update_collection_tree_authority_level", + e.target.checked, + ) + } + /> + )} + </FormFieldRoot> ); } diff --git a/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.styled.js b/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.styled.js new file mode 100644 index 0000000000000000000000000000000000000000..9af08dd826080ab6d442b20a3d82d1ef83cd2e59 --- /dev/null +++ b/enterprise/frontend/src/metabase-enterprise/collections/components/FormCollectionAuthorityLevel.styled.js @@ -0,0 +1,16 @@ +import styled from "styled-components"; +import CheckBox from "metabase/components/CheckBox"; +import { color } from "metabase/lib/colors"; + +export const FormFieldRoot = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const Label = styled(CheckBox.Label)` + color: ${color("text-dark")}; + font-size: 1em; + font-weight: bold; + margin-bottom: 1px; +`; diff --git a/enterprise/frontend/src/metabase-enterprise/collections/index.js b/enterprise/frontend/src/metabase-enterprise/collections/index.js index 8bdc6996d80d6a5e63a4ad3cb7b0e86ccc2e9a10..97cf32e1d8b40bafeb82a5aaf49d30d0b79f1114 100644 --- a/enterprise/frontend/src/metabase-enterprise/collections/index.js +++ b/enterprise/frontend/src/metabase-enterprise/collections/index.js @@ -44,6 +44,10 @@ PLUGIN_COLLECTIONS.formFields = [ }, ], }, + { + name: "update_collection_tree_authority_level", + type: "hidden", + }, ]; PLUGIN_FORM_WIDGETS.collectionAuthorityLevel = FormCollectionAuthorityLevel; diff --git a/frontend/test/metabase/scenarios/collections/collection-types.cy.spec.js b/frontend/test/metabase/scenarios/collections/collection-types.cy.spec.js index bc5cb768bcca9ba418c749956d0c463dcb7dd525..2c3564e18eb00a830cd161f5062eef8e8609e7c0 100644 --- a/frontend/test/metabase/scenarios/collections/collection-types.cy.spec.js +++ b/frontend/test/metabase/scenarios/collections/collection-types.cy.spec.js @@ -23,6 +23,10 @@ describeWithToken("collections types", () => { cy.signInAsAdmin(); }); + const TREE_UPDATE_REGULAR_MESSAGE = "Make all sub-collections Regular, too."; + const TREE_UPDATE_OFFICIAL_MESSAGE = + "Make all sub-collections Official, too."; + it("should be able to manage collection authority level", () => { cy.visit("/collection/root"); @@ -42,6 +46,56 @@ describeWithToken("collections types", () => { it("displays official badge throughout the application", () => { testOfficialBadgePresence(); }); + + it("should be able to update authority level for collection children", () => { + cy.visit("/collection/root"); + cy.findByText("First collection").click(); + + // Test not visible when creating a new collection + cy.icon("new_folder").click(); + modal().within(() => { + cy.findByText(TREE_UPDATE_REGULAR_MESSAGE).should("not.exist"); + cy.findByText(TREE_UPDATE_OFFICIAL_MESSAGE).should("not.exist"); + setOfficial(); + cy.findByText(TREE_UPDATE_REGULAR_MESSAGE).should("not.exist"); + cy.findByText(TREE_UPDATE_OFFICIAL_MESSAGE).should("not.exist"); + cy.icon("close").click(); + }); + + // Test can make all children official + editCollection(); + modal().within(() => { + cy.findByText(TREE_UPDATE_REGULAR_MESSAGE).should("not.exist"); + cy.findByText(TREE_UPDATE_OFFICIAL_MESSAGE).should("not.exist"); + setOfficial(); + cy.findByText(TREE_UPDATE_REGULAR_MESSAGE).should("not.exist"); + cy.findByText(TREE_UPDATE_OFFICIAL_MESSAGE).click(); + cy.button("Update").click(); + }); + + getSidebarCollectionChildrenFor("First collection").within(() => { + expandCollectionChildren("Second collection"); + cy.icon("badge").should("have.length", 3); + cy.icon("folder").should("not.exist"); + }); + + // Test can make all children regular + editCollection(); + modal().within(() => { + cy.findByText(TREE_UPDATE_REGULAR_MESSAGE).should("not.exist"); + cy.findByText(TREE_UPDATE_OFFICIAL_MESSAGE).should("not.exist"); + setOfficial(false); + cy.findByText(TREE_UPDATE_REGULAR_MESSAGE).click(); + cy.findByText(TREE_UPDATE_OFFICIAL_MESSAGE).should("not.exist"); + cy.button("Update").click(); + }); + + getSidebarCollectionChildrenFor("First collection").within(() => { + expandCollectionChildren("Second collection"); + cy.icon("folder").should("have.length", 3); + cy.icon("badge").should("not.exist"); + }); + }); }); describeWithoutToken("collection types", () => { @@ -134,6 +188,22 @@ function editCollection() { cy.findByText("Edit this collection").click(); } +function expandCollectionChildren(collectionName) { + cy.findByText(collectionName) + .parent() + .find(".Icon-chevronright") + .eq(0) // there may be more nested icons, but we need the top level one + .click(); +} + +function getSidebarCollectionChildrenFor(collectionName) { + return sidebar() + .findByText(collectionName) + .closest("a") + .parent() + .parent(); +} + function setOfficial(official = true) { const isOfficialNow = !official; cy.findByLabelText("Regular").should(