Skip to content

Commit

Permalink
[RHOAIENG-10312] Connection type table view
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 committed Aug 7, 2024
1 parent 457d01d commit 58cbe32
Show file tree
Hide file tree
Showing 19 changed files with 737 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ export const patchConnectionType = async (
const { dashboardNamespace } = getNamespaces(fastify);

if (
(partialConfigMap.metadata.labels?.[KnownLabels.DASHBOARD_RESOURCE] &&
(partialConfigMap.metadata?.labels?.[KnownLabels.DASHBOARD_RESOURCE] &&
partialConfigMap.metadata.labels[KnownLabels.DASHBOARD_RESOURCE] !== 'true') ||
(partialConfigMap.metadata.labels?.[KnownLabels.CONNECTION_TYPE] &&
(partialConfigMap.metadata?.labels?.[KnownLabels.CONNECTION_TYPE] &&
partialConfigMap.metadata.labels[KnownLabels.CONNECTION_TYPE] !== 'true')
) {
const error = 'Unable to update connection type, incorrect labels.';
Expand Down
114 changes: 114 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/connectionTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { appChrome } from '~/__tests__/cypress/cypress/pages/appChrome';
import { TableRow } from './components/table';
import { TableToolbar } from './components/TableToolbar';

class ConnectionTypesTableToolbar extends TableToolbar {}
class ConnectionTypeRow extends TableRow {
findConnectionTypeName() {
return this.find().findByTestId('connection-type-name');
}

shouldHaveName(name: string) {
return this.findConnectionTypeName().should('have.text', name);
}

findConnectionTypeDescription() {
return this.find().findByTestId('connection-type-description');
}

findConnectionTypeCreator() {
return this.find().findByTestId('connection-type-creator');
}

shouldHaveDescription(description: string) {
return this.findConnectionTypeDescription().should('have.text', description);
}

shouldHaveCreator(creator: string) {
return this.findConnectionTypeCreator().should('have.text', creator);
}

shouldShowPreInstalledLabel() {
return this.find().findByTestId('connection-type-user-label').should('exist');
}

findEnabled() {
return this.find().pfSwitchValue('connection-type-enable-switch');
}

findEnableSwitch() {
return this.find().pfSwitch('connection-type-enable-switch');
}

shouldBeEnabled() {
this.findEnabled().should('be.checked');
}

shouldBeDisabled() {
this.findEnabled().should('not.be.checked');
}

findEnableStatus() {
return this.find().findByTestId('connection-type-enable-status');
}
}

class ConnectionTypesPage {
visit() {
cy.visitWithLogin('/connectionTypes');
this.wait();
}

private wait() {
cy.findByTestId('app-page-title');
cy.testA11y();
}

findNavItem() {
return appChrome.findNavItem('Connection types');
}

navigate() {
this.findNavItem().click();
this.wait();
}

shouldHaveConnectionTypes() {
this.findTable().should('exist');
return this;
}

shouldReturnNotFound() {
cy.findByTestId('not-found-page').should('exist');
return this;
}

shouldBeEmpty() {
cy.findByTestId('connection-types-empty-state').should('exist');
return this;
}

findTable() {
return cy.findByTestId('connection-types-table');
}

getConnectionTypeRow(name: string) {
return new ConnectionTypeRow(() =>
this.findTable().findAllByTestId(`connection-type-name`).contains(name).parents('tr'),
);
}

findEmptyFilterResults() {
return cy.findByTestId('no-result-found-title');
}

findSortButton(name: string) {
return this.findTable().find('thead').findByRole('button', { name });
}

getTableToolbar() {
return new ConnectionTypesTableToolbar(() => cy.findByTestId('connection-types-table-toolbar'));
}
}

export const connectionTypesPage = new ConnectionTypesPage();
12 changes: 12 additions & 0 deletions frontend/src/__tests__/cypress/cypress/support/commands/odh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
RegisteredModel,
RegisteredModelList,
} from '~/concepts/modelRegistry/types';
import type { ConnectionTypeConfigMap } from '~/concepts/connectionTypes/types';
import type {
DashboardConfigKind,
DataScienceClusterInitializationKindStatus,
Expand Down Expand Up @@ -581,6 +582,17 @@ declare global {
path: { namespace: string };
},
response: OdhResponse<number>,
) => Cypress.Chainable<null>) &
((
type: 'GET /api/connection-types',
response: ConnectionTypeConfigMap[],
) => Cypress.Chainable<null>) &
((
type: 'PATCH /api/connection-types/:name',
options: {
path: { name: string };
},
response: { success: boolean; error: string },
) => Cypress.Chainable<null>);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { pageNotfound } from '~/__tests__/cypress/cypress/pages/pageNotFound';
import {
asProductAdminUser,
asProjectAdminUser,
} from '~/__tests__/cypress/cypress/utils/mockUsers';
import { connectionTypesPage } from '~/__tests__/cypress/cypress/pages/connectionTypes';
import { mockDashboardConfig } from '~/__mocks__';
import { mockConnectionTypeConfigMap } from '~/__mocks__/mockConnectionType';

it('Connection types should not be available for non product admins', () => {
asProjectAdminUser();
cy.visitWithLogin('/connectionTypes');
pageNotfound.findPage().should('exist');
connectionTypesPage.findNavItem().should('not.exist');
});

it('Connection types should be hidden by feature flag', () => {
asProductAdminUser();

cy.visitWithLogin('/connectionTypes');
connectionTypesPage.shouldReturnNotFound();

cy.interceptOdh(
'GET /api/config',
mockDashboardConfig({
disableConnectionTypes: false,
}),
);

connectionTypesPage.visit();
});

describe('Connection types', () => {
beforeEach(() => {
asProductAdminUser();

cy.interceptOdh(
'GET /api/config',
mockDashboardConfig({
disableConnectionTypes: false,
}),
);
cy.interceptOdh('GET /api/connection-types', [
mockConnectionTypeConfigMap({}),
mockConnectionTypeConfigMap({
name: 'no-display-name',
displayName: '',
description: 'description 2',
username: 'Pre-installed',
enabled: false,
}),
mockConnectionTypeConfigMap({
name: 'test-2',
displayName: 'Test display name',
description: 'Test description',
}),
]);
});

it('should show the connections type table', () => {
connectionTypesPage.visit();
connectionTypesPage.shouldHaveConnectionTypes();
});

it('should show the empty state when there are no results', () => {
cy.interceptOdh('GET /api/connection-types', []);
connectionTypesPage.visit();
connectionTypesPage.shouldBeEmpty();
});

it('should show the correct column values', () => {
connectionTypesPage.visit();

const row = connectionTypesPage.getConnectionTypeRow('Test display name');
row.shouldHaveDescription('Test description');
row.shouldHaveCreator('dashboard-admin');
row.shouldBeEnabled();

const row2 = connectionTypesPage.getConnectionTypeRow('no-display-name');
row2.shouldHaveDescription('description 2');
row2.shouldShowPreInstalledLabel();
row2.shouldBeDisabled();
});

it('should show status text when switching enabled state', () => {
connectionTypesPage.visit();
cy.interceptOdh(
'PATCH /api/connection-types/:name',
{ path: { name: 'test-2' } },
{ success: true, error: '' },
);
cy.interceptOdh(
'PATCH /api/connection-types/:name',
{ path: { name: 'no-display-name' } },
{ success: true, error: '' },
);

const row = connectionTypesPage.getConnectionTypeRow('Test display name');
row.findEnableSwitch().click();
row.findEnableStatus().should('have.text', 'Disabling...');

const row2 = connectionTypesPage.getConnectionTypeRow('no-display-name');
row2.findEnableSwitch().click();
row2.findEnableStatus().should('have.text', 'Enabling...');
});
});
5 changes: 5 additions & 0 deletions frontend/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const ClusterSettingsPage = React.lazy(() => import('../pages/clusterSettings/Cl
const CustomServingRuntimeRoutes = React.lazy(
() => import('../pages/modelServing/customServingRuntimes/CustomServingRuntimeRoutes'),
);
const ConnectionTypesPage = React.lazy(() => import('../pages/connectionTypes/ConnectionTypes'));
const GroupSettingsPage = React.lazy(() => import('../pages/groupSettings/GroupSettings'));
const LearningCenterPage = React.lazy(() => import('../pages/learningCenter/LearningCenter'));
const BYONImagesPage = React.lazy(() => import('../pages/BYONImages/BYONImages'));
Expand All @@ -69,6 +70,7 @@ const AppRoutes: React.FC = () => {
const { isAdmin, isAllowed } = useUser();
const isJupyterEnabled = useCheckJupyterEnabled();
const isHomeAvailable = useIsAreaAvailable(SupportedArea.HOME).status;
const isConnectionTypesAvailable = useIsAreaAvailable(SupportedArea.CONNECTION_TYPES).status;

if (!isAllowed) {
return (
Expand Down Expand Up @@ -123,6 +125,9 @@ const AppRoutes: React.FC = () => {
<Route path="/clusterSettings" element={<ClusterSettingsPage />} />
<Route path="/acceleratorProfiles/*" element={<AcceleratorProfileRoutes />} />
<Route path="/servingRuntimes/*" element={<CustomServingRuntimeRoutes />} />
{isConnectionTypesAvailable ? (
<Route path="/connectionTypes" element={<ConnectionTypesPage />} />
) : null}
<Route path="/modelRegistrySettings/*" element={<ModelRegistrySettingsRoutes />} />
<Route path="/groupSettings" element={<GroupSettingsPage />} />
</>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/concepts/areas/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export const SupportedAreasStateMap: SupportedAreasState = {
featureFlags: ['disableCustomServingRuntimes'],
reliantAreas: [SupportedArea.MODEL_SERVING],
},
[SupportedArea.CONNECTION_TYPES]: {
featureFlags: ['disableConnectionTypes'],
},
[SupportedArea.DS_PIPELINES]: {
featureFlags: ['disablePipelines'],
requiredComponents: [StackComponent.DS_PIPELINES],
Expand Down Expand Up @@ -124,7 +127,4 @@ export const SupportedAreasStateMap: SupportedAreasState = {
requiredComponents: [StackComponent.MODEL_REGISTRY],
requiredCapabilities: [StackCapability.SERVICE_MESH, StackCapability.SERVICE_MESH_AUTHZ],
},
[SupportedArea.DATA_CONNECTIONS_TYPES]: {
featureFlags: ['disableConnectionTypes'],
},
};
3 changes: 1 addition & 2 deletions frontend/src/concepts/areas/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export enum SupportedArea {
CLUSTER_SETTINGS = 'cluster-settings',
USER_MANAGEMENT = 'user-management',
ACCELERATOR_PROFILES = 'accelerator-profiles',
CONNECTION_TYPES = 'connections-types',

/* DS Projects specific areas */
DS_PROJECTS_PERMISSIONS = 'ds-projects-permission',
Expand All @@ -61,8 +62,6 @@ export enum SupportedArea {

/* Model Registry areas */
MODEL_REGISTRY = 'model-registry',

DATA_CONNECTIONS_TYPES = 'data-connections-types',
}

/** Components deployed by the Operator. Part of the DSC Status. */
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/concepts/connectionTypes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ export type ConnectionTypeField =
export type ConnectionTypeConfigMap = K8sResourceCommon & {
metadata: {
name: string;
annotations: DisplayNameAnnotations & {
annotations?: DisplayNameAnnotations & {
'opendatahub.io/enabled'?: 'true' | 'false';
'opendatahub.io/username'?: string;
};
labels: DashboardLabels & {
labels?: DashboardLabels & {
'opendatahub.io/connection-type': 'true';
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas';

const useConnectionTypesEnabled = (): boolean =>
useIsAreaAvailable(SupportedArea.DATA_CONNECTIONS_TYPES).status;
useIsAreaAvailable(SupportedArea.CONNECTION_TYPES).status;

export default useConnectionTypesEnabled;
31 changes: 31 additions & 0 deletions frontend/src/pages/connectionTypes/ConnectionTypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { PageSection } from '@patternfly/react-core';
import {
connectionTypesPageDescription,
connectionTypesPageTitle,
} from '~/pages/connectionTypes/const';
import ConnectionTypesTable from '~/pages/connectionTypes/ConnectionTypesTable';
import ApplicationsPage from '~/pages/ApplicationsPage';
import { useWatchConnectionTypes } from '~/utilities/useWatchConnectionTypes';
import EmptyConnectionTypes from '~/pages/connectionTypes/EmptyConnectionTypes';

const ConnectionTypes: React.FC = () => {
const { connectionTypes, loaded, loadError, forceRefresh } = useWatchConnectionTypes();

return (
<ApplicationsPage
loaded={loaded}
loadError={loadError}
empty={loaded && !connectionTypes.length}
emptyStatePage={<EmptyConnectionTypes />}
title={connectionTypesPageTitle}
description={connectionTypesPageDescription}
>
<PageSection isFilled variant="light">
<ConnectionTypesTable connectionTypes={connectionTypes} onUpdate={forceRefresh} />
</PageSection>
</ApplicationsPage>
);
};

export default ConnectionTypes;
Loading

0 comments on commit 58cbe32

Please sign in to comment.