Skip to content

Commit

Permalink
Merge branch 'main' into renovate/all-minor-patch
Browse files Browse the repository at this point in the history
  • Loading branch information
craigyu authored Dec 18, 2024
2 parents bae5d76 + 15983a7 commit 2093608
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { inject, type Ref } from "vue";
import Dropdown from "../UI/Dropdown.vue";
import NotificationMessage from "../UI/NotificationMessage.vue";
import SubsectionTitle from "../UI/SubsectionTitle.vue";
import ForestClientSection from "./ForestClientSection.vue";
import ForestClientSection from "./ForestClientAddTable.vue";
const formData = inject<Ref<AppPermissionFormType>>(APP_PERMISSION_FORM_KEY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ onUnmounted(() => {
</script>

<template>
<div class="foresnt-client-section-container">
<div class="foresnt-client-add-table-container">
<SubsectionTitle
title="Restrict access by organizations"
subtitle="Add one or more organizations for this user to have access to"
Expand Down Expand Up @@ -237,7 +237,7 @@ onUnmounted(() => {
</template>

<style lang="scss">
.foresnt-client-section-container {
.foresnt-client-add-table-container {
.subsection-title-container {
margin: 1.5rem 0;
}
Expand Down
167 changes: 167 additions & 0 deletions frontend/src/components/AddPermissions/ForestClientSelectTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<script setup lang="ts">
import DataTable from "primevue/datatable";
import Column from "primevue/column";
import Checkbox from "primevue/checkbox";
import RadioButton from "primevue/radiobutton";
import { AdminMgmtApiService } from "@/services/ApiServiceFactory";
import { useQuery } from "@tanstack/vue-query";
import { Field, useField } from "vee-validate";
import { computed, inject, watch, type Ref } from "vue";
import { APP_PERMISSION_FORM_KEY } from "@/constants/InjectionKeys";
import type { AppPermissionFormType } from "@/views/AddAppPermission/utils";
import Label from "../UI/Label.vue";
import SubsectionTitle from "../UI/SubsectionTitle.vue";
import { getForestClientsUnderApp } from "@/utils/AuthUtils";
import type { FamForestClientBase } from "fam-admin-mgmt-api/model";
import ErrorText from "../UI/ErrorText.vue";
const formData = inject<Ref<AppPermissionFormType>>(APP_PERMISSION_FORM_KEY);
if (!formData) {
throw new Error("formData is required but not provided");
}
const props = defineProps<{
appId: number;
fieldId: string;
}>();
const { validate: validateForestClients } = useField(props.fieldId);
const adminUserAccessQuery = useQuery({
queryKey: ["admin-user-access"],
queryFn: () =>
AdminMgmtApiService.adminUserAccessesApi
.adminUserAccessPrivilege()
.then((res) => res.data),
select: (data) => getForestClientsUnderApp(props.appId, data),
refetchOnMount: true,
});
const availableForestClients = computed<FamForestClientBase[]>(() => {
const data = adminUserAccessQuery.data.value;
return data ?? [];
});
watch(
availableForestClients,
() => {
if (availableForestClients.value.length === 1) {
formData.value.forestClients = availableForestClients.value;
}
},
{ immediate: true }
);
const isForestClientSelected = (client: FamForestClientBase) =>
formData.value.forestClients.some(
(selectedClient) =>
selectedClient.forest_client_number === client.forest_client_number
);
const toggleForestClient = (client: FamForestClientBase) => {
const index = formData.value.forestClients.findIndex(
(selectedClient) =>
selectedClient.forest_client_number === client.forest_client_number
);
if (index >= 0) {
// Remove client if already selected
formData.value.forestClients.splice(index, 1);
} else {
// Add client if not selected
formData.value.forestClients.push(client);
}
// Validate again to remove resolved error if there is any
validateForestClients();
};
</script>

<template>
<div class="foresnt-client-select-table-container">
<SubsectionTitle
title="Restrict access by organizations"
subtitle="Select one or more organizations for this to access"
/>

<Field
:name="props.fieldId"
v-slot="{ errorMessage }"
v-model="formData.forestClients"
>
<Label label-text="Organizations" required />

<ErrorText
v-if="errorMessage"
show-icon
:error-msg="errorMessage"
/>

<!-- Table section -->
<DataTable class="fam-table" :value="availableForestClients">
<template #empty>No organization available</template>

<Column v-if="availableForestClients.length === 1" header="">
<template #body="{ data }">
<RadioButton
class="fam-checkbox"
:value="data"
v-model="formData.forestClients[0]"
readonly
/>
</template>
</Column>

<Column v-else header="">
<template #body="{ data }">
<Checkbox
class="fam-checkbox"
:binary="true"
:model-value="isForestClientSelected(data)"
@change="toggleForestClient(data)"
/>
</template>
</Column>

<Column header="Name" field="client_name" />

<Column header="Client number" field="forest_client_number" />
</DataTable>
</Field>
</div>
</template>

<style lang="scss">
.foresnt-client-select-table-container {
.error-text-container {
padding: 0;
height: fit-content;
margin-bottom: 0.5rem;
}
.subsection-title-container {
margin: 1.5rem 0;
}
.input-with-verify-button {
.add-organization-button {
width: 12rem;
}
}
.fam-table {
.p-datatable-emptymessage {
background-color: var(--layer-01);
}
}
.fam-checkbox {
display: flex;
flex-direction: row;
align-items: center;
.p-checkbox-box {
width: 1rem;
height: 1rem;
}
}
}
</style>
16 changes: 13 additions & 3 deletions frontend/src/components/AddPermissions/RoleSelectTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { ErrorMessage, Field } from "vee-validate";
import { computed, inject, ref, type Ref } from "vue";
import Label from "../UI/Label.vue";
import DelegatedAdminSection from "./DelegatedAdminSection.vue";
import ForestClientSection from "./ForestClientSection.vue";
import ForestClientAddTable from "./ForestClientAddTable.vue";
import ForestClientSelectTable from "./ForestClientSelectTable.vue";
const props = defineProps<{
appId: number;
Expand Down Expand Up @@ -145,9 +146,18 @@ const handleRoleSelect = (role: FamRoleGrantDto) => {
<Column field="roleDescription" header="Description">
<template #body="{ data }">
<span>{{ data.description }}</span>

<ForestClientSection
<ForestClientSelectTable
v-if="
isDelegatedAdminOnly &&
isAbstractRoleSelected(formData) &&
formData.role?.id === data.id
"
:app-id="props.appId"
:field-id="props.forestClientsFieldId"
/>

<ForestClientAddTable
v-else-if="
selectedRole?.id !== delegatedAdminRow.id &&
isAbstractRoleSelected(formData) &&
formData.role?.id === data.id
Expand Down
21 changes: 19 additions & 2 deletions frontend/src/components/ManagePermissionsTable/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -729,9 +729,9 @@ const handleFilter = (searchValue: string, isChanged: boolean) => {
</template>
</Column>

<Column header="Action">
<Column header="Action" class="action-col">
<template #body="{ data }">
<div class="nowrap-cell">
<div class="nowrap-cell action-button-group">
<button
v-if="!isAppAdminTable"
title="User permission history"
Expand Down Expand Up @@ -770,5 +770,22 @@ const handleFilter = (searchValue: string, isChanged: boolean) => {
align-items: center;
gap: 0.25rem;
}
tr > td.action-col {
padding: 0 1rem 0 1rem;
.action-button-group {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
width: 100%;
.btn-icon {
padding: 0.5rem;
display: flex;
flex-direction: column;
}
}
}
}
</style>
8 changes: 4 additions & 4 deletions frontend/src/components/UI/ErrorText.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import ErrorOutline from "@carbon/icons-vue/es/error--outline/16";
import WarnOutline from "@carbon/icons-vue/es/warning--filled/16";
defineProps<{
showIcon?: boolean;
Expand All @@ -8,7 +8,7 @@ defineProps<{
</script>
<template>
<div class="error-text-container">
<ErrorOutline v-if="showIcon" />
<WarnOutline v-if="showIcon" />
<p v-if="errorMsg">{{ errorMsg }}</p>
</div>
</template>
Expand All @@ -20,12 +20,12 @@ defineProps<{
p {
margin: 0;
color: var(--support-error);
color: var(--text-error);
}
svg {
margin-right: 0.5rem;
stroke: var(--support-error);
fill: var(--support-error);
}
}
</style>
44 changes: 43 additions & 1 deletion frontend/src/utils/AuthUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
AdminRoleAuthGroup,
type AdminUserAccessResponse,
type FamAuthGrantDto,
type FamForestClientBase,
} from "fam-admin-mgmt-api/model";

/**
Expand Down Expand Up @@ -55,3 +55,45 @@ export const isUserDelegatedAdminOnly = (

return !isAppAdmin && isDelegatedAdmin;
};

/**
* Retrieves the list of forest clients associated with a specific application
* for which the user is a delegated admin.
*
* @param {number} appId - The ID of the application to retrieve forest clients for.
* @param {AdminUserAccessResponse} [userAccess] - The response containing user access information.
* @returns {FamForestClientBase[]} An array of forest clients if the user is a delegated admin
* for the specified application; returns an empty array if the user does not have delegated admin access
* or if the userAccess data is invalid.
*
*/
export const getForestClientsUnderApp = (
appId: number,
userAccess?: AdminUserAccessResponse
): FamForestClientBase[] | null => {
if (
!userAccess ||
!isSelectedAppAuthorized(
AdminRoleAuthGroup.DelegatedAdmin,
appId,
userAccess
)
) {
return [];
}

const forestClients: FamForestClientBase[] =
userAccess.access
.find(
(grantDto) =>
grantDto.auth_key === AdminRoleAuthGroup.DelegatedAdmin
)
?.grants.find(
(grantDetailDto) => grantDetailDto.application.id === appId
)
?.roles?.flatMap(
(roleGrantDto) => roleGrantDto.forest_clients ?? []
) || [];

return forestClients;
};

0 comments on commit 2093608

Please sign in to comment.