Skip to content

Commit

Permalink
fix: #1076 fetch before navigation (#1077)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianliuwk1019 authored Dec 12, 2023
1 parent efbba1c commit b8c0cd8
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 141 deletions.
54 changes: 23 additions & 31 deletions frontend/src/components/grantaccess/GrantAccess.vue
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
<script setup lang="ts">
import router from '@/router';
import { ErrorMessage, Field, Form as VeeForm } from 'vee-validate';
import { onMounted, ref } from 'vue';
import { number, object, string } from 'yup';
import Dropdown from 'primevue/dropdown';
import InputText from 'primevue/inputtext';
import RadioButton from 'primevue/radiobutton';
import { ErrorMessage, Field, Form as VeeForm } from 'vee-validate';
import { ref, type PropType } from 'vue';
import { number, object, string } from 'yup';
import ApiServiceFactory from '@/services/ApiServiceFactory';
import Button from '@/components/common/Button.vue';
import ForestClientCard from '@/components/grantaccess/ForestClientCard.vue';
import UserIdentityCard from '@/components/grantaccess/UserIdentityCard.vue';
import { IconSize } from '@/enum/IconEnum';
import { Severity } from '@/enum/SeverityEnum';
import { setGrantAccessNotificationMsg } from '@/store/NotificationState';
import {
selectedApplication,
selectedApplicationDisplayText,
} from '@/store/ApplicationState';
import { AppActlApiService } from '@/services/ApiServiceFactory';
import { selectedApplicationDisplayText } from '@/store/ApplicationState';
import LoadingState from '@/store/LoadingState';
import { setGrantAccessNotificationMsg } from '@/store/NotificationState';
import {
FamForestClientStatusType,
UserType,
type FamApplicationRole,
type FamForestClient,
type IdimProxyIdirInfo,
type FamUserRoleAssignmentCreate,
UserType,
type IdimProxyIdirInfo,
} from 'fam-app-acsctl-api';
import { requireInjection } from '@/services/utils';
// Inject App Access Control Api service
const appActlApiService = requireInjection(ApiServiceFactory.APP_ACCESS_CONTROL_API_SERVICE_KEY);
const props = defineProps({
applicationRoleOptions: {
// options fetched from route.
type: Array as PropType<FamApplicationRole[]>,
default: [],
},
});
const FOREST_CLIENT_INPUT_MAX_LENGTH = 8;
const domainOptions = { IDIR: UserType.I, BCEID: UserType.B };
Expand Down Expand Up @@ -65,21 +66,11 @@ const formValidationSchema = object({
});
const verifiedUserIdentity = ref<IdimProxyIdirInfo | null>(null);
const applicationRoleOptions = ref<FamApplicationRole[]>([]);
const forestClientData = ref<FamForestClient[]>([]);
const forestClientNumberVerifyErrors = ref([] as Array<string>);
onMounted(async () => {
applicationRoleOptions.value = (
await appActlApiService
.applicationsApi.getFamApplicationRoles(
selectedApplication.value?.application_id as number
)
).data;
});
/* ------------------ User information method ------------------------- */
const isIdirDomainSelected = () => {
return formData.value.domain === domainOptions.IDIR;
};
Expand All @@ -102,13 +93,14 @@ const verifyUserId = async (userId: string, domain: string) => {
if (domain == domainOptions.BCEID) return; // IDIR search currently, no BCeID yet.
verifiedUserIdentity.value = (
await appActlApiService.idirBceidProxyApi.idirSearch(userId)
await AppActlApiService.idirBceidProxyApi.idirSearch(userId)
).data;
};
/* ------------------- Role selection method -------------------------- */
const getSelectedRole = (): FamApplicationRole | undefined => {
return applicationRoleOptions.value?.find(
return props.applicationRoleOptions?.find(
(item) => item.role_id === formData.value.role_id
);
};
Expand All @@ -118,6 +110,7 @@ const isAbstractRoleSelected = () => {
};
/* ----------------- Forest client number method ----------------------- */
const removeForestClientSection = () => {
// cleanup the forest client number input field
cleanupForestClientNumberInput();
Expand All @@ -140,8 +133,8 @@ const verifyForestClientNumber = async (forestClientNumbers: string) => {
`Client ID ${item} is invalid and cannot be added.`
);
}
await appActlApiService
.forestClientsApi.search(item)
await AppActlApiService.forestClientsApi
.search(item)
.then((result) => {
if (!result.data[0]) {
forestClientNumberVerifyErrors.value.push(
Expand Down Expand Up @@ -233,8 +226,7 @@ const handleSubmit = async () => {
: '';
const data = toRequestPayload(formData.value, item);
await appActlApiService
.userRoleAssignmentApi
await AppActlApiService.userRoleAssignmentApi
.createUserRoleAssignment(data)
.then(() => {
successForestClientIdList.push(itemForestClientNumber);
Expand Down
81 changes: 23 additions & 58 deletions frontend/src/components/managePermissions/ManagePermissions.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { onMounted, onUnmounted, shallowRef } from 'vue';
import Dropdown, { type DropdownChangeEvent } from 'primevue/dropdown';
import { onUnmounted, shallowRef, type PropType } from 'vue';
import ManagePermissionsTitle from '@/components/managePermissions/ManagePermissionsTitle.vue';
import UserDataTable from '@/components/managePermissions/UserDataTable.vue';
import ApiServiceFactory from '@/services/ApiServiceFactory';
import { Severity } from '@/enum/SeverityEnum';
import {
applicationsUserAdministers,
isApplicationSelected,
Expand All @@ -14,78 +14,43 @@ import {
} from '@/store/ApplicationState';
import LoadingState from '@/store/LoadingState';
import {
setNotificationMsg,
resetNotification,
setNotificationMsg,
} from '@/store/NotificationState';
import { Severity } from '@/enum/SeverityEnum';
import type { FamApplicationUserRoleAssignmentGet } from 'fam-app-acsctl-api';
import { requireInjection } from '@/services/utils';
// Inject App Access Control Api service
const appActlApiService = requireInjection(ApiServiceFactory.APP_ACCESS_CONTROL_API_SERVICE_KEY);
const userRoleAssignments = shallowRef<FamApplicationUserRoleAssignmentGet[]>();
onMounted(async () => {
// Reload list each time we navigate to this page to avoid forcing user to refresh if their access changes.
applicationsUserAdministers.value = (
await appActlApiService.applicationsApi.getApplications()
).data;
if (isApplicationSelected) {
await getAppUserRoleAssignment();
}
import {
deletAndRefreshUserRoleAssignments,
fetchUserRoleAssignments,
} from '@/services/fetchData';
const props = defineProps({
userRoleAssignments: {
type: Array as PropType<FamApplicationUserRoleAssignmentGet[]>,
default: [],
},
});
const userRoleAssignments = shallowRef<
FamApplicationUserRoleAssignmentGet[]
>(props.userRoleAssignments);
onUnmounted(() => {
resetNotification();
});
const getAppUserRoleAssignment = async () => {
if (!selectedApplication.value) return;
const userRoleAssignmentList = (
await appActlApiService
.applicationsApi.getFamApplicationUserRoleAssignment(
selectedApplication.value.application_id
)
).data;
// Fix Sonar Array "sort" issue
userRoleAssignmentList.sort((first, second) => {
const nameCompare = first.user.user_name.localeCompare(
second.user.user_name
);
if (nameCompare != 0) return nameCompare;
const roleCompare = first.role.role_name.localeCompare(
second.role.role_name
);
return roleCompare;
});
userRoleAssignments.value = userRoleAssignmentList;
};
const selectApplication = async (e: DropdownChangeEvent) => {
const onApplicationSelected = async (e: DropdownChangeEvent) => {
setSelectedApplication(e.value ? JSON.stringify(e.value) : null);
if (
applicationsUserAdministers.value &&
applicationsUserAdministers.value.length > 0
) {
await getAppUserRoleAssignment();
}
userRoleAssignments.value = await fetchUserRoleAssignments(selectedApplication.value?.application_id);
};
async function deleteUserRoleAssignment(
assignment: FamApplicationUserRoleAssignmentGet
) {
try {
await appActlApiService
.userRoleAssignmentApi
.deleteUserRoleAssignment(
assignment.user_role_xref_id
userRoleAssignments.value = await deletAndRefreshUserRoleAssignments(
assignment.user_role_xref_id,
assignment.role.application_id
);
userRoleAssignments.value = userRoleAssignments.value!.filter((a) => {
return a.user_role_xref_id != assignment.user_role_xref_id;
});
setNotificationMsg(
Severity.success,
Expand All @@ -108,7 +73,7 @@ async function deleteUserRoleAssignment(
<label>You are modifying access in this application:</label>
<Dropdown
v-model="selectedApplication"
@change="selectApplication"
@change="onApplicationSelected"
:options="applicationsUserAdministers"
optionLabel="application_description"
placeholder="Choose an application to manage permissions"
Expand Down
8 changes: 0 additions & 8 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import PrimeVue from 'primevue/config';
import ConfirmationService from 'primevue/confirmationservice';
import ToastService from 'primevue/toastservice';

import ApiServiceFactory from '@/services/ApiServiceFactory';
import 'primevue/resources/primevue.min.css';
import 'primevue/resources/themes/bootstrap4-light-blue/theme.css';
import './assets/styles/styles.scss';
Expand All @@ -23,12 +22,5 @@ app.use(ToastService);
app.use(ConfirmationService);
app.use(PrimeVue);

// Global provided services.
const apiServiceProvider = new ApiServiceFactory();
app.provide(ApiServiceFactory.ADMIN_MANAGEMENT_API_SERVICE_KEY,
apiServiceProvider.getAdminManagementApiService());
app.provide(ApiServiceFactory.APP_ACCESS_CONTROL_API_SERVICE_KEY,
apiServiceProvider.getAppAccessControlApiService());

app.use(router).mount('#app');
export { app };
61 changes: 49 additions & 12 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import GrantAccessView from '@/views/GrantAccessView.vue';
import LandingView from '@/views/LandingView.vue';
import ManagePermissionsView from '@/views/ManagePermissionsView.vue';
import { populateBreadcrumb } from '@/store/BreadcrumbState';
import {
fetchApplicationRoles,
fetchApplications,
fetchUserRoleAssignments,
} from '@/services/fetchData';
import { selectedApplication } from '@/store/ApplicationState';

// WARNING: any components referenced below that themselves reference the router cannot be automatically hot-reloaded in local development due to circular dependency
// See vitejs issue https://github.com/vitejs/vite/issues/3033 for discussion.
Expand All @@ -22,12 +28,12 @@ import { populateBreadcrumb } from '@/store/BreadcrumbState';
// This fixes the issue, but seems to break using shared state (e.g. in ApplicationService).

export interface IRouteInfo {
label: string,
to: string
};
label: string;
to: string;
}

export type RouteItems = {
[key: string]: IRouteInfo
[key: string]: IRouteInfo;
};

const routeItems = {
Expand All @@ -37,8 +43,8 @@ const routeItems = {
},
addUserPermission: {
to: '/grant',
label: 'Add user permission'
}
label: 'Add user permission',
},
} as RouteItems;

const routes = [
Expand All @@ -48,7 +54,7 @@ const routes = [
meta: {
title: 'Welcome to FAM',
layout: 'SimpleLayout',
hasBreadcrumb: false
hasBreadcrumb: false,
},
component: LandingView,
},
Expand All @@ -58,22 +64,53 @@ const routes = [
meta: {
title: routeItems.dashboard.label,
layout: 'ProtectedLayout',
hasBreadcrumb: false
hasBreadcrumb: false,
},
component: ManagePermissionsView,
beforeEnter: async (to: any) => {
// Requires fetching applications the user administers.
await fetchApplications();
const userRoleAssignments = await fetchUserRoleAssignments(
selectedApplication.value?.application_id
);
Object.assign(to.meta, { userRoleAssignments: userRoleAssignments });
return true;
},
props: (route: any) => {
return {
// userRoleAssignments is ready for the `component` as props.
userRoleAssignments: route.meta.userRoleAssignments,
};
},
},
{
path: routeItems.addUserPermission.to,
name: routeItems.addUserPermission.label,
meta: {
title: routeItems.addUserPermission.label,
layout: 'ProtectedLayout',
hasBreadcrumb: true
hasBreadcrumb: true,
},
component: GrantAccessView,
beforeEnter: () => {
populateBreadcrumb([routeItems.dashboard, routeItems.addUserPermission])
}
beforeEnter: async (to: any) => {
populateBreadcrumb([
routeItems.dashboard,
routeItems.addUserPermission,
]);
// Passing fetched data to router.meta (so it is available for assigning to 'props' later)
Object.assign(to.meta, {
applicationRoleOptions: await fetchApplicationRoles(
selectedApplication.value?.application_id
),
});
return true;
},
props: (route: any) => {
return {
// options is ready for the `component` as props.
applicationRoleOptions: route.meta.applicationRoleOptions,
};
},
},
{
path: '/authCallback',
Expand Down
Loading

0 comments on commit b8c0cd8

Please sign in to comment.