Skip to content

Commit 2dd37bd

Browse files
abrilgzzdanielluo-msft
authored andcommitted
Show loader when fetching roles and signin in, prevent enable/disable and remove if user does not have write permissions
1 parent ee85885 commit 2dd37bd

File tree

4 files changed

+45
-33
lines changed

4 files changed

+45
-33
lines changed

Service/GroupMembershipManagement/Hosts/WebApi/Infrastructure/compute/template.bicep

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ param dataFactoryName string = '${solutionAbbreviation}-data-${environmentAbbrev
6161
@description('Name of the Azure Data Factory pipeline.')
6262
param adfPipeline string
6363

64-
@description('Enter the subscription id.')
65-
param authenticationType string = 'ClientSecret'
66-
6764
var subscriptionId = subscription().subscriptionId
6865
var appInsightsInstrumentationKey = resourceId(subscription().subscriptionId, dataResourceGroup, 'Microsoft.KeyVault/vaults/secrets', dataKeyVaultName, 'appInsightsInstrumentationKey')
6966
var webapiClientId = resourceId(subscription().subscriptionId, prereqsResourceGroup, 'Microsoft.KeyVault/vaults/secrets', prereqsKeyVaultName, 'webapiClientId')
@@ -151,10 +148,6 @@ var appSettings = [
151148
name: 'Settings:GraphCredentials:TenantId'
152149
value: '@Microsoft.KeyVault(SecretUri=${reference(graphAppTenantId, '2019-09-01').secretUriWithVersion})'
153150
}
154-
{
155-
name: 'Settings:GraphCredentials:AuthenticationType'
156-
value: authenticationType
157-
}
158151
{
159152
name: 'Settings:ActionableEmailProviderId'
160153
value: '@Microsoft.KeyVault(SecretUri=${reference(actionableEmailProviderId, '2019-09-01').secretUriWithVersion})'

UI/web-app/src/App/App.base.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
3-
43
import { classNamesFunction, type IProcessedStyleSet } from '@fluentui/react';
54
import { useTheme } from '@fluentui/react/lib/Theme';
6-
import React, { useEffect, useState } from 'react';
5+
import React, { useEffect } from 'react';
76

87
import { useSelector, useDispatch } from 'react-redux';
98
import { Outlet } from 'react-router-dom';
@@ -20,7 +19,7 @@ import { setLanguage } from '../store/localization.api';
2019
import { fetchSettings } from '../store/settings.api';
2120
import { AppFooter } from '../components/AppFooter';
2221
import { fetchDefaultSqlMembershipSource, fetchDefaultSqlMembershipSourceAttributes } from '../store/sqlMembershipSources.api';
23-
import { selectHasAccess } from '../store/roles.slice';
22+
import { selectHasAccess, selectIsFetchingRoles } from '../store/roles.slice';
2423

2524
const getClassNames = classNamesFunction<IAppStyleProps, IAppStyles>();
2625

@@ -37,6 +36,7 @@ export const AppBase: React.FunctionComponent<IAppProps> = (props: IAppProps) =>
3736
const profile = useSelector(selectProfile);
3837
const loggedIn = useSelector(selectLoggedIn);
3938
const hasAccess = useSelector(selectHasAccess);
39+
const isFetchingRoles = useSelector(selectIsFetchingRoles);
4040

4141
// run once after load.
4242
useEffect(() => {
@@ -58,7 +58,15 @@ export const AppBase: React.FunctionComponent<IAppProps> = (props: IAppProps) =>
5858
dispatch(setLanguage(profile?.userPreferredLanguage));
5959
}, [dispatch, profile?.userPreferredLanguage]);
6060

61-
if (loggedIn) {
61+
const showLoader = !loggedIn || isFetchingRoles;
62+
63+
if (showLoader) {
64+
return (
65+
<div className={classNames.root}>
66+
<Loader />
67+
</div>
68+
);
69+
} else {
6270
return (
6371
<div className={classNames.root}>
6472
<AppHeader />
@@ -73,11 +81,5 @@ export const AppBase: React.FunctionComponent<IAppProps> = (props: IAppProps) =>
7381
<AppFooter />
7482
</div>
7583
);
76-
} else {
77-
return (
78-
<div className={classNames.root}>
79-
<Loader />
80-
</div>
81-
);
8284
}
8385
};

UI/web-app/src/pages/JobDetails/JobDetails.base.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import {
5454
import { JobDetails } from '../../models/JobDetails';
5555
import { useStrings } from '../../store/hooks';
5656
import { setPagingBarVisible } from '../../store/pagingBar.slice';
57-
import { selectIsSubmissionReviewer } from '../../store/roles.slice';
57+
import { selectIsJobOwnerWriter, selectIsSubmissionReviewer } from '../../store/roles.slice';
5858
import { SyncStatus } from '../../models';
5959
import { OnboardingSteps } from '../../models/OnboardingSteps';
6060
import { fetchJobs } from '../../store/jobs.api';
@@ -96,6 +96,7 @@ export const JobDetailsBase: React.FunctionComponent<IJobDetailsProps> = (
9696
const removeGMMError = useSelector(selectRemoveGMMError);
9797
const jobsLoading = useSelector(selectJobsLoading);
9898
const removeGMMPending = useSelector(selectRemoveGMMLoading);
99+
const isJobWriter = useSelector(selectIsJobOwnerWriter);
99100
const showLoader = jobsLoading || removeGMMPending;
100101

101102
const OpenInNewWindowIcon: IIconProps = { iconName: 'OpenInNewWindow' };
@@ -206,13 +207,14 @@ export const JobDetailsBase: React.FunctionComponent<IJobDetailsProps> = (
206207
actionOnClick={openMembershipConfiguration}
207208
/>
208209
<div className={classNames.removeGMM}>
209-
<ActionButton
210-
iconProps={{ iconName: 'Delete' }}
211-
title={strings.JobDetails.labels.removeGMM}
212-
ariaLabel={strings.JobDetails.labels.removeGMM}
213-
onClick={onRemoveGMMButtonClick}>
214-
{strings.JobDetails.labels.removeGMM}
215-
</ActionButton>
210+
{isJobWriter &&
211+
<ActionButton
212+
iconProps={{ iconName: 'Delete' }}
213+
title={strings.JobDetails.labels.removeGMM}
214+
ariaLabel={strings.JobDetails.labels.removeGMM}
215+
onClick={onRemoveGMMButtonClick}>
216+
{strings.JobDetails.labels.removeGMM}
217+
</ActionButton>}
216218
</div>
217219
</div>
218220
<Dialog
@@ -228,10 +230,10 @@ export const JobDetailsBase: React.FunctionComponent<IJobDetailsProps> = (
228230
}}
229231
>
230232
<DialogFooter>
231-
<PrimaryButton
232-
onClick={onConfirmRemove}
233+
<PrimaryButton
234+
onClick={onConfirmRemove}
233235
text={strings.JobDetails.labels.removeGMMConfirmation}
234-
styles={{ root: { padding: '16px'} }}
236+
styles={{ root: { padding: '16px' } }}
235237
/>
236238
<DefaultButton onClick={onDialogClose} text={strings.cancel} />
237239
</DialogFooter>
@@ -278,6 +280,7 @@ const MembershipStatusContent: React.FunctionComponent<IContentProps> = (
278280
const patchResponse = useSelector(selectPatchJobDetailsResponse);
279281
const [jobStatus, setJobStatus] = useState(job.status);
280282
const [isJobEnabled, setIsJobEnabled] = useState(job.enabledOrNot);
283+
const isJobWriter = useSelector(selectIsJobOwnerWriter);
281284

282285
useEffect(() => {
283286
setJobStatus(job.status);
@@ -331,7 +334,7 @@ const MembershipStatusContent: React.FunctionComponent<IContentProps> = (
331334
inlineLabel={true}
332335
checked={isJobEnabled}
333336
onChange={handleStatusChange}
334-
disabled={jobStatus === SyncStatus.PendingReview || jobStatus === SyncStatus.SubmissionRejected}
337+
disabled={!isJobWriter || jobStatus === SyncStatus.PendingReview || jobStatus === SyncStatus.SubmissionRejected}
335338
/>
336339
<div>
337340
<div className={isJobEnabled ? classNames.jobEnabled : classNames.jobDisabled}>

UI/web-app/src/store/roles.slice.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createSlice } from '@reduxjs/toolkit';
55
import type { RootState } from './store';
66
import { getAllRoles } from './roles.api';
77

8-
// Define a type for the slice state
8+
// Define a type for the slice stat
99
export type Roles = {
1010
isJobOwnerReader: boolean;
1111
isJobOwnerWriter: boolean;
@@ -14,9 +14,10 @@ export type Roles = {
1414
isSubmissionReviewer: boolean;
1515
isHyperlinkAdministrator: boolean;
1616
isCustomMembershipProviderAdministrator: boolean;
17+
isFetchingRoles: boolean;
1718
}
1819

19-
// Define the initial state using that type
20+
// Define the initial state using that ty
2021
const initialState: Roles = {
2122
isJobOwnerReader: false,
2223
isJobOwnerWriter: false,
@@ -25,19 +26,28 @@ const initialState: Roles = {
2526
isSubmissionReviewer: false,
2627
isHyperlinkAdministrator: false,
2728
isCustomMembershipProviderAdministrator: false,
29+
isFetchingRoles: false,
2830
};
2931

3032
export const rolesSlice = createSlice({
3133
name: 'roles',
3234
initialState,
3335
reducers: { },
3436
extraReducers: (builder) => {
37+
builder.addCase(getAllRoles.pending, (state) => {
38+
state.isFetchingRoles = true;
39+
});
3540
builder.addCase(getAllRoles.fulfilled, (state, action) => {
36-
Object.assign(state, action.payload);
41+
Object.assign(state, action.payload);
42+
state.isFetchingRoles = false;
43+
});
44+
builder.addCase(getAllRoles.rejected, (state) => {
45+
state.isFetchingRoles = false;
3746
});
3847
}
3948
});
4049

50+
export const selectIsFetchingRoles = (state: RootState) => state.roles.isFetchingRoles;
4151
export const selectIsJobOwnerReader = (state: RootState) => state.roles.isJobOwnerReader;
4252
export const selectIsJobOwnerWriter = (state: RootState) => state.roles.isJobOwnerWriter;
4353
export const selectIsJobTenantReader = (state: RootState) => state.roles.isJobTenantReader;
@@ -50,8 +60,12 @@ export const selectHasAccess = (state: RootState) => {
5060
return state.roles.isJobOwnerReader || state.roles.isJobOwnerWriter || state.roles.isJobTenantReader || state.roles.isJobTenantWriter;
5161
};
5262

63+
export const selectHasJobWritePermissions = (state: RootState) => {
64+
return state.roles.isJobOwnerWriter || state.roles.isJobTenantWriter;
65+
};
66+
5367
export const selectIsJobWriter = (state: RootState) => {
5468
return state.roles.isJobOwnerWriter || state.roles.isJobTenantWriter;
5569
};
5670

57-
export default rolesSlice.reducer;
71+
export default rolesSlice.reducer;

0 commit comments

Comments
 (0)