Skip to content

Commit

Permalink
Update modals and generate meaningful k8s name for images
Browse files Browse the repository at this point in the history
  • Loading branch information
DaoDaoNoCode committed Jul 17, 2023
1 parent 2c849f3 commit 33cdefe
Show file tree
Hide file tree
Showing 21 changed files with 714 additions and 505 deletions.
19 changes: 16 additions & 3 deletions backend/src/routes/api/images/imageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import {
import { FastifyRequest } from 'fastify';
import createError from 'http-errors';

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

export const getImageList = async (
fastify: KubeFastifyInstance,
labels: { [key: string]: string },
Expand Down Expand Up @@ -189,8 +196,14 @@ const packagesToString = (packages: BYONImagePackage[]): string => {
const mapImageStreamToBYONImage = (is: ImageStream): BYONImage => ({
id: is.metadata.uid,
name: is.metadata.name,
display_name: is.metadata.annotations['opendatahub.io/notebook-image-name'] || is.metadata.name,
description: is.metadata.annotations['opendatahub.io/notebook-image-desc'] || '',
display_name:
is.metadata.annotations['opendatahub.io/notebook-image-name'] ||
is.metadata.annotations['openshift.io/display-name'] ||
is.metadata.name,
description:
is.metadata.annotations['opendatahub.io/notebook-image-desc'] ||
is.metadata.annotations['openshift.io/description'] ||
'',
visible: is.metadata.labels['opendatahub.io/notebook-image'] === 'true',
error: getBYONImageErrorMessage(is),
packages: JSON.parse(
Expand Down Expand Up @@ -240,7 +253,7 @@ export const postImage = async (
'opendatahub.io/notebook-image-url': fullUrl,
'opendatahub.io/notebook-image-creator': body.provider,
},
name: `byon-${Date.now()}`,
name: `custom-${translateDisplayNameForK8s(body.display_name)}`,
namespace: namespace,
labels: labels,
},
Expand Down
1 change: 1 addition & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ export type ODHSegmentKey = {

export type BYONImage = {
id: string;
// FIXME: This shouldn't be a user defined value consumed from the request payload but should be a controlled value from an authentication middleware.
provider: string;
imported_time: string;
error: string;
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/concepts/dashboard/DashboardInlineHelpIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react';
import { Icon, Text, TextContent, TextVariants } from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';

const DashboardInlineHelpIcon: React.FC = () => (
<Icon isInline>
<TextContent>
<Text component={TextVariants.small}>
<HelpIcon />
</Text>
</TextContent>
</Icon>
);

export default DashboardInlineHelpIcon;
19 changes: 19 additions & 0 deletions frontend/src/concepts/dashboard/DashboardSmallTextList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Text, TextContent } from '@patternfly/react-core';
import * as React from 'react';

type DashboardSmallTextListProps = {
keyPrefix: string;
textList: string[];
};

const DashboardSmallTextList: React.FC<DashboardSmallTextListProps> = ({ keyPrefix, textList }) => (
<TextContent>
{textList.map((text, i) => (
<Text style={{ marginBottom: 0 }} key={`${keyPrefix}-${text}-${i}`} component="small">
{text}
</Text>
))}
</TextContent>
);

export default DashboardSmallTextList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import * as React from 'react';
import {
Button,
EmptyState,
EmptyStateBody,
EmptyStateIcon,
EmptyStatePrimary,
EmptyStateVariant,
TabContent,
Title,
} from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';
import { BYONImagePackage } from '~/types';
import { DisplayedContentTab } from './ManageBYONImageModal';
import DisplayedContentTable from './DisplayedContentTable';

type DisplayedContentTabContentProps = {
activeKey: string | number;
tabKey: DisplayedContentTab;
resources: BYONImagePackage[];
setResources: React.Dispatch<React.SetStateAction<BYONImagePackage[]>>;
tempResources: BYONImagePackage[];
setTempResources: React.Dispatch<React.SetStateAction<BYONImagePackage[]>>;
editIndex?: number;
setEditIndex: (index?: number) => void;
};

const DisplayedContentTabContent: React.FC<DisplayedContentTabContentProps> = ({
activeKey,
tabKey,
resources,
setResources,
tempResources,
setTempResources,
editIndex,
setEditIndex,
}) => {
const resourceType = tabKey === DisplayedContentTab.SOFTWARE ? 'software' : 'packages';

const addEmptyRow = React.useCallback(() => {
setTempResources((prev) => [
...prev,
{
name: '',
version: '',
visible: true,
},
]);
setEditIndex(tempResources.length);
}, [tempResources.length, setTempResources, setEditIndex]);

return (
<TabContent
id={`tabContent-${tabKey}`}
eventKey={tabKey}
activeKey={activeKey}
hidden={tabKey !== activeKey}
>
{tempResources.length === 0 ? (
<EmptyState variant={EmptyStateVariant.small}>
<EmptyStateIcon icon={PlusCircleIcon} />
<Title headingLevel="h2" size="lg">
No {resourceType} displayed
</Title>
<EmptyStateBody>
Displayed contents help inform other users of what your notebook image contains. To add
displayed content, add the names of software or packages included in your image that you
want users to know about.
</EmptyStateBody>
<EmptyStatePrimary>
<Button
data-id={`add-${resourceType}-button`}
variant="secondary"
onClick={addEmptyRow}
>
Add {resourceType}
</Button>
</EmptyStatePrimary>
</EmptyState>
) : (
<DisplayedContentTable
tabKey={tabKey}
onReset={() => {
setTempResources([...resources]);
setEditIndex(undefined);
}}
onConfirm={(rowIndex, name, version) => {
const copiedArray = [...tempResources];
copiedArray[rowIndex].name = name;
copiedArray[rowIndex].version = version;
setTempResources(copiedArray);
setResources(copiedArray);
setEditIndex(undefined);
}}
resources={tempResources}
onAdd={addEmptyRow}
editIndex={editIndex}
onEdit={setEditIndex}
onDelete={(index) => {
const copiedArray = [...resources];
copiedArray.splice(index, 1);
setTempResources(copiedArray);
setResources(copiedArray);
}}
/>
)}
</TabContent>
);
};

export default DisplayedContentTabContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as React from 'react';
import { Button, Panel, PanelFooter, PanelHeader, PanelMainBody } from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';
import Table from '~/components/table/Table';
import { BYONImagePackage } from '~/types';
import { DisplayedContentTab } from './ManageBYONImageModal';
import { getColumns } from './tableData';
import DisplayedContentTableRow from './DisplayedContentTableRow';

type DisplayedContentTableProps = {
tabKey: DisplayedContentTab;
onReset: () => void;
onConfirm: (rowIndex: number, name: string, version: string) => void;
resources: BYONImagePackage[];
onAdd: () => void;
editIndex?: number;
onEdit: (index: number) => void;
onDelete: (index: number) => void;
};

const DisplayedContentTable: React.FC<DisplayedContentTableProps> = ({
tabKey,
onReset,
onConfirm,
resources,
onAdd,
editIndex,
onEdit,
onDelete,
}) => {
const content = tabKey === DisplayedContentTab.SOFTWARE ? 'software' : 'packages';
const columns = getColumns(tabKey);

return (
<Panel>
<PanelHeader>
Add the {content} labels that will be displayed with this notebook image. Modifying the{' '}
{content} here does not effect the contents of the notebook image.
</PanelHeader>
<PanelMainBody>
<Table
variant="compact"
data={resources}
columns={columns}
disableRowRenderSupport
rowRenderer={(resource, rowIndex) => (
<DisplayedContentTableRow
key={rowIndex}
obj={resource}
tabKey={tabKey}
isActive={editIndex === rowIndex}
isEditing={editIndex !== undefined}
onConfirm={(name, version) => onConfirm(rowIndex, name, version)}
onReset={onReset}
onEdit={() => onEdit(rowIndex)}
onDelete={() => onDelete(rowIndex)}
onMoveToNextRow={() => {
if (rowIndex === resources.length - 1) {
onAdd();
} else {
onEdit(rowIndex + 1);
}
}}
/>
)}
/>
</PanelMainBody>
<PanelFooter>
<Button
data-id="add-resource-button"
variant="link"
icon={<PlusCircleIcon />}
onClick={onAdd}
isDisabled={editIndex !== undefined}
isInline
>
Add {content}
</Button>
</PanelFooter>
</Panel>
);
};

export default DisplayedContentTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as React from 'react';
import { Tbody, Td, Tr } from '@patternfly/react-table';
import { ActionList, ActionListItem, Button, TextInput } from '@patternfly/react-core';
import { CheckIcon, MinusCircleIcon, PencilAltIcon, TimesIcon } from '@patternfly/react-icons';
import { BYONImagePackage } from '~/types';
import { DisplayedContentTab } from './ManageBYONImageModal';

type DisplayedContentTableRowProps = {
tabKey: DisplayedContentTab;
obj: BYONImagePackage;
isActive: boolean;
isEditing: boolean;
onConfirm: (name: string, version: string) => void;
onReset: () => void;
onEdit: () => void;
onDelete: () => void;
onMoveToNextRow: () => void;
};

const DisplayedContentTableRow: React.FC<DisplayedContentTableRowProps> = ({
tabKey,
obj,
isActive,
isEditing,
onConfirm,
onReset,
onEdit,
onDelete,
onMoveToNextRow,
}) => {
const [name, setName] = React.useState(obj.name);
const [version, setVersion] = React.useState(obj.version);

const onKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
event.stopPropagation();
if (event.key === 'Enter') {
onConfirm(name, version);
onMoveToNextRow();
}
if (event.key === 'Escape') {
onReset();
}
},
[name, onConfirm, onMoveToNextRow, version, onReset],
);

return (
<Tbody>
<Tr>
<Td dataLabel={tabKey === DisplayedContentTab.SOFTWARE ? 'Software' : 'Packages'}>
{isActive ? (
<TextInput value={name} onChange={setName} onKeyDown={onKeyDown} autoFocus />
) : (
<>{obj.name}</>
)}
</Td>
<Td dataLabel="Version">
{isActive ? (
<TextInput value={version} onChange={setVersion} onKeyDown={onKeyDown} />
) : (
<>{obj.version}</>
)}
</Td>
<Td isActionCell modifier="nowrap" style={{ textAlign: 'right' }}>
<ActionList isIconList>
{isActive ? (
<>
<ActionListItem>
<Button
aria-label="Save displayed content"
variant="link"
onClick={() => onConfirm(name, version)}
>
<CheckIcon />
</Button>
</ActionListItem>
<ActionListItem>
<Button aria-label="Discard displayed content" variant="plain" onClick={onReset}>
<TimesIcon />
</Button>
</ActionListItem>
</>
) : (
<>
<ActionListItem>
<Button
aria-label="Edit displayed content"
isDisabled={isEditing}
variant="plain"
onClick={onEdit}
>
<PencilAltIcon />
</Button>
</ActionListItem>
<ActionListItem>
<Button
aria-label="Remove displayed content"
isDisabled={isEditing}
variant="plain"
onClick={onDelete}
>
<MinusCircleIcon />
</Button>
</ActionListItem>
</>
)}
</ActionList>
</Td>
</Tr>
</Tbody>
);
};

export default DisplayedContentTableRow;
Loading

0 comments on commit 33cdefe

Please sign in to comment.