Skip to content

Commit

Permalink
fix(rbac): watch users and permission-policies (#1102)
Browse files Browse the repository at this point in the history
  • Loading branch information
debsmita1 authored Jan 30, 2024
1 parent 61f225c commit cec734b
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 73 deletions.
10 changes: 5 additions & 5 deletions plugins/rbac/src/components/RoleOverview/MembersCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('MembersCard', () => {
loading: false,
data: useMembersMockData,
error: undefined,
retry: () => {},
retry: { roleRetry: jest.fn(), membersRetry: jest.fn() },
});
const { queryByText } = await renderInTestApp(
<MembersCard roleName="role:default/rbac_admin" />,
Expand All @@ -87,7 +87,7 @@ describe('MembersCard', () => {
loading: false,
data: [],
error: undefined,
retry: () => {},
retry: { roleRetry: jest.fn(), membersRetry: jest.fn() },
});
const { queryByText } = await renderInTestApp(
<MembersCard roleName="role:default/rbac_admin" />,
Expand All @@ -102,7 +102,7 @@ describe('MembersCard', () => {
loading: false,
data: [],
error: { message: 'xyz' },
retry: () => {},
retry: { roleRetry: jest.fn(), membersRetry: jest.fn() },
});
const { queryByText } = await renderInTestApp(
<MembersCard roleName="role:default/rbac_admin" />,
Expand All @@ -122,7 +122,7 @@ describe('MembersCard', () => {
loading: false,
data: useMembersMockData,
error: undefined,
retry: () => {},
retry: { roleRetry: jest.fn(), membersRetry: jest.fn() },
});
const { getByTestId } = await renderInTestApp(
<MembersCard roleName="role:default/rbac_admin" />,
Expand All @@ -136,7 +136,7 @@ describe('MembersCard', () => {
loading: false,
data: useMembersMockData,
error: undefined,
retry: () => {},
retry: { roleRetry: jest.fn(), membersRetry: jest.fn() },
});
const { queryByTestId } = await renderInTestApp(
<MembersCard roleName="role:default/rbac_admin" />,
Expand Down
5 changes: 4 additions & 1 deletion plugins/rbac/src/components/RoleOverview/MembersCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export const MembersCard = ({ roleName }: MembersCardProps) => {
icon: getRefreshIcon,
tooltip: 'Refresh',
isFreeAction: true,
onClick: () => retry(),
onClick: () => {
retry.roleRetry();
retry.membersRetry();
},
},
{
icon: () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('PermissionsCard', () => {
mockPermissionPolicies.mockReturnValue({
loading: false,
data: usePermissionPoliciesMockData,
retry: () => {},
retry: { policiesRetry: jest.fn(), permissionPoliciesRetry: jest.fn() },
error: new Error(''),
});
const { queryByText } = await renderInTestApp(
Expand All @@ -65,7 +65,7 @@ describe('PermissionsCard', () => {
mockPermissionPolicies.mockReturnValue({
loading: false,
data: [],
retry: () => {},
retry: { policiesRetry: jest.fn(), permissionPoliciesRetry: jest.fn() },
error: new Error(''),
});
const { queryByText } = await renderInTestApp(
Expand All @@ -79,7 +79,7 @@ describe('PermissionsCard', () => {
mockPermissionPolicies.mockReturnValue({
loading: false,
data: [],
retry: () => {},
retry: { policiesRetry: jest.fn(), permissionPoliciesRetry: jest.fn() },
error: { message: '404', name: 'Not Found' },
});
const { queryByText } = await renderInTestApp(
Expand All @@ -99,7 +99,7 @@ describe('PermissionsCard', () => {
loading: false,
data: [],
error: new Error(''),
retry: () => {},
retry: { policiesRetry: jest.fn(), permissionPoliciesRetry: jest.fn() },
});
const { getByTestId } = await renderInTestApp(
<PermissionsCard entityReference="role:default/rbac_admin" />,
Expand All @@ -113,7 +113,7 @@ describe('PermissionsCard', () => {
loading: false,
data: [],
error: new Error(''),
retry: () => {},
retry: { policiesRetry: jest.fn(), permissionPoliciesRetry: jest.fn() },
});
const { queryByTestId } = await renderInTestApp(
<PermissionsCard entityReference="role:default/rbac_admin" />,
Expand Down
5 changes: 4 additions & 1 deletion plugins/rbac/src/components/RoleOverview/PermissionsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ export const PermissionsCard = ({ entityReference }: PermissionsCardProps) => {
icon: getRefreshIcon,
tooltip: 'Refresh',
isFreeAction: true,
onClick: () => retry(),
onClick: () => {
retry.permissionPoliciesRetry();
retry.policiesRetry();
},
},
{
icon: () => getEditIcon(permissionResult.allowed, entityReference),
Expand Down
98 changes: 57 additions & 41 deletions plugins/rbac/src/hooks/useMembers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import { useAsync, useAsyncRetry } from 'react-use';
import { useAsyncRetry, useInterval } from 'react-use';

import { stringifyEntityRef } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';

import { rbacApiRef } from '../api/RBACBackendClient';
import { MembersData } from '../types';
import { MemberEntity, MembersData } from '../types';
import { getKindNamespaceName, getMembersFromGroup } from '../utils/rbac-utils';

const getErrorText = (
Expand All @@ -24,74 +24,90 @@ const getErrorText = (
return undefined;
};

export const useMembers = (roleName: string) => {
const getMemberData = (
memberResource: MemberEntity | undefined,
ref: string,
) => {
if (memberResource) {
return {
name:
memberResource.spec.profile?.displayName ??
memberResource.metadata.name,
type: memberResource.kind,
ref: {
namespace: memberResource.metadata.namespace as string,
kind: memberResource.kind.toLowerCase(),
name: memberResource.metadata.name,
},
members:
memberResource.kind === 'Group'
? getMembersFromGroup(memberResource)
: 0,
};
}
const { kind, namespace, name } = getKindNamespaceName(ref);
return {
name,
type: kind === 'user' ? 'User' : ('Group' as 'User' | 'Group'),
ref: {
namespace,
kind,
name,
},
members: 0,
};
};

export const useMembers = (roleName: string, pollInterval?: number) => {
const rbacApi = useApi(rbacApiRef);
let data: MembersData[] = [];
const {
loading: rolesLoading,
value: role,
retry,
retry: roleRetry,
error: roleError,
} = useAsyncRetry(async () => {
return await rbacApi.getRole(roleName);
});

const {
loading: membersLoading,
value: members,
retry: membersRetry,
error: membersError,
} = useAsync(async () => {
} = useAsyncRetry(async () => {
return await rbacApi.getMembers();
});

const loading = rolesLoading && membersLoading;
const loading = !roleError && !membersError && !role && !members;

data = React.useMemo(
() =>
Array.isArray(role)
? role[0].memberReferences.reduce((acc: MembersData[], ref: string) => {
const memberResource =
Array.isArray(members) &&
members.find(member => stringifyEntityRef(member) === ref);
if (memberResource) {
acc.push({
name:
memberResource.spec.profile?.displayName ??
memberResource.metadata.name,
type: memberResource.kind,
ref: {
namespace: memberResource.metadata.namespace as string,
kind: memberResource.kind.toLowerCase(),
name: memberResource.metadata.name,
},
members:
memberResource.kind === 'Group'
? getMembersFromGroup(memberResource)
: 0,
});
} else {
const { kind, namespace, name } = getKindNamespaceName(ref);
acc.push({
name,
type: kind === 'user' ? 'User' : 'Group',
ref: {
namespace,
kind,
name,
},
members: 0,
});
}
const memberResource: MemberEntity | undefined = Array.isArray(
members,
)
? members.find(member => stringifyEntityRef(member) === ref)
: undefined;
const memberData = getMemberData(memberResource, ref);
acc.push(memberData);
return acc;
}, [])
: [],
[role, members],
);

useInterval(
() => {
roleRetry();
membersRetry();
},
loading ? null : pollInterval || 10000,
);

return {
loading,
data,
retry,
retry: { roleRetry, membersRetry },
error: getErrorText(role, members) || roleError || membersError,
};
};
70 changes: 50 additions & 20 deletions plugins/rbac/src/hooks/usePermissionPolicies.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,80 @@
import React from 'react';
import { useAsync, useAsyncRetry } from 'react-use';
import { useAsyncRetry, useInterval } from 'react-use';

import { useApi } from '@backstage/core-plugin-api';

import { rbacApiRef } from '../api/RBACBackendClient';
import { getPermissionsData } from '../utils/rbac-utils';

export const usePermissionPolicies = (entityReference: string) => {
const getErrorText = (
policies: any,
permissionPolicies: any,
): { name: number; message: string } | undefined => {
if (!Array.isArray(policies) && (policies as Response)?.statusText) {
return {
name: (policies as Response).status,
message: `Error fetching policies. ${(policies as Response).statusText}`,
};
} else if (
!Array.isArray(permissionPolicies) &&
(permissionPolicies as Response)?.statusText
) {
return {
name: (permissionPolicies as Response).status,
message: `Error fetching the plugins. ${
(permissionPolicies as Response).statusText
}`,
};
}
return undefined;
};

export const usePermissionPolicies = (
entityReference: string,
pollInterval?: number,
) => {
const rbacApi = useApi(rbacApiRef);
const {
loading: policiesLoading,
value: policies,
retry,
error,
retry: policiesRetry,
error: policiesError,
} = useAsyncRetry(async () => {
return await rbacApi.getAssociatedPolicies(entityReference);
});

const {
value: permissionPolicies,
loading: permissionPoliciesLoading,
error: permissionPoliciesError,
} = useAsync(async () => {
retry: permissionPoliciesRetry,
} = useAsyncRetry(async () => {
return await rbacApi.listPermissions();
});

const loading = policiesLoading || permissionPoliciesLoading;
const loading =
!permissionPoliciesError &&
!policiesError &&
!policies &&
!permissionPolicies;

const data = React.useMemo(() => {
const pp = Array.isArray(permissionPolicies) ? permissionPolicies : [];
return Array.isArray(policies) ? getPermissionsData(policies, pp) : [];
}, [policies, permissionPolicies]);

useInterval(
() => {
policiesRetry();
permissionPoliciesRetry();
},
loading ? null : pollInterval || 10000,
);
return {
loading,
data,
retry,
error: (error as Error) ||
(permissionPoliciesError as Error) || {
name: (policies as Response)?.status,
message: `Error fetching policies. ${(policies as Response)
?.statusText}`,
} || {
name: (permissionPolicies as Response)?.status,
message: `Error fetching permission policies. ${(
permissionPolicies as Response
)?.statusText}`,
},
retry: { policiesRetry, permissionPoliciesRetry },
error:
policiesError ||
permissionPoliciesError ||
getErrorText(policies, permissionPolicies),
};
};

0 comments on commit cec734b

Please sign in to comment.