Skip to content

Commit

Permalink
Use project display names in DW, move related utils from ~/pages/proj…
Browse files Browse the repository at this point in the history
…ects to ~/concepts/projects and ~/concepts/k8s

Signed-off-by: Mike Turley <[email protected]>
  • Loading branch information
mturley committed Apr 22, 2024
1 parent 9014a05 commit 55a735f
Show file tree
Hide file tree
Showing 51 changed files with 238 additions and 92 deletions.
18 changes: 12 additions & 6 deletions frontend/src/__mocks__/mockProjectK8sResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@ import { genUID } from '~/__mocks__/mockUtils';
import { KnownLabels, ProjectKind } from '~/k8sTypes';

type MockResourceConfigType = {
hasAnnotations?: boolean;
username?: string;
displayName?: string;
description?: string;
k8sName?: string;
creationTimestamp?: string;
enableModelMesh?: boolean;
isDSProject?: boolean;
phase?: 'Active' | 'Terminating';
};

export const mockProjectK8sResource = ({
hasAnnotations = true,
username = 'test-user',
displayName = 'Test Project',
k8sName = 'test-project',
creationTimestamp = '2023-02-14T21:43:59Z',
enableModelMesh,
description = '',
isDSProject = true,
Expand All @@ -26,19 +30,21 @@ export const mockProjectK8sResource = ({
metadata: {
name: k8sName,
uid: genUID('project'),
creationTimestamp: '2023-02-14T21:43:59Z',
creationTimestamp,
labels: {
'kubernetes.io/metadata.name': k8sName,
...(enableModelMesh !== undefined && {
[KnownLabels.MODEL_SERVING_PROJECT]: enableModelMesh ? 'true' : 'false',
}),
...(isDSProject && { [KnownLabels.DASHBOARD_RESOURCE]: 'true' }),
},
annotations: {
'openshift.io/description': description,
'openshift.io/display-name': displayName,
'openshift.io/requester': username,
},
...(hasAnnotations && {
annotations: {
...(description && { 'openshift.io/description': description }),
...(displayName && { 'openshift.io/display-name': displayName }),
...(username && { 'openshift.io/requester': username }),
},
}),
resourceVersion: '1',
},
status: {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/__tests__/inferenceServices.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from '~/api/k8s/inferenceServices';
import { InferenceServiceModel, ProjectModel } from '~/api/models';
import { InferenceServiceKind, ProjectKind } from '~/k8sTypes';
import { translateDisplayNameForK8s } from '~/pages/projects/utils';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { AcceleratorProfileState } from '~/utilities/useAcceleratorProfileState';

jest.mock('@openshift/dynamic-plugin-sdk-utils', () => ({
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/inferenceServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { InferenceServiceModel } from '~/api/models';
import { InferenceServiceKind, K8sAPIOptions, KnownLabels } from '~/k8sTypes';
import { CreatingInferenceServiceObject } from '~/pages/modelServing/screens/types';
import { translateDisplayNameForK8s } from '~/pages/projects/utils';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';
import { AcceleratorProfileState } from '~/utilities/useAcceleratorProfileState';
import { getModelServingProjects } from './projects';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/notebooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { K8sAPIOptions, KnownLabels, NotebookKind } from '~/k8sTypes';
import { usernameTranslate } from '~/utilities/notebookControllerUtils';
import { EnvironmentFromVariable, StartNotebookData } from '~/pages/projects/types';
import { ROOT_MOUNT_PATH } from '~/pages/projects/pvc/const';
import { translateDisplayNameForK8s } from '~/pages/projects/utils';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { getTolerationPatch, TolerationChanges } from '~/utilities/tolerations';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';
import {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { K8sAPIOptions, ProjectKind } from '~/k8sTypes';
import { ProjectModel, ProjectRequestModel } from '~/api/models';
import { throwErrorFromAxios } from '~/api/errorUtils';
import { translateDisplayNameForK8s } from '~/pages/projects/utils';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { ODH_PRODUCT_NAME } from '~/utilities/const';
import { LABEL_SELECTOR_DASHBOARD_RESOURCE, LABEL_SELECTOR_MODEL_SERVING_PROJECT } from '~/const';
import { NamespaceApplicationCase } from '~/pages/projects/types';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/pvcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@openshift/dynamic-plugin-sdk-utils';
import { K8sAPIOptions, KnownLabels, PersistentVolumeClaimKind } from '~/k8sTypes';
import { PVCModel } from '~/api/models';
import { translateDisplayNameForK8s } from '~/pages/projects/utils';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { LABEL_SELECTOR_DASHBOARD_RESOURCE } from '~/const';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';
import { CreatingStorageObject } from '~/pages/projects/types';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { K8sAPIOptions, KnownLabels, SecretKind } from '~/k8sTypes';
import { SecretModel } from '~/api/models';
import { genRandomChars } from '~/utilities/string';
import { translateDisplayNameForK8s } from '~/pages/projects/utils';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';

export const DATA_CONNECTION_PREFIX = 'aws-connection';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/servingRuntimes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { CreatingServingRuntimeObject } from '~/pages/modelServing/screens/types';
import { ContainerResources } from '~/types';
import { getModelServingRuntimeName } from '~/pages/modelServing/utils';
import { getDisplayNameFromK8sResource, translateDisplayNameForK8s } from '~/pages/projects/utils';
import { getDisplayNameFromK8sResource, translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';
import { AcceleratorProfileState } from '~/utilities/useAcceleratorProfileState';
import { getModelServingProjects } from './projects';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as React from 'react';
import { Bullseye, Alert } from '@patternfly/react-core';
import { Bullseye, Alert, Spinner } from '@patternfly/react-core';
import { ClusterQueueKind, LocalQueueKind, WorkloadKind } from '~/k8sTypes';
import { FetchStateObject } from '~/types';
import { DEFAULT_LIST_FETCH_STATE, DEFAULT_VALUE_FETCH_STATE } from '~/utilities/const';
import { SupportedArea, conditionalArea } from '~/concepts/areas';
import useSyncPreferredProject from '~/concepts/projects/useSyncPreferredProject';
import { ProjectsContext, byName } from '~/concepts/projects/ProjectsContext';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { useMakeFetchObject } from '~/utilities/useMakeFetchObject';
import {
DEFAULT_DW_PROJECT_CURRENT_METRICS,
Expand All @@ -24,7 +25,8 @@ type DistributedWorkloadsContextType = {
workloads: FetchStateObject<WorkloadKind[]>;
projectCurrentMetrics: DWProjectCurrentMetrics;
refreshAllData: () => void;
namespace?: string;
namespace: string;
projectDisplayName: string;
};

type DistributedWorkloadsContextProviderProps = {
Expand All @@ -38,6 +40,8 @@ export const DistributedWorkloadsContext = React.createContext<DistributedWorklo
workloads: DEFAULT_LIST_FETCH_STATE,
projectCurrentMetrics: DEFAULT_DW_PROJECT_CURRENT_METRICS,
refreshAllData: () => undefined,
namespace: '',
projectDisplayName: '',
});

export const DistributedWorkloadsContextProvider =
Expand Down Expand Up @@ -100,6 +104,14 @@ export const DistributedWorkloadsContextProvider =
);
}

if (!project) {
return (
<Bullseye>
<Spinner />
</Bullseye>
);
}

return (
<DistributedWorkloadsContext.Provider
value={{
Expand All @@ -109,6 +121,7 @@ export const DistributedWorkloadsContextProvider =
projectCurrentMetrics,
refreshAllData,
namespace,
projectDisplayName: getProjectDisplayName(project),
}}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/concepts/k8s/NameDescriptionField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@patternfly/react-core';
import { ExclamationCircleIcon, HelpIcon } from '@patternfly/react-icons';
import { NameDescType } from '~/pages/projects/types';
import { isValidK8sName, translateDisplayNameForK8s } from '~/pages/projects/utils';
import { isValidK8sName, translateDisplayNameForK8s } from '~/concepts/k8s/utils';

type NameDescriptionFieldProps = {
nameFieldId: string;
Expand Down
63 changes: 63 additions & 0 deletions frontend/src/concepts/k8s/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { mockProjectK8sResource } from '~/__mocks__';
import {
getDescriptionFromK8sResource,
getDisplayNameFromK8sResource,
isValidK8sName,
translateDisplayNameForK8s,
} from '~/concepts/k8s/utils';

describe('getDisplayNameFromK8sResource', () => {
it('gets the display name when present', () => {
const mockProject = mockProjectK8sResource({
k8sName: 'my-project',
displayName: 'My Project',
});
expect(getDisplayNameFromK8sResource(mockProject)).toBe('My Project');
});

it('uses the resource name if no display name is present', () => {
const mockProject = mockProjectK8sResource({
k8sName: 'my-project',
displayName: '',
});
expect(getDisplayNameFromK8sResource(mockProject)).toBe('my-project');
});
});

describe('getDescriptionFromK8sResource', () => {
it('gets the description', () => {
const mockProject = mockProjectK8sResource({ description: 'This is a test project' });
expect(getDescriptionFromK8sResource(mockProject)).toBe('This is a test project');
});

it('returns empty string if no description', () => {
const mockProject = mockProjectK8sResource({ description: '' });
expect(getDescriptionFromK8sResource(mockProject)).toBe('');
});
});

describe('translateDisplayNameForK8s', () => {
it('translates a string into a valid k8s name', () => {
expect(translateDisplayNameForK8s('Test Project 1')).toBe('test-project-1');
expect(translateDisplayNameForK8s("John Doe's Cool Project!")).toBe('john-does-cool-project');
expect(translateDisplayNameForK8s('$ymbols & Capitals and Spaces! (These are invalid!)')).toBe(
'ymbols--capitals-and-spaces-these-are-invalid',
);
});
});

describe('isValidK8sName', () => {
it('identifies invalid names', () => {
expect(isValidK8sName('')).toBe(false);
expect(isValidK8sName('Test Project 1')).toBe(false);
expect(isValidK8sName("John Doe's Cool Project!")).toBe(false);
expect(isValidK8sName('$ymbols & Capitals and Spaces! (These are invalid!)')).toBe(false);
});

it('identifies valid names', () => {
expect(isValidK8sName(undefined)).toBe(true);
expect(isValidK8sName('test-project-1')).toBe(true);
expect(isValidK8sName('john-does-cool-project')).toBe(true);
expect(isValidK8sName('ymbols--capitals-and-spaces-these-are-invalid')).toBe(true);
});
});
16 changes: 16 additions & 0 deletions frontend/src/concepts/k8s/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { K8sDSGResource } from '~/k8sTypes';

export const getDisplayNameFromK8sResource = (resource: K8sDSGResource): string =>
resource.metadata.annotations?.['openshift.io/display-name'] || resource.metadata.name;
export const getDescriptionFromK8sResource = (resource: K8sDSGResource): string =>
resource.metadata.annotations?.['openshift.io/description'] || '';

export const translateDisplayNameForK8s = (name: string): string =>
name
.trim()
.toLowerCase()
.replace(/\s/g, '-')
.replace(/[^A-Za-z0-9-]/g, '');

export const isValidK8sName = (name?: string): boolean =>
name === undefined || (name.length > 0 && /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(name));
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import DeleteModal from '~/pages/projects/components/DeleteModal';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { deleteServer } from '~/concepts/pipelines/utils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import NameDescriptionField from '~/concepts/k8s/NameDescriptionField';
import { RunFormData, RunTypeOption } from '~/concepts/pipelines/content/createRun/types';
import { ValueOf } from '~/typeHelpers';
import { ParamsSection } from '~/concepts/pipelines/content/createRun/contentSections/ParamsSection';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { useLatestPipelineVersion } from '~/concepts/pipelines/apiHooks/useLatestPipelineVersion';
import RunTypeSectionScheduled from '~/concepts/pipelines/content/createRun/contentSections/RunTypeSectionScheduled';
import { PipelineVersionKFv2, RuntimeConfigParameters } from '~/concepts/pipelines/kfTypes';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
TextInput,
} from '@patternfly/react-core';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import useCreateExperimentData from '~/concepts/pipelines/content/experiment/useCreateExperimentData';
import { ExperimentKFv2 } from '~/concepts/pipelines/kfTypes';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@patternfly/react-core';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { usePipelineImportModalData } from '~/concepts/pipelines/content/import/useImportModalData';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { PipelineKFv2 } from '~/concepts/pipelines/kfTypes';
import PipelineUploadRadio from './PipelineUploadRadio';
import { PipelineUploadOption } from './utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@patternfly/react-core';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { usePipelineVersionImportModalData } from '~/concepts/pipelines/content/import/useImportModalData';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes';
import PipelineSelector from '~/concepts/pipelines/content/pipelineSelector/PipelineSelector';
import { PipelineUploadOption, generatePipelineVersionName } from './utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Link } from 'react-router-dom';
import { PipelineRunJobKFv2, PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes';
import { getRunDuration } from '~/concepts/pipelines/content/tables/utils';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { relativeDuration } from '~/utilities/time';
import {
asTimestamp,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/concepts/projects/ProjectSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Bullseye, Flex, FlexItem } from '@patternfly/react-core';
import { Dropdown, DropdownItem, DropdownToggle } from '@patternfly/react-core/deprecated';
import { getProjectDisplayName } from '~/pages/projects/utils';
import { getProjectDisplayName } from '~/concepts/projects/utils';
import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext';
import { ProjectObjectType, typedObjectImage } from '~/concepts/design/utils';

Expand Down
68 changes: 67 additions & 1 deletion frontend/src/concepts/projects/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { isAvailableProject } from '~/concepts/projects/utils';
import { mockProjectK8sResource } from '~/__mocks__';
import {
isAvailableProject,
getProjectDisplayName,
getProjectDescription,
getProjectOwner,
getProjectCreationTime,
} from '~/concepts/projects/utils';

const mockDashboardNamespace = 'mock-opendatahub';

Expand Down Expand Up @@ -31,3 +38,62 @@ describe('isAvailableProject', () => {
expect(isAvailableProject('odh-not-dashboard', mockDashboardNamespace)).toBe(true);
});
});

describe('getProjectDisplayName', () => {
it('gets the display name when present', () => {
const mockProject = mockProjectK8sResource({
k8sName: 'my-project',
displayName: 'My Project',
});
expect(getProjectDisplayName(mockProject)).toBe('My Project');
});

it('uses the resource name if no display name is present', () => {
const mockProject = mockProjectK8sResource({
k8sName: 'my-project',
displayName: '',
});
expect(getProjectDisplayName(mockProject)).toBe('my-project');
});
});

describe('getProjectDescription', () => {
it('gets the description', () => {
const mockProject = mockProjectK8sResource({ description: 'This is a test project' });
expect(getProjectDescription(mockProject)).toBe('This is a test project');
});

it('returns empty string if no description', () => {
const mockProject = mockProjectK8sResource({ description: '' });
expect(getProjectDescription(mockProject)).toBe('');
});
});

describe('getProjectOwner', () => {
it('gets the requester if present', () => {
const mockProject = mockProjectK8sResource({ username: 'john-doe' });
expect(getProjectOwner(mockProject)).toBe('john-doe');
});

it('returns empty string if no annotations', () => {
const mockProject = mockProjectK8sResource({ hasAnnotations: false });
expect(getProjectOwner(mockProject)).toBe('');
});

it('returns empty string if no requester', () => {
const mockProject = mockProjectK8sResource({ username: '' });
expect(getProjectOwner(mockProject)).toBe('');
});
});

describe('getProjectCreationTime', () => {
it('returns creation timestamp as unix time integer if present', () => {
const mockProject = mockProjectK8sResource({ creationTimestamp: '2024-04-19T16:36:37.104Z' });
expect(getProjectCreationTime(mockProject)).toBe(1713544597104);
});

it('returns 0 if no timestamp present', () => {
const mockProject = mockProjectK8sResource({ creationTimestamp: '' });
expect(getProjectCreationTime(mockProject)).toBe(0);
});
});
Loading

0 comments on commit 55a735f

Please sign in to comment.