Skip to content

Commit

Permalink
added useGroup websocket
Browse files Browse the repository at this point in the history
  • Loading branch information
pnaik1 committed May 28, 2024
1 parent f957299 commit 7d58b63
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 219 deletions.
13 changes: 13 additions & 0 deletions frontend/src/__mocks__/mockGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { GroupKind } from '~/k8sTypes';

type MockGroupType = {
name?: string;
};
export const mockGroup = ({ name = 'odh-admins' }: MockGroupType): GroupKind => ({
metadata: {
name,
},
users: [],
apiVersion: 'user.openshift.io/v1',
kind: 'Group',
});
98 changes: 56 additions & 42 deletions frontend/src/api/k8s/__tests__/groups.spec.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,64 @@
import { k8sListResource } from '@openshift/dynamic-plugin-sdk-utils';
import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import { listGroups } from '~/api';
import { GroupKind } from '~/k8sTypes';
import { useK8sWatchResource } from '@openshift/dynamic-plugin-sdk-utils';
import { groupVersionKind, useAccessReview, useGroups } from '~/api';
import { testHook } from '~/__tests__/unit/testUtils/hooks';
import { GroupModel } from '~/api/models';
import { mockGroup } from '~/__mocks__/mockGroup';

jest.mock('@openshift/dynamic-plugin-sdk-utils', () => ({
k8sListResource: jest.fn(),
useK8sWatchResource: jest.fn(),
}));
const k8sListResourceMock = jest.mocked(k8sListResource<GroupKind>);
const groupMock: GroupKind = {
apiVersion: 'v1',
kind: 'Groupkind',
metadata: {
name: 'name',
namespace: 'namespace',
},
users: [],
};
describe('listGroups', () => {
it('should list groups when label selector is not present', async () => {
k8sListResourceMock.mockResolvedValue(mockK8sResourceList([groupMock]));
const result = await listGroups();
expect(k8sListResourceMock).toHaveBeenCalledTimes(1);
expect(k8sListResourceMock).toHaveBeenCalledWith({
model: GroupModel,
queryOptions: {},
});
expect(result).toStrictEqual([groupMock]);

jest.mock('~/api/useAccessReview', () => ({
useAccessReview: jest.fn(),
}));

const useAccessReviewMock = jest.mocked(useAccessReview);
const useK8sWatchResourceMock = useK8sWatchResource as jest.Mock;

describe('useGroups', () => {
it('should wrap useK8sWatchResource to watch groups', async () => {
const mockReturnValue: ReturnType<typeof useK8sWatchResourceMock> = [[], false, undefined];
useAccessReviewMock.mockReturnValue([true, true]);
useK8sWatchResourceMock.mockReturnValue(mockReturnValue);
const { result } = testHook(useGroups)();

expect(useK8sWatchResourceMock).toHaveBeenCalledTimes(1);
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(
{
isList: true,
groupVersionKind: groupVersionKind(GroupModel),
},
GroupModel,
);
expect(result.current).toStrictEqual(mockReturnValue);
});
it('should list groups when label selector is present', async () => {
k8sListResourceMock.mockResolvedValue(mockK8sResourceList([groupMock]));
const result = await listGroups('labelSelector');
expect(k8sListResourceMock).toHaveBeenCalledTimes(1);
expect(k8sListResourceMock).toHaveBeenCalledWith({
model: GroupModel,
queryOptions: { queryParams: { labelSelector: 'labelSelector' } },
});
expect(result).toStrictEqual([groupMock]);

it('should render list of groups', () => {
const mockReturnValue: ReturnType<typeof useK8sWatchResourceMock> = [
[mockGroup({})],
true,
undefined,
];
useAccessReviewMock.mockReturnValue([true, true]);
useK8sWatchResourceMock.mockReturnValue(mockReturnValue);
const { result } = testHook(useGroups)();
expect(useK8sWatchResourceMock).toHaveBeenCalledTimes(1);
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(
{
isList: true,
groupVersionKind: groupVersionKind(GroupModel),
},
GroupModel,
);
expect(result.current).toStrictEqual(mockReturnValue);
});
it('should handle errors and rethrow', async () => {
k8sListResourceMock.mockRejectedValue(new Error('error'));
await expect(listGroups()).rejects.toThrow('error');
expect(k8sListResourceMock).toHaveBeenCalledTimes(1);
expect(k8sListResourceMock).toHaveBeenCalledWith({
model: GroupModel,
queryOptions: {},
});

it('should handle 403 error', () => {
useAccessReviewMock.mockReturnValue([false, true]);
useK8sWatchResourceMock.mockReturnValue([undefined, true, undefined]);
const { result } = testHook(useGroups)();
expect(useK8sWatchResourceMock).toHaveBeenCalledTimes(1);
expect(useK8sWatchResourceMock).toHaveBeenCalledWith(null, GroupModel);
expect(result.current).toStrictEqual([[], true, undefined]);
});
});
38 changes: 28 additions & 10 deletions frontend/src/api/k8s/groups.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { k8sListResource } from '@openshift/dynamic-plugin-sdk-utils';
import { GroupKind } from '~/k8sTypes';
import { WatchK8sResult, useK8sWatchResource } from '@openshift/dynamic-plugin-sdk-utils';
import React from 'react';
import { AccessReviewResourceAttributes, GroupKind } from '~/k8sTypes';
import { GroupModel } from '~/api/models';
import { groupVersionKind } from '~/api/k8sUtils';
import { useAccessReview } from '~/api/useAccessReview';

export const listGroups = (labelSelector?: string): Promise<GroupKind[]> => {
const queryOptions = {
...(labelSelector && { queryParams: { labelSelector } }),
};
return k8sListResource<GroupKind>({
model: GroupModel,
queryOptions,
}).then((listResource) => listResource.items);
const accessReviewResource: AccessReviewResourceAttributes = {
group: 'user.openshift.io',
resource: 'groups',
verb: 'list',
};

export const useGroups = (): WatchK8sResult<GroupKind[]> => {
const [allowList, accessReviewLoaded] = useAccessReview(accessReviewResource);
const [groupData, loaded, error] = useK8sWatchResource<GroupKind[]>(
allowList && accessReviewLoaded
? {
isList: true,
groupVersionKind: groupVersionKind(GroupModel),
}
: null,
GroupModel,
);
return React.useMemo(() => {
if (!allowList) {
return [[], true, undefined];
}
return [groupData, loaded, error];
}, [error, groupData, loaded, allowList]);
};
14 changes: 6 additions & 8 deletions frontend/src/pages/projects/ProjectDetailsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Navigate, Outlet, useParams } from 'react-router-dom';
import { WatchK8sResult } from '@openshift/dynamic-plugin-sdk-utils';
import {
GroupKind,
InferenceServiceKind,
Expand All @@ -10,7 +11,7 @@ import {
ServingRuntimeKind,
TemplateKind,
} from '~/k8sTypes';
import { DEFAULT_CONTEXT_DATA } from '~/utilities/const';
import { DEFAULT_CONTEXT_DATA, DEFAULT_LIST_WATCH_RESULT } from '~/utilities/const';
import useServingRuntimes from '~/pages/modelServing/useServingRuntimes';
import useInferenceServices from '~/pages/modelServing/useInferenceServices';
import { ContextResourceData } from '~/types';
Expand All @@ -26,13 +27,13 @@ import useTemplateDisablement from '~/pages/modelServing/customServingRuntimes/u
import { useDashboardNamespace } from '~/redux/selectors';
import { getTokenNames } from '~/pages/modelServing/utils';
import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas';
import { useGroups } from '~/api';
import { NotebookState } from './notebook/types';
import { DataConnection } from './types';
import useDataConnections from './screens/detail/data-connections/useDataConnections';
import useProjectNotebookStates from './notebook/useProjectNotebookStates';
import useProjectPvcs from './screens/detail/storage/useProjectPvcs';
import useProjectSharing from './projectSharing/useProjectSharing';
import useGroups from './projectSharing/useGroups';

type ProjectDetailsContextType = {
currentProject: ProjectKind;
Expand All @@ -48,7 +49,7 @@ type ProjectDetailsContextType = {
inferenceServices: ContextResourceData<InferenceServiceKind>;
serverSecrets: ContextResourceData<SecretKind>;
projectSharingRB: ContextResourceData<RoleBindingKind>;
groups: ContextResourceData<GroupKind>;
groups: WatchK8sResult<GroupKind[]>;
};

export const ProjectDetailsContext = React.createContext<ProjectDetailsContextType>({
Expand All @@ -66,7 +67,7 @@ export const ProjectDetailsContext = React.createContext<ProjectDetailsContextTy
inferenceServices: DEFAULT_CONTEXT_DATA,
serverSecrets: DEFAULT_CONTEXT_DATA,
projectSharingRB: DEFAULT_CONTEXT_DATA,
groups: DEFAULT_CONTEXT_DATA,
groups: DEFAULT_LIST_WATCH_RESULT,
});

const ProjectDetailsContextProvider: React.FC = () => {
Expand All @@ -93,7 +94,7 @@ const ProjectDetailsContextProvider: React.FC = () => {
);
const serverSecrets = useContextResourceData<SecretKind>(useServingRuntimeSecrets(namespace));
const projectSharingRB = useContextResourceData<RoleBindingKind>(useProjectSharing(namespace));
const groups = useContextResourceData<GroupKind>(useGroups());
const groups = useGroups();

const notebookRefresh = notebooks.refresh;
const pvcRefresh = pvcs.refresh;
Expand All @@ -104,7 +105,6 @@ const ProjectDetailsContextProvider: React.FC = () => {
const servingRuntimeTemplateDisablementRefresh = servingRuntimeTemplateDisablement.refresh;
const inferenceServiceRefresh = inferenceServices.refresh;
const projectSharingRefresh = projectSharingRB.refresh;
const groupsRefresh = groups.refresh;
const refreshAllProjectData = React.useCallback(() => {
notebookRefresh();
setTimeout(notebookRefresh, 2000);
Expand All @@ -113,7 +113,6 @@ const ProjectDetailsContextProvider: React.FC = () => {
servingRuntimeRefresh();
inferenceServiceRefresh();
projectSharingRefresh();
groupsRefresh();
servingRuntimeTemplateRefresh();
servingRuntimeTemplateOrderRefresh();
servingRuntimeTemplateDisablementRefresh();
Expand All @@ -127,7 +126,6 @@ const ProjectDetailsContextProvider: React.FC = () => {
servingRuntimeTemplateDisablementRefresh,
inferenceServiceRefresh,
projectSharingRefresh,
groupsRefresh,
]);

const filterTokens = React.useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { filterRoleBindingSubjects } from './utils';
const ProjectSharing: React.FC = () => {
const {
projectSharingRB: { data: roleBindings, loaded, error: loadError, refresh: refreshRB },
groups: { data: groups },
groups: [groups],
} = React.useContext(ProjectDetailsContext);

if (loadError) {
Expand Down
129 changes: 0 additions & 129 deletions frontend/src/pages/projects/projectSharing/__tests__/useGroups.spec.ts

This file was deleted.

Loading

0 comments on commit 7d58b63

Please sign in to comment.