Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thomas/refactor permissions #501

Merged
merged 5 commits into from
Jul 23, 2024
Merged

Thomas/refactor permissions #501

merged 5 commits into from
Jul 23, 2024

Conversation

balzdur
Copy link
Collaborator

@balzdur balzdur commented Jul 22, 2024

Refactor of the settings :

  • use fine grain permission (based on the previous implemented strategy)
  • dynamic generation of "accessible" settings
    • used for the nav generation (remove "empty" sections like discussed last friday)
    • used for the settings nav item in the global nav
  • change settings order based on last friday discussions

diff --git a/packages/app-builder/src/routes/_builder+/settings+/inboxes.$inboxId.tsx b/packages/app-builder/src/routes/_builder+/settings+/inboxes.$inboxId.tsx
index 7e923684..1cb216e8 100644
--- a/packages/app-builder/src/routes/_builder+/settings+/inboxes.$inboxId.tsx
+++ b/packages/app-builder/src/routes/_builder+/settings+/inboxes.$inboxId.tsx
@@ -29,29 +29,60 @@ export const handle = {

 export async function loader({ request, params }: LoaderFunctionArgs) {
   const { authService, featureAccessService } = serverServices;
-  const { apiClient, cases } = await authService.isAuthenticated(request, {
-    failureRedirect: getRoute('/sign-in'),
-  });
+  const { user, apiClient, cases } = await authService.isAuthenticated(
+    request,
+    {
+      failureRedirect: getRoute('/sign-in'),
+    },
+  );

   const inboxId = fromParams(params, 'inboxId');

-  const [{ inbox }, caseList, inboxUserRoles] = await Promise.all([
+  const [
+    { inbox },
+    caseList,
+    inboxUserRoles,
+    isEditInboxAvailable,
+    isDeleteInboxAvailable,
+    isCreateInboxUserAvailable,
+    isEditInboxUserAvailable,
+    isDeleteInboxUserAvailable,
+  ] = await Promise.all([
     apiClient.getInbox(inboxId),
     cases.listCases({ inboxIds: [inboxId] }),
     featureAccessService.getInboxUserRoles(),
+    featureAccessService.isEditInboxAvailable(user),
+    featureAccessService.isDeleteInboxAvailable(user),
+    featureAccessService.isCreateInboxUserAvailable(user),
+    featureAccessService.isEditInboxUserAvailable(user),
+    featureAccessService.isDeleteInboxUserAvailable(user),
   ]);

   return json({
     inbox,
     caseList,
     inboxUserRoles,
+    isEditInboxAvailable,
+    isDeleteInboxAvailable,
+    isCreateInboxUserAvailable,
+    isEditInboxUserAvailable,
+    isDeleteInboxUserAvailable,
   });
 }

 const columnHelper = createColumnHelper<InboxUserDto>();

 export default function Inbox() {
-  const { caseList, inbox, inboxUserRoles } = useLoaderData<typeof loader>();
+  const {
+    caseList,
+    inbox,
+    inboxUserRoles,
+    isEditInboxAvailable,
+    isDeleteInboxAvailable,
+    isCreateInboxUserAvailable,
+    isEditInboxUserAvailable,
+    isDeleteInboxUserAvailable,
+  } = useLoaderData<typeof loader>();
   const { t } = useTranslation(handle.i18n);
   const { orgUsers } = useOrganizationUsers();

@@ -73,23 +104,37 @@ export default function Inbox() {
         size: 200,
         cell: ({ getValue }) => t(tKeyForInboxUserRole(getValue())),
       }),
-      columnHelper.display({
-        id: 'actions',
-        size: 100,
-        cell: ({ cell }) => {
-          return (
-            <div className="text-grey-00 group-hover:text-grey-100 flex gap-2">
-              <UpdateInboxUser
-                inboxUser={cell.row.original}
-                inboxUserRoles={inboxUserRoles}
-              />
-              <DeleteInboxUser inboxUser={cell.row.original} />
-            </div>
-          );
-        },
-      }),
+      ...(isEditInboxUserAvailable || isDeleteInboxUserAvailable
+        ? [
+            columnHelper.display({
+              id: 'actions',
+              size: 100,
+              cell: ({ cell }) => {
+                return (
+                  <div className="text-grey-00 group-hover:text-grey-100 flex gap-2">
+                    {isEditInboxUserAvailable ? (
+                      <UpdateInboxUser
+                        inboxUser={cell.row.original}
+                        inboxUserRoles={inboxUserRoles}
+                      />
+                    ) : null}
+                    {isDeleteInboxUserAvailable ? (
+                      <DeleteInboxUser inboxUser={cell.row.original} />
+                    ) : null}
+                  </div>
+                );
+              },
+            }),
+          ]
+        : []),
     ];
-  }, [inboxUserRoles, orgUsers, t]);
+  }, [
+    inboxUserRoles,
+    isDeleteInboxUserAvailable,
+    isEditInboxUserAvailable,
+    orgUsers,
+    t,
+  ]);

   const { table, getBodyProps, rows, getContainerProps } = useTable({
     data: inbox.users ?? [],
@@ -111,10 +156,12 @@ export default function Inbox() {
             <span className="flex-1">
               {t('settings:inboxes.inbox_details.title')}
             </span>
-            <UpdateInbox
-              inbox={inbox}
-              redirectRoutePath="/settings/inboxes/:inboxId"
-            />
+            {isEditInboxAvailable ? (
+              <UpdateInbox
+                inbox={inbox}
+                redirectRoutePath="/settings/inboxes/:inboxId"
+              />
+            ) : null}
           </CollapsiblePaper.Title>
           <CollapsiblePaper.Content>
             <div className="grid auto-rows-fr grid-cols-[max-content_1fr] items-center gap-x-10 gap-y-4">
@@ -136,11 +183,13 @@ export default function Inbox() {
             <span className="flex-1">
               {t('settings:inboxes.inbox_details.members')}
             </span>
-            <CreateInboxUser
-              inboxId={inbox.id}
-              users={nonInboxUsers}
-              inboxUserRoles={inboxUserRoles}
-            />
+            {isCreateInboxUserAvailable ? (
+              <CreateInboxUser
+                inboxId={inbox.id}
+                users={nonInboxUsers}
+                inboxUserRoles={inboxUserRoles}
+              />
+            ) : null}
           </CollapsiblePaper.Title>
           <CollapsiblePaper.Content>
             <Table.Container {...getContainerProps()} className="max-h-96">
@@ -161,21 +210,23 @@ export default function Inbox() {
           </CollapsiblePaper.Content>
         </CollapsiblePaper.Container>

-        {caseList.totalCount.value === 0 ? (
-          <DeleteInbox inbox={inbox} />
-        ) : (
-          <Tooltip.Default
-            content={
-              <p className="p-2">
-                {t('settings:inboxes.inbox_details.delete_inbox.tooltip')}
-              </p>
-            }
-          >
-            <span className="w-fit">
-              <DeleteInbox inbox={inbox} disabled />
-            </span>
-          </Tooltip.Default>
-        )}
+        {isDeleteInboxAvailable ? (
+          caseList.totalCount.value === 0 ? (
+            <DeleteInbox inbox={inbox} />
+          ) : (
+            <Tooltip.Default
+              content={
+                <p className="p-2">
+                  {t('settings:inboxes.inbox_details.delete_inbox.tooltip')}
+                </p>
+              }
+            >
+              <span className="w-fit">
+                <DeleteInbox inbox={inbox} disabled />
+              </span>
+            </Tooltip.Default>
+          )
+        ) : null}
       </Page.Content>
     </Page.Container>
   );
diff --git a/packages/app-builder/src/routes/_builder+/settings+/inboxes._index.tsx b/packages/app-builder/src/routes/_builder+/settings+/inboxes._index.tsx
index d8739e85..7110dd98 100644
--- a/packages/app-builder/src/routes/_builder+/settings+/inboxes._index.tsx
+++ b/packages/app-builder/src/routes/_builder+/settings+/inboxes._index.tsx
@@ -1,5 +1,4 @@
 import { CollapsiblePaper, Page } from '@app-builder/components';
-import { isAdmin } from '@app-builder/models';
 import {
   type InboxWithCasesCount,
   tKeyForInboxUserRole,
@@ -18,24 +17,27 @@ import * as R from 'remeda';
 import { Table, useTable } from 'ui-design-system';

 export async function loader({ request }: LoaderFunctionArgs) {
-  const { authService } = serverServices;
+  const { authService, featureAccessService } = serverServices;
   const { inbox, user } = await authService.isAuthenticated(request, {
     failureRedirect: getRoute('/sign-in'),
   });
-  if (!isAdmin(user)) {
+  if (!featureAccessService.isReadAllInboxesAvailable(user)) {
     return redirect(getRoute('/'));
   }

-  const inboxes = await inbox.listInboxesWithCaseCount();
+  const [inboxes, isCreateInboxAvailable] = await Promise.all([
+    inbox.listInboxesWithCaseCount(),
+    featureAccessService.isCreateInboxAvailable(user),
+  ]);

-  return json({ inboxes });
+  return json({ inboxes, isCreateInboxAvailable });
 }

 const columnHelper = createColumnHelper<InboxWithCasesCount>();

 export default function Inboxes() {
   const { t } = useTranslation(['settings']);
-  const { inboxes } = useLoaderData<typeof loader>();
+  const { inboxes, isCreateInboxAvailable } = useLoaderData<typeof loader>();

   const navigate = useNavigate();

@@ -46,15 +48,16 @@ export default function Inboxes() {
         header: t('settings:inboxes.name'),
         size: 100,
       }),
-      columnHelper.display({
+      columnHelper.accessor((row) => row.users, {
         id: 'users',
         header: t('settings:inboxes.users'),
         size: 200,
-        cell: ({ cell }) => {
-          if (!cell.row.original.users) return null;
+        cell: ({ getValue }) => {
+          const users = getValue();
+          if (!users) return null;

           return R.pipe(
-            cell.row.original.users,
+            users,
             R.groupBy((u) => u.role),
             R.entries(),
             R.map(([role, users]) => {
@@ -86,7 +89,9 @@ export default function Inboxes() {
         <CollapsiblePaper.Container>
           <CollapsiblePaper.Title>
             <span className="flex-1">{t('settings:inboxes')}</span>
-            <CreateInbox redirectRoutePath="/settings/inboxes/:inboxId" />
+            {isCreateInboxAvailable ? (
+              <CreateInbox redirectRoutePath="/settings/inboxes/:inboxId" />
+            ) : null}
           </CollapsiblePaper.Title>
           <CollapsiblePaper.Content>
             <Table.Container {...getContainerProps()} className="max-h-96">
diff --git a/packages/app-builder/src/services/feature-access.server.ts b/packages/app-builder/src/services/feature-access.server.ts
index 8326713c..e02200ae 100644
--- a/packages/app-builder/src/services/feature-access.server.ts
+++ b/packages/app-builder/src/services/feature-access.server.ts
@@ -135,13 +135,6 @@ export function makeFeatureAccessService({
     }) => {
       return permissions.canEditDataModel;
     },
-    isCreateInboxAvailable: ({
-      permissions,
-    }: {
-      permissions: UserPermissions;
-    }) => {
-      return permissions.canEditInboxes;
-    },
     getUserRoles: async () => {
       const licenseEntitlements = await getLicenseEntitlements();
       if (licenseEntitlements.userRoles) {
@@ -250,5 +243,52 @@ export function makeFeatureAccessService({
       // Not necessary in the backend implementation but added to only let creators delete api keys
       return permissions.canCreateApiKey;
     },
+    isReadAllInboxesAvailable: ({ role }: { role: string }) => {
+      return role === 'ADMIN' || role === 'MARBLE_ADMIN';
+    },
+    isCreateInboxAvailable: ({
+      permissions,
+    }: {
+      permissions: UserPermissions;
+    }) => {
+      return permissions.canEditInboxes;
+    },
+    isEditInboxAvailable: ({
+      permissions,
+    }: {
+      permissions: UserPermissions;
+    }) => {
+      return permissions.canEditInboxes;
+    },
+    isDeleteInboxAvailable: ({
+      permissions,
+    }: {
+      permissions: UserPermissions;
+    }) => {
+      return permissions.canEditInboxes;
+    },
+    isCreateInboxUserAvailable: ({
+      permissions,
+    }: {
+      permissions: UserPermissions;
+    }) => {
+      return permissions.canEditInboxes;
+    },
+    isEditInboxUserAvailable: ({
+      permissions,
+    }: {
+      permissions: UserPermissions;
+    }) => {
+      return permissions.canEditInboxes;
+    },
+    isDeleteInboxUserAvailable: ({
+      permissions,
+    }: {
+      permissions: UserPermissions;
+    }) => {
+      return permissions.canEditInboxes;
+    },
   };
 }
+
+export type FeatureAccessService = ReturnType<typeof makeFeatureAccessService>;
@balzdur balzdur merged commit 2dc1fc5 into main Jul 23, 2024
3 checks passed
@balzdur balzdur deleted the thomas/refactor-permissions branch July 23, 2024 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants