From 4a4003fd1640979cb15f16706480de13d614c7b4 Mon Sep 17 00:00:00 2001 From: Charles Wahome Date: Mon, 6 Nov 2023 13:46:24 +0300 Subject: [PATCH] Task: scope permissions (#2867) --- .../actions/collections-action-creators.ts | 9 ++- .../CollectionPermissionsProvider.tsx | 57 ++++++++++---- .../services/reducers/collections-reducer.ts | 26 +++++-- src/app/services/redux-constants.ts | 1 + .../collection/CollectionPermissions.tsx | 14 ++-- .../collection/ManifestDescription.tsx | 77 ++++++------------- .../resource-explorer/collection/Paths.tsx | 39 ++++++++-- .../collection/PreviewCollection.tsx | 49 +++++++++--- .../collection/api-manifest.util.spec.ts | 32 +++++++- .../collection/api-manifest.util.ts | 29 ++++--- .../collection/collection.util.ts | 29 ++++--- src/types/resources.ts | 1 + 12 files changed, 235 insertions(+), 128 deletions(-) diff --git a/src/app/services/actions/collections-action-creators.ts b/src/app/services/actions/collections-action-creators.ts index a8b97ece2..9f55ca035 100644 --- a/src/app/services/actions/collections-action-creators.ts +++ b/src/app/services/actions/collections-action-creators.ts @@ -1,7 +1,7 @@ import { AppAction } from '../../../types/action'; import { COLLECTION_CREATE_SUCCESS, - RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS + RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS, RESOURCEPATHS_UPDATE_SUCCESS } from '../redux-constants'; export function addResourcePaths(response: object): AppAction { @@ -11,6 +11,13 @@ export function addResourcePaths(response: object): AppAction { }; } +export function updateResourcePaths(response: object): AppAction { + return { + type: RESOURCEPATHS_UPDATE_SUCCESS, + response + }; +} + export function createCollection(response: object): AppAction { return { type: COLLECTION_CREATE_SUCCESS, diff --git a/src/app/services/context/collection-permissions/CollectionPermissionsProvider.tsx b/src/app/services/context/collection-permissions/CollectionPermissionsProvider.tsx index 2b12cac61..0e3121681 100644 --- a/src/app/services/context/collection-permissions/CollectionPermissionsProvider.tsx +++ b/src/app/services/context/collection-permissions/CollectionPermissionsProvider.tsx @@ -1,17 +1,25 @@ -import { useMemo, useState } from 'react'; +import { ReactNode, useMemo, useState } from 'react'; import { CollectionPermission, Method, ResourcePath } from '../../../../types/resources'; -import { getVersionsFromPaths } from '../../../views/sidebar/resource-explorer/collection/collection.util'; +import { + getScopesFromPaths, getVersionsFromPaths, scopeOptions +} from '../../../views/sidebar/resource-explorer/collection/collection.util'; import { DEVX_API_URL } from '../../graph-constants'; import { CollectionPermissionsContext } from './CollectionPermissionsContext'; const DEVX_API_PERMISSIONS_URL = `${DEVX_API_URL}/api/permissions`; -function getRequestsFromPaths(paths: ResourcePath[], version: string) { - const requests: any[] = []; +interface CollectionRequest { + method: Method; + requestUrl: string; +} + +function getRequestsFromPaths(paths: ResourcePath[], version: string, scope: string) { + const requests: CollectionRequest[] = []; paths.forEach(path => { const { method, url } = path; - if (version === path.version) { + path.scope = path.scope ?? scopeOptions[0].key; + if (version === path.version && scope === path.scope) { requests.push({ method: method as Method, requestUrl: url @@ -23,22 +31,39 @@ function getRequestsFromPaths(paths: ResourcePath[], version: string) { async function getCollectionPermissions(paths: ResourcePath[]): Promise<{ [key: string]: CollectionPermission[] }> { const versions = getVersionsFromPaths(paths); + const scopes = getScopesFromPaths(paths); const collectionPermissions: { [key: string]: CollectionPermission[] } = {}; + const fetchPromises: Promise[] = []; + for (const version of versions) { - const response = await fetch(DEVX_API_PERMISSIONS_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(getRequestsFromPaths(paths, version)) - }); - const perms = await response.json(); - collectionPermissions[version] = (perms.results) ? perms.results : []; + for (const scope of scopes) { + const requestPaths = getRequestsFromPaths(paths, version, scope); + if (requestPaths.length === 0) { + continue; + } + const url = `${DEVX_API_PERMISSIONS_URL}?version=${version}&scopeType=${scope}`; + fetchPromises.push(fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(requestPaths) + })); + } } + + const responses = await Promise.all(fetchPromises); + + for (let i = 0; i < responses.length; i++) { + const perms = await responses[i].json(); + const key = `${versions[Math.floor(i / scopes.length)]}-${scopes[i % scopes.length]}`; + collectionPermissions[key] = (perms.results) ? perms.results : []; + } + return collectionPermissions; } -const CollectionPermissionsProvider = ({ children }: any) => { +const CollectionPermissionsProvider = ({ children }: { children: ReactNode }) => { const [permissions, setPermissions] = useState<{ [key: string]: CollectionPermission[] } | undefined>(undefined); const [isFetching, setIsFetching] = useState(false); const [code, setCode] = useState(''); @@ -62,7 +87,7 @@ const CollectionPermissionsProvider = ({ children }: any) => { return ( {children} + value={{ getPermissions, ...valueObject }}>{children} ); } diff --git a/src/app/services/reducers/collections-reducer.ts b/src/app/services/reducers/collections-reducer.ts index ef13177bf..8e7e898ac 100644 --- a/src/app/services/reducers/collections-reducer.ts +++ b/src/app/services/reducers/collections-reducer.ts @@ -2,7 +2,7 @@ import { AppAction } from '../../../types/action'; import { Collection, ResourcePath } from '../../../types/resources'; import { COLLECTION_CREATE_SUCCESS, - RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS + RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS, RESOURCEPATHS_UPDATE_SUCCESS } from '../redux-constants'; import { getUniquePaths } from './collections-reducer.util'; @@ -11,12 +11,13 @@ const initialState: Collection[] = []; export function collections(state: Collection[] = initialState, action: AppAction): Collection[] { switch (action.type) { - case COLLECTION_CREATE_SUCCESS: + case COLLECTION_CREATE_SUCCESS: { const items = [...state]; items.push(action.response); return items; + } - case RESOURCEPATHS_ADD_SUCCESS: + case RESOURCEPATHS_ADD_SUCCESS: { const index = state.findIndex(k => k.isDefault); if (index > -1) { const paths: ResourcePath[] = getUniquePaths(state[index].paths, action.response); @@ -24,9 +25,20 @@ export function collections(state: Collection[] = initialState, action: AppActio context[index].paths = paths; return context; } - return state + return state; + } + + case RESOURCEPATHS_UPDATE_SUCCESS: { + const collectionIndex = state.findIndex(k => k.isDefault); + if (collectionIndex > -1) { + const context = [...state]; + context[collectionIndex].paths = action.response; + return context; + } + return state; + } - case RESOURCEPATHS_DELETE_SUCCESS: + case RESOURCEPATHS_DELETE_SUCCESS: { const indexOfDefaultCollection = state.findIndex(k => k.isDefault); if (indexOfDefaultCollection > -1) { const list: ResourcePath[] = [...state[indexOfDefaultCollection].paths]; @@ -38,9 +50,9 @@ export function collections(state: Collection[] = initialState, action: AppActio newState[indexOfDefaultCollection].paths = list; return newState; } - return state + } default: return state; } -} +} \ No newline at end of file diff --git a/src/app/services/redux-constants.ts b/src/app/services/redux-constants.ts index 65cb0f06b..6716bf9f4 100644 --- a/src/app/services/redux-constants.ts +++ b/src/app/services/redux-constants.ts @@ -52,6 +52,7 @@ export const GET_POLICY_ERROR = 'GET_POLICY_ERROR'; export const GET_POLICY_PENDING = 'GET_POLICY_PENDING'; export const RESOURCEPATHS_ADD_SUCCESS = 'RESOURCEPATHS_ADD_SUCCESS'; export const RESOURCEPATHS_DELETE_SUCCESS = 'RESOURCEPATHS_DELETE_SUCCESS'; +export const RESOURCEPATHS_UPDATE_SUCCESS = 'RESOURCEPATHS_UPDATE_SUCCESS'; export const BULK_ADD_HISTORY_ITEMS_SUCCESS = 'BULK_ADD_HISTORY_ITEMS_SUCCESS'; export const SET_SNIPPET_TAB_SUCCESS = 'SET_SNIPPET_TAB_SUCCESS'; export const GET_ALL_PRINCIPAL_GRANTS_PENDING = 'GET_ALL_PRINCIPAL_GRANTS_PENDING'; diff --git a/src/app/views/sidebar/resource-explorer/collection/CollectionPermissions.tsx b/src/app/views/sidebar/resource-explorer/collection/CollectionPermissions.tsx index 2b8ca6c30..f14ccb687 100644 --- a/src/app/views/sidebar/resource-explorer/collection/CollectionPermissions.tsx +++ b/src/app/views/sidebar/resource-explorer/collection/CollectionPermissions.tsx @@ -1,5 +1,5 @@ -import { DefaultButton, DetailsList, DialogFooter, Label, PrimaryButton, SelectionMode } from '@fluentui/react'; -import React, { useEffect } from 'react'; +import { DefaultButton, DetailsList, DialogFooter, IGroup, Label, PrimaryButton, SelectionMode } from '@fluentui/react'; +import { FC, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import { useAppSelector } from '../../../../../store'; @@ -7,10 +7,11 @@ import { componentNames } from '../../../../../telemetry'; import { CollectionPermission } from '../../../../../types/resources'; import { PopupsComponent } from '../../../../services/context/popups-context'; import { useCollectionPermissions } from '../../../../services/hooks/useCollectionPermissions'; +import { generateGroupsFromList } from '../../../../utils/generate-groups'; import { translateMessage } from '../../../../utils/translate-messages'; import { downloadToLocal, trackDownload } from '../../../common/download'; -const CollectionPermissions: React.FC> = (props) => { +const CollectionPermissions: FC> = (props) => { const { getPermissions, permissions, isFetching } = useCollectionPermissions(); const { collections } = useAppSelector( @@ -23,10 +24,6 @@ const CollectionPermissions: React.FC> = (props) => { key: 'value', name: translateMessage('Value'), fieldName: 'value', minWidth: 300, ariaLabel: translateMessage('Value') - }, - { - key: 'scopeType', name: translateMessage('Scope Type'), fieldName: 'scopeType', minWidth: 200, - ariaLabel: translateMessage('Scope Type') } ]; @@ -65,10 +62,12 @@ const CollectionPermissions: React.FC> = (props) => { } const permissionsArray: CollectionPermission[] = []; + let groups: IGroup[] | undefined = []; if (permissions) { Object.keys(permissions).forEach(key => { permissionsArray.push(...permissions[key]); }); + groups = generateGroupsFromList(permissionsArray, 'scopeType') } return ( @@ -76,6 +75,7 @@ const CollectionPermissions: React.FC> = (props) => { {permissions && diff --git a/src/app/views/sidebar/resource-explorer/collection/ManifestDescription.tsx b/src/app/views/sidebar/resource-explorer/collection/ManifestDescription.tsx index 7412c93a6..dc2e960ce 100644 --- a/src/app/views/sidebar/resource-explorer/collection/ManifestDescription.tsx +++ b/src/app/views/sidebar/resource-explorer/collection/ManifestDescription.tsx @@ -1,31 +1,28 @@ import { - ChoiceGroup, - FontSizes, FontWeights, IChoiceGroupOption, Link, + FontSizes, FontWeights, + Link, PrimaryButton, Spinner, Stack, VerticalDivider, getTheme, mergeStyleSets } from '@fluentui/react'; -import React, { FormEvent, useCallback, useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { useAppSelector } from '../../../../../store'; import { componentNames, eventTypes, telemetry } from '../../../../../telemetry'; import { APIManifest } from '../../../../../types/api-manifest'; import { PopupsComponent } from '../../../../services/context/popups-context'; -import { API_MANIFEST_SPEC_PAGE, PERMS_SCOPE } from '../../../../services/graph-constants'; +import { API_MANIFEST_SPEC_PAGE } from '../../../../services/graph-constants'; import { useCollectionPermissions } from '../../../../services/hooks/useCollectionPermissions'; -import { translateMessage } from '../../../../utils/translate-messages'; import { trackedGenericCopy } from '../../../common/copy'; import { downloadToLocal, trackDownload } from '../../../common/download'; import { generateAPIManifest } from './api-manifest.util'; - -const ManifestDescription: React.FC> = () => { - const { permissions, isFetching } = useCollectionPermissions(); +const ManifestDescription: FC> = () => { + const { permissions, isFetching, getPermissions } = useCollectionPermissions(); const [manifest, setManifest] = useState(); const [isGeneratingManifest, setIsGeneratingManifest] = useState(false); - const [selectedScope, setSelectedScope] = useState(''); const [manifestCopied, setManifestCopied] = useState(false); const manifestStyle = mergeStyleSets( @@ -69,38 +66,27 @@ const ManifestDescription: React.FC> = () => { } ); - const options: IChoiceGroupOption[] = [ - { - key: `${PERMS_SCOPE.WORK}`, - text: translateMessage('Delegated work'), - disabled: isGeneratingManifest - }, - { - key: `${PERMS_SCOPE.APPLICATION}`, - text: translateMessage('Application permissions'), - disabled: isGeneratingManifest - }, - { - key: `${PERMS_SCOPE.APPLICATION}_${PERMS_SCOPE.WORK}`, - text: translateMessage('Delegated & application permissions'), - disabled: isGeneratingManifest - } - ]; - const { collections } = useAppSelector( (state) => state ); const paths = collections ? collections.find(k => k.isDefault)!.paths : []; useEffect(() => { - if (!isFetching && selectedScope !== '') { - const generatedManifest = generateAPIManifest({ paths, permissions, scopeType: selectedScope }); + if (paths.length > 0) { + getPermissions(paths); + } + }, [paths]); + + useEffect(() => { + if (permissions && paths.length > 0) { + setIsGeneratingManifest(true); + const generatedManifest = generateAPIManifest({ paths, permissions }); if (Object.keys(generatedManifest).length > 0) { setIsGeneratingManifest(false); - setManifest(generatedManifest); } + setManifest(generatedManifest); } - }, [selectedScope, isFetching]); + }, [permissions]); const downloadManifest = () => { if (!manifest) { return; } @@ -119,13 +105,6 @@ const ManifestDescription: React.FC> = () => { setManifestCopied(false); } - const onSelectionChange = useCallback((ev: FormEvent | undefined, - option: IChoiceGroupOption | undefined) => { - setSelectedScope(option!.key); - setIsGeneratingManifest(true); - setManifestCopied(false); - }, []); - const copyManifestToClipboard = () => { if (!manifest) { return; } const base64UrlEncodedManifest = btoa(JSON.stringify(manifest)); @@ -154,14 +133,6 @@ const ManifestDescription: React.FC> = () => {
- - - - -
@@ -173,11 +144,9 @@ const ManifestDescription: React.FC> = () => {  
-

-
> = () => {
+ {isFetching && <> + Fetching permissions +
+ } - - {isGeneratingManifest && <> Fetching permissions   } {!isGeneratingManifest && } - - {isGeneratingManifest && <> Fetching permissions   } {!isGeneratingManifest && } diff --git a/src/app/views/sidebar/resource-explorer/collection/Paths.tsx b/src/app/views/sidebar/resource-explorer/collection/Paths.tsx index ae9589f6c..b9c42215a 100644 --- a/src/app/views/sidebar/resource-explorer/collection/Paths.tsx +++ b/src/app/views/sidebar/resource-explorer/collection/Paths.tsx @@ -1,16 +1,20 @@ -import { Component } from 'react'; +import { Dropdown, IDropdownOption, MarqueeSelection, TooltipHost, getId } from '@fluentui/react'; import { DetailsList, DetailsListLayoutMode, IColumn, Selection } from '@fluentui/react/lib/DetailsList'; -import { getId, MarqueeSelection, TooltipHost } from '@fluentui/react'; +import { Component } from 'react'; + import { ResourcePath } from '../../../../../types/resources'; +import { ScopeOption, scopeOptions } from './collection.util'; interface IPathProps { resources: ResourcePath[]; columns: IColumn[]; selectItems: Function; + setSelectedScope: (resource: ResourcePath, scope: string) => void; } + export default class Paths extends Component { private _selection: Selection; @@ -24,9 +28,32 @@ export default class Paths extends Component { } }); } - private renderItemColumn = (item: any, index: number | undefined, column: IColumn | undefined) => { + + public componentDidUpdate(prevProps: IPathProps): void { + if (prevProps.resources !== this.props.resources) { + this._selection.setAllSelected(false); + } + } + + private renderItemColumn = (item: ResourcePath, index: number | undefined, column: IColumn | undefined) => { + + const selectedItems = this._selection.getSelection(); + + const handleOnScopeChange = (_event: React.FormEvent, option?: IDropdownOption) => { + this.props.setSelectedScope(item, option?.key as string) + this.props.selectItems([]); + }; + if (column) { - const itemContent = item[column.fieldName as keyof any] as string; + if (column.key === 'scope') { + return 1} + styles={{ dropdown: { width: 300 } }} + />; + } return ( { > {item.method} - {`/${item.version}${itemContent}`} + {`/${item.version}${item.url}`} ); } } - public render(): JSX.Element { const { resources, columns } = this.props; - return ( > = (props) => { const dispatch: AppDispatch = useDispatch(); const { show: showManifestDescription } = usePopups('manifest-description', 'panel') const { show: viewPermissions } = usePopups('collection-permissions', 'panel'); - const { getPermissions } = useCollectionPermissions(); const { collections } = useAppSelector( (state) => state ); - const items = collections && collections.length > 0 ? collections.find(k => k.isDefault)!.paths : []; + const items = collections && collections.length > + 0 ? collections.find(k => k.isDefault)!.paths : []; + const [selectedItems, setSelectedItems] = useState([]); const columns = [ - { key: 'url', name: 'URL', fieldName: 'url', minWidth: 300, maxWidth: 350, isResizable: true } + { key: 'url', name: 'URL', fieldName: 'url', minWidth: 300, maxWidth: 350, isResizable: true }, + { key: 'scope', name: 'Scope', fieldName: 'scope', minWidth: 300, maxWidth: 350, isResizable: true } ]; const generateCollection = () => { @@ -64,6 +66,20 @@ const PathsReview: React.FC> = (props) => { iconProps: { iconName: 'Delete' }, disabled: selectedItems.length === 0, onClick: () => removeSelectedItems() + }, + { + key: 'set-scope', + text: translateMessage('Set scope'), + iconProps: { iconName: 'AzureKeyVault' }, + disabled: selectedItems.length === 0, + subMenuProps: { + items: scopeOptions.map((option) => { + return { + key: option.key, text: option.text, + onClick: () => bulkSelectScope(option.key as string) + } + }) + } } ]; @@ -86,9 +102,23 @@ const PathsReview: React.FC> = (props) => { } }, [items]); - useEffect(() => { - getPermissions(items); - }, [items.length]) + const setSelectedScope = (resource: ResourcePath, scope: string): void => { + const itemResources = [...items] + itemResources[itemResources.findIndex((item) => + item.key === resource.key)].scope = scope; + dispatch(updateResourcePaths(itemResources)); + setSelectedItems([]); + } + + const bulkSelectScope = (scope: string): void => { + const itemResources = [...items] + selectedItems.map((resource) => { + itemResources[itemResources.findIndex((item) => + item.key === resource.key)].scope = scope; + }) + dispatch(updateResourcePaths(itemResources)); + setSelectedItems([]); + } return ( <> @@ -127,6 +157,7 @@ const PathsReview: React.FC> = (props) => { resources={items} columns={columns} selectItems={selectItems} + setSelectedScope={setSelectedScope} /> } diff --git a/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.spec.ts b/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.spec.ts index 19a0e2b61..30979f945 100644 --- a/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.spec.ts +++ b/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.spec.ts @@ -1,17 +1,41 @@ -import { IResource } from '../../../../../types/resources'; +import { IResource, IResourceLink } from '../../../../../types/resources'; import content from '../../../../utils/resources/resources.json'; import { createResourcesList, getResourcePaths } from '../resource-explorer.utils'; import { generateAPIManifest } from './api-manifest.util'; const resource = JSON.parse(JSON.stringify(content)) as IResource; +const permissions = { + 'v1.0-DelegatedWork': [ + { + 'value': 'Place.Read.All', + 'scopeType': 'DelegatedWork', + 'consentDisplayName': 'Read all company places', + 'consentDescription': 'Allows the app to read your company\'s places', + 'isAdmin': true, + 'isLeastPrivilege': true, + 'isHidden': false + }, + { + 'value': 'Place.ReadWrite.All', + 'scopeType': 'DelegatedWork', + 'consentDisplayName': 'Read and write organization places', + 'consentDescription': 'Allows the app to manage organization places', + 'isAdmin': true, + 'isLeastPrivilege': true, + 'isHidden': false + } + ] +} + describe('API Manifest should', () => { it.only('have requests generated', async () => { const version = 'v1.0'; + const scopeType = 'DelegatedWork'; const filtered = createResourcesList(resource.children!, version)[0]; - const item: any = filtered.links[0]; + const item = filtered.links[0] as IResourceLink; const paths = getResourcePaths(item, version); - const manifest = generateAPIManifest({ paths, permissions: undefined, scopeType: 'Application_DelegatedWork' }); - expect(manifest.apiDependencies[`graph-${version}`].requests.length).toBe(paths.length); + const manifest = generateAPIManifest({ paths, permissions }); + expect(manifest.apiDependencies[`graph-${version}-${scopeType}`].requests.length).toBe(paths.length); }); }); \ No newline at end of file diff --git a/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.ts b/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.ts index 3861853c3..7962383d3 100644 --- a/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.ts +++ b/src/app/views/sidebar/resource-explorer/collection/api-manifest.util.ts @@ -1,34 +1,35 @@ import { APIManifest, Access, ApiDependencies, ManifestRequest } from '../../../../../types/api-manifest'; import { CollectionPermission, ResourcePath } from '../../../../../types/resources'; import { GRAPH_BETA_DESCRIPTION_URL, GRAPH_URL, GRAPH_V1_DESCRIPTION_URL } from '../../../../services/graph-constants'; -import { getVersionsFromPaths } from './collection.util'; +import { scopeOptions } from './collection.util'; interface ManifestProps { paths: ResourcePath[]; permissions?: { [key: string]: CollectionPermission[]; }; - scopeType: string; } -export function generateAPIManifest({ paths, permissions, scopeType }: ManifestProps): APIManifest { +export function generateAPIManifest({ paths, permissions }: ManifestProps): APIManifest { return { publisher: { name: 'Microsoft Graph', contactEmail: 'graphsdkpub@microsoft.com' }, - apiDependencies: getDependenciesFromPaths({ paths, permissions, scopeType }) + apiDependencies: getDependenciesFromPaths({ paths, permissions }) }; } -function getDependenciesFromPaths({ paths, permissions, scopeType }: ManifestProps): ApiDependencies { +function getDependenciesFromPaths({ paths, permissions }: ManifestProps): ApiDependencies { const dependencies: ApiDependencies = {}; - const versions: string[] = getVersionsFromPaths(paths); + const variants = Object.keys(permissions!); - versions.forEach(version => { - const dependencyName = `graph-${version}`; - const accessPermissions = permissions ? permissions[version] : []; + variants.forEach(variant => { + const dependencyName = `graph-${variant}`; + const accessPermissions = permissions ? permissions[variant] : []; + const version = variant.split('-')[0]; + const scopeType = variant.split('-')[1]; dependencies[dependencyName] = { apiDescriptionUrl: version === 'beta' ? GRAPH_BETA_DESCRIPTION_URL : GRAPH_V1_DESCRIPTION_URL, apiDeploymentBaseUrl: GRAPH_URL, @@ -37,18 +38,19 @@ function getDependenciesFromPaths({ paths, permissions, scopeType }: ManifestPro clientIdentifier: '', access: getAccessFromPermissions(accessPermissions, scopeType) }, - requests: getRequestsFromPaths(paths, version) + requests: getRequestsFromPaths(paths, version, scopeType) } }); return dependencies; } -function getRequestsFromPaths(paths: ResourcePath[], version: string): ManifestRequest[] { +function getRequestsFromPaths(paths: ResourcePath[], version: string, scopeType: string): ManifestRequest[] { const requests: ManifestRequest[] = []; paths.forEach(path => { const { method, url } = path; - if (path.version === version) { + path.scope = path.scope ? path.scope : scopeOptions[0].key; + if (path.version === version && path.scope === scopeType) { requests.push({ method: method!.toString().toUpperCase(), uriTemplate: `${url}` @@ -59,9 +61,6 @@ function getRequestsFromPaths(paths: ResourcePath[], version: string): ManifestR } function getAccessFromPermissions(permissions: CollectionPermission[], scopeType: string): Access[] { - if (scopeType === 'Application_DelegatedWork') { - return [createAccessPermissions(permissions, 'Application'), createAccessPermissions(permissions, 'DelegatedWork')]; - } return [createAccessPermissions(permissions, scopeType)]; } diff --git a/src/app/views/sidebar/resource-explorer/collection/collection.util.ts b/src/app/views/sidebar/resource-explorer/collection/collection.util.ts index 05fa4fd1a..7e3c52199 100644 --- a/src/app/views/sidebar/resource-explorer/collection/collection.util.ts +++ b/src/app/views/sidebar/resource-explorer/collection/collection.util.ts @@ -1,13 +1,24 @@ import { ResourcePath } from '../../../../../types/resources'; +import { PERMS_SCOPE } from '../../../../services/graph-constants'; -function getVersionsFromPaths(paths: ResourcePath[]) { - const versions: string[] = []; - paths.forEach(path => { - if (!versions.includes(path.version!)) { - versions.push(path.version!); - } - }); - return versions; +interface ScopeOption { + key: PERMS_SCOPE; + text: PERMS_SCOPE; } -export { getVersionsFromPaths }; +const scopeOptions: ScopeOption[] = Object.entries(PERMS_SCOPE).map(([_key, value]) => ({ + key: value, + text: value as PERMS_SCOPE +})); + +function getScopesFromPaths(paths: ResourcePath[]): string[] { + const scopes = paths.map(path => path.scope ?? scopeOptions[0].key); + return [...new Set(scopes)]; +} + +function getVersionsFromPaths(paths: ResourcePath[]): string[] { + const versions = paths.map(path => path.version!); + return [...new Set(versions)]; +} + +export { getVersionsFromPaths, scopeOptions, ScopeOption, getScopesFromPaths }; diff --git a/src/types/resources.ts b/src/types/resources.ts index a3a7bc51c..bb6aa5021 100644 --- a/src/types/resources.ts +++ b/src/types/resources.ts @@ -36,6 +36,7 @@ export interface ResourcePath { method?: string; key?: string; url: string; + scope?: string; } export enum ResourceLinkType {