diff --git a/packages/common/src/projects/HubProject.ts b/packages/common/src/projects/HubProject.ts index 01ad4fa71b0..8d4fa8fe752 100644 --- a/packages/common/src/projects/HubProject.ts +++ b/packages/common/src/projects/HubProject.ts @@ -36,6 +36,8 @@ import { cloneObject, maybePush } from "../util"; import { createProject, editorToProject, updateProject } from "./edit"; import { ProjectEditorType } from "./_internal/ProjectSchema"; import { enrichEntity } from "../core/enrichEntity"; +import { getProp } from "../objects"; +import { IGroup } from "@esri/arcgis-rest-types"; /** * Hub Project Class @@ -202,17 +204,30 @@ export class HubProject * on project creation, we want to pre-populate the sharing * field with the core and collaboration groups if the user * has the appropriate shareToGroup portal privilege + * + * TODO: we should re-evaluate this "auto-populate" pattern */ const { access: canShare } = this.checkPermission( "platform:portal:user:shareToGroup" ); if (!editor.id && canShare) { - // TODO: at what point can we remove this "auto-share" behavior? - editor._groups = maybePush(editorContext.contentGroupId, editor._groups); - editor._groups = maybePush( + const currentUserGroups: IGroup[] = + getProp(this.context, "currentUser.groups") || []; + const defaultShareWithGroups = [ + editorContext.contentGroupId, editorContext.collaborationGroupId, - editor._groups - ); + ].reduce((acc, groupId) => { + const group = currentUserGroups.find((g: IGroup) => g.id === groupId); + const canShareToGroup = + !!group && + (!group.isViewOnly || + (group.isViewOnly && + ["owner", "admin"].includes(group.memberType))); + + canShareToGroup && acc.push(groupId); + return acc; + }, []); + editor._groups = [...editor._groups, ...defaultShareWithGroups]; } return editor; diff --git a/packages/common/test/projects/HubProject.test.ts b/packages/common/test/projects/HubProject.test.ts index ea026a907bf..45e215a412e 100644 --- a/packages/common/test/projects/HubProject.test.ts +++ b/packages/common/test/projects/HubProject.test.ts @@ -1,5 +1,11 @@ import * as PortalModule from "@esri/arcgis-rest-portal"; -import { IHubProject, IResolvedMetric, getProp } from "../../src"; +import { + IHubProject, + IResolvedMetric, + cloneObject, + getProp, + mergeObjects, +} from "../../src"; import { Catalog } from "../../src/search"; import { ArcGISContextManager } from "../../src/ArcGISContextManager"; import { HubProject } from "../../src/projects/HubProject"; @@ -12,6 +18,25 @@ import * as ResolveMetricModule from "../../src/metrics/resolveMetric"; import { HubItemEntity } from "../../src/core/HubItemEntity"; import * as EnrichEntityModule from "../../src/core/enrichEntity"; +const initContextManager = async (opts = {}) => { + const defaults = { + authentication: MOCK_AUTH, + currentUser: { + username: "casey", + privileges: ["portal:user:shareToGroup"], + } as unknown as PortalModule.IUser, + portal: { + name: "DC R&D Center", + id: "BRXFAKE", + urlKey: "fake-org", + } as unknown as PortalModule.IPortal, + portalUrl: "https://myserver.com", + }; + return await ArcGISContextManager.create( + mergeObjects(opts, defaults, ["currentUser"]) + ); +}; + describe("HubProject Class:", () => { let authdCtxMgr: ArcGISContextManager; let unauthdCtxMgr: ArcGISContextManager; @@ -20,19 +45,7 @@ describe("HubProject Class:", () => { // When we pass in all this information, the context // manager will not try to fetch anything, so no need // to mock those calls - authdCtxMgr = await ArcGISContextManager.create({ - authentication: MOCK_AUTH, - currentUser: { - username: "casey", - privileges: ["portal:user:shareToGroup"], - } as unknown as PortalModule.IUser, - portal: { - name: "DC R&D Center", - id: "BRXFAKE", - urlKey: "fake-org", - } as unknown as PortalModule.IPortal, - portalUrl: "https://myserver.com", - }); + authdCtxMgr = await initContextManager(); }); describe("static methods:", () => { @@ -363,7 +376,7 @@ describe("HubProject Class:", () => { expect(enrichEntitySpy).toHaveBeenCalledTimes(1); }); - it("toEditor converst entity to correct structure", async () => { + it("toEditor converts entity to correct structure", async () => { const chk = HubProject.fromJson( { id: "bc3", @@ -381,6 +394,35 @@ describe("HubProject Class:", () => { ); expect(result._groups).toEqual([]); }); + describe('auto-populating "shareWith" groups', () => { + let projectInstance: any; + beforeEach(async () => { + const _authdCtxMgr = await initContextManager({ + currentUser: { + groups: [ + { id: "00a", isViewOnly: false }, + { id: "00b", isViewOnly: true, memberType: "admin" }, + { id: "00d", isViewOnly: false }, + ] as PortalModule.IGroup[], + }, + }); + projectInstance = HubProject.fromJson({}, _authdCtxMgr.context); + }); + it('handles auto-populating "shareWith" groups that the current user can share to', async () => { + const result = await projectInstance.toEditor({ + contentGroupId: "00a", + collaborationGroupId: "00b", + }); + expect(result._groups).toEqual(["00a", "00b"]); + }); + it('does not auto-populate "shareWith" gruops that the current user cannot share to', async () => { + const result = await projectInstance.toEditor({ + contentGroupId: "00e", + collaborationGroupId: "00f", + }); + expect(result._groups).toEqual([]); + }); + }); }); describe("fromEditor:", () => {