From e28a142e966cf689fd8b51ba9049763626dc2eba Mon Sep 17 00:00:00 2001 From: Kevin Su Date: Mon, 8 May 2023 09:06:18 -0700 Subject: [PATCH] Add support fetching description entity (#735) * Add Description entity Signed-off-by: Kevin Su * Add Description entity Signed-off-by: Kevin Su * nit Signed-off-by: Kevin Su * nit Signed-off-by: Kevin Su * nit Signed-off-by: Kevin Su * address comment Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su * test ci Signed-off-by: Kevin Su --------- Signed-off-by: Kevin Su Co-authored-by: Jason Porter <84735036+jsonporter@users.noreply.github.com> --- .../components/Entities/EntityDescription.tsx | 45 +++++++++++++++---- .../src/components/Entities/strings.ts | 2 + .../components/Task/SimpleTaskInterface.tsx | 7 ++- .../Workflow/SearchableWorkflowNameList.tsx | 10 +++++ .../Workflow/useWorkflowInfoItem.ts | 4 +- .../__stories__/WorkflowGraph.stories.tsx | 2 + .../console/src/components/data/apiContext.ts | 5 ++- .../src/components/hooks/useDescription.ts | 31 +++++++++++++ .../console/src/models/Common/constants.ts | 1 + packages/console/src/models/Common/utils.ts | 14 ++++++ .../src/models/DescriptionEntity/api.ts | 30 +++++++++++++ .../src/models/DescriptionEntity/types.ts | 28 ++++++++++++ .../src/models/DescriptionEntity/utils.ts | 19 ++++++++ packages/console/src/models/Task/types.ts | 2 + packages/console/src/models/Workflow/types.ts | 3 +- 15 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 packages/console/src/components/hooks/useDescription.ts create mode 100644 packages/console/src/models/DescriptionEntity/api.ts create mode 100644 packages/console/src/models/DescriptionEntity/types.ts create mode 100644 packages/console/src/models/DescriptionEntity/utils.ts diff --git a/packages/console/src/components/Entities/EntityDescription.tsx b/packages/console/src/components/Entities/EntityDescription.tsx index d8d0253ac..a76734f7a 100644 --- a/packages/console/src/components/Entities/EntityDescription.tsx +++ b/packages/console/src/components/Entities/EntityDescription.tsx @@ -3,10 +3,8 @@ import { makeStyles, Theme } from '@material-ui/core/styles'; import classnames from 'classnames'; import { useCommonStyles } from 'components/common/styles'; import { WaitForData } from 'components/common/WaitForData'; -import { useNamedEntity } from 'components/hooks/useNamedEntity'; import { IdentifierScope, - NamedEntityMetadata, ResourceIdentifier, Variable, } from 'models/Common/types'; @@ -20,7 +18,8 @@ import { TaskClosure } from 'models/Task/types'; import { executionFilterGenerator } from './generators'; import { Row } from './Row'; import t, { patternKey } from './strings'; -import { entityStrings, entitySections } from './constants'; +import {entityStrings, entitySections} from './constants'; +import {useDescriptionEntityList} from "../hooks/useDescription"; const Skeleton = reactLoadingSkeleton; @@ -96,9 +95,30 @@ export const EntityDescription: React.FC<{ }> = ({ id }) => { const commonStyles = useCommonStyles(); const styles = useStyles(); - const namedEntity = useNamedEntity(id); - const { metadata = {} as NamedEntityMetadata } = namedEntity.value; - const hasDescription = !!metadata.description; + + const { resourceType } = id; + const sort = { + key: executionSortFields.createdAt, + direction: SortDirection.DESCENDING, + }; + + const baseFilters = React.useMemo( + () => executionFilterGenerator[resourceType](id), + [id, resourceType], + ); + + const descriptionEntities = useDescriptionEntityList( + { ...id, version: '' }, + { + sort, + filter: baseFilters, + limit: 1, + }, + ); + + const descriptionEntity = descriptionEntities?.value?.[0] + const hasDescription = descriptionEntity?.longDescription.value.length !== 0; + const hasLink = !!descriptionEntity?.sourceCode?.link; const sections = entitySections[id.resourceType]; return ( @@ -113,7 +133,7 @@ export const EntityDescription: React.FC<{ className={styles.description} > @@ -124,12 +144,21 @@ export const EntityDescription: React.FC<{ })} > {hasDescription - ? metadata.description + ? descriptionEntity?.longDescription?.value : t( patternKey('noDescription', entityStrings[id.resourceType]), )} + {hasLink && ( + + {hasLink + ?{descriptionEntity?.sourceCode?.link} + : t( + patternKey('noGithubLink', entityStrings[id.resourceType]), + )} + + )} {sections?.descriptionInputsAndOutputs && } diff --git a/packages/console/src/components/Entities/strings.ts b/packages/console/src/components/Entities/strings.ts index 3ceeb18a8..9c7e676be 100644 --- a/packages/console/src/components/Entities/strings.ts +++ b/packages/console/src/components/Entities/strings.ts @@ -10,6 +10,7 @@ const str = { noDescription_task: 'This task has no description.', noSchedules_workflow: 'This workflow has no schedules.', noSchedules_task: 'This task has no schedules.', + noDescription: 'No description found.', allExecutionsChartTitle_workflow: 'All Executions in the Workflow', allExecutionsChartTitle_task: 'All Executions in the Task', allExecutionsChartTitle_launch_plan: 'All Executions Using Launch Plan', @@ -22,6 +23,7 @@ const str = { details_task: 'Task Details', inputsFieldName: 'Inputs', outputsFieldName: 'Outputs', + githubLink: 'Git', imageFieldName: 'Image', envVarsFieldName: 'Env Vars', commandsFieldName: 'Commands', diff --git a/packages/console/src/components/Task/SimpleTaskInterface.tsx b/packages/console/src/components/Task/SimpleTaskInterface.tsx index f58182616..28416e1d9 100644 --- a/packages/console/src/components/Task/SimpleTaskInterface.tsx +++ b/packages/console/src/components/Task/SimpleTaskInterface.tsx @@ -66,10 +66,11 @@ const VariablesList: React.FC<{ variables: Record }> = ({ export const SimpleTaskInterface: React.FC<{ task: Task }> = ({ task }) => { const { inputs = emptyVariables, outputs = emptyVariables } = task.closure.compiledTask.template.interface || {}; + const description = task.shortDescription || "No description found."; return (
= ({ task }) => { name: 'outputs', content: , }, + { + name: 'description', + content: {description} + } ]} />
diff --git a/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx b/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx index 187b785ee..3215c887a 100644 --- a/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx +++ b/packages/console/src/components/Workflow/SearchableWorkflowNameList.tsx @@ -331,6 +331,16 @@ const SearchableWorkflowNameItem: React.FC = )} +
+
Description
+
+ {isLoading ? ( + + ) : ( + workflow?.description ?? No description found. + )} +
+
0 ? parsedOutputs.join(', ') : undefined; - return { id, inputs, outputs }; + const description = workflow?.shortDescription && workflow?.shortDescription.length > 0 ? workflow.shortDescription : undefined + return { id, inputs, outputs, description}; }, { staleTime: 1000 * 60 * 5, diff --git a/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx b/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx index 6c0295983..319983255 100644 --- a/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx +++ b/packages/console/src/components/WorkflowGraph/__stories__/WorkflowGraph.stories.tsx @@ -10,6 +10,7 @@ import { WorkflowGraph } from '../WorkflowGraph'; import graphData from './rich.json'; const graphDataClosure = graphData as unknown as CompiledWorkflowClosure; +const shortDescription = "" const workflow: Workflow = { closure: { compiledWorkflow: graphDataClosure }, @@ -19,6 +20,7 @@ const workflow: Workflow = { name: 'test', version: '1', }, + shortDescription: shortDescription, }; const onNodeSelectionChanged = action('nodeSelected'); diff --git a/packages/console/src/components/data/apiContext.ts b/packages/console/src/components/data/apiContext.ts index a9f41eb91..6f2295ab3 100644 --- a/packages/console/src/components/data/apiContext.ts +++ b/packages/console/src/components/data/apiContext.ts @@ -4,6 +4,7 @@ import * as LaunchAPI from 'models/Launch/api'; import * as ProjectAPI from 'models/Project/api'; import * as TaskAPI from 'models/Task/api'; import * as WorkflowAPI from 'models/Workflow/api'; +import * as DescriptionEntityAPI from 'models/DescriptionEntity/api' import * as React from 'react'; type APIFunctions = typeof CommonAPI & @@ -11,7 +12,8 @@ type APIFunctions = typeof CommonAPI & typeof LaunchAPI & typeof ProjectAPI & typeof TaskAPI & - typeof WorkflowAPI; + typeof WorkflowAPI & + typeof DescriptionEntityAPI; export interface APIContextValue extends APIFunctions { // use API functions only, for now @@ -24,6 +26,7 @@ export const defaultAPIContextValue: APIContextValue = { ...ProjectAPI, ...TaskAPI, ...WorkflowAPI, + ...DescriptionEntityAPI }; /** Exposes all of the model layer api functions for use by data fetching diff --git a/packages/console/src/components/hooks/useDescription.ts b/packages/console/src/components/hooks/useDescription.ts new file mode 100644 index 000000000..cc74e737d --- /dev/null +++ b/packages/console/src/components/hooks/useDescription.ts @@ -0,0 +1,31 @@ +import {Identifier, IdentifierScope, RequestConfig} from "../../models"; +import {useFetchableData} from "./useFetchableData"; +import {getDescriptionEntity, listDescriptionEntities} from "../../models/DescriptionEntity/api"; +import {DescriptionEntity} from "../../models/DescriptionEntity/types"; +import {FetchableData} from "./types"; +import {useAPIContext} from "../data/apiContext"; +import {usePagination} from "./usePagination"; + + +/** A hook for fetching a description entity */ +export function useDescriptionEntity(id: Identifier): FetchableData { + const { getDescriptionEntity } = useAPIContext(); + return useFetchableData( + { + useCache: true, + debugName: 'DescriptionEntity', + defaultValue: {} as DescriptionEntity, + doFetch: async descriptionEntityId => (await getDescriptionEntity(descriptionEntityId)) as DescriptionEntity, + }, + id, + ); +} + +/** A hook for fetching a paginated list of description entities */ +export function useDescriptionEntityList(scope: IdentifierScope, config: RequestConfig) { + const { listDescriptionEntities } = useAPIContext(); + return usePagination( + { ...config, cacheItems: true, fetchArg: scope }, + listDescriptionEntities, + ); +} diff --git a/packages/console/src/models/Common/constants.ts b/packages/console/src/models/Common/constants.ts index 198e7a59b..df7b72725 100644 --- a/packages/console/src/models/Common/constants.ts +++ b/packages/console/src/models/Common/constants.ts @@ -13,6 +13,7 @@ export const endpointPrefixes = { recoverExecution: '/executions/recover', setSignal: '/signals', task: '/tasks', + descriptionEntity: 'description_entities', taskExecution: '/task_executions', taskExecutionChildren: '/children/task_executions', workflow: '/workflows', diff --git a/packages/console/src/models/Common/utils.ts b/packages/console/src/models/Common/utils.ts index 9c005bfd2..d435009e7 100644 --- a/packages/console/src/models/Common/utils.ts +++ b/packages/console/src/models/Common/utils.ts @@ -50,3 +50,17 @@ export function makeNamedEntityPath({ name, ]).join('/'); } + +export function makeDescriptionEntityPath( + prefix: string, + { resourceType, project, domain, name, version }: Partial, +) { + const path = takeWhile([ + resourceType, + project, + domain, + name, + decodeURIComponent(version || ''), + ]).join('/'); + return `${prefix}/${path}`; +} diff --git a/packages/console/src/models/DescriptionEntity/api.ts b/packages/console/src/models/DescriptionEntity/api.ts new file mode 100644 index 000000000..115753e71 --- /dev/null +++ b/packages/console/src/models/DescriptionEntity/api.ts @@ -0,0 +1,30 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { + getAdminEntity, +} from 'models/AdminEntity/AdminEntity'; +import { defaultPaginationConfig } from 'models/AdminEntity/constants'; +import { RequestConfig } from 'models/AdminEntity/types'; +import { Identifier, IdentifierScope } from 'models/Common/types'; +import { DescriptionEntity } from './types'; +import { makeDescriptionPath, descriptionEntityListTransformer } from './utils'; + +/** Fetches a list of `DescriptionEntity` records matching the provided `scope` */ +export const listDescriptionEntities = (scope: IdentifierScope, config?: RequestConfig) => + getAdminEntity( + { + path: makeDescriptionPath(scope), + messageType: Admin.DescriptionEntityList, + transform: descriptionEntityListTransformer, + }, + { ...defaultPaginationConfig, ...config }, + ); + +/** Fetches an individual `DescriptionEntity` record */ +export const getDescriptionEntity = (id: Identifier, config?: RequestConfig) => + getAdminEntity( + { + path: makeDescriptionPath(id), + messageType: Admin.DescriptionEntity, + }, + config, + ); diff --git a/packages/console/src/models/DescriptionEntity/types.ts b/packages/console/src/models/DescriptionEntity/types.ts new file mode 100644 index 000000000..14f013ec0 --- /dev/null +++ b/packages/console/src/models/DescriptionEntity/types.ts @@ -0,0 +1,28 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { Identifier } from 'models/Common/types'; + + +/** Optional link to source code used to define this entity */ +export interface SourceCode extends Admin.ISourceCode { + link?: string +} + +/** Full user description with formatting preserved */ +export interface LongDescription extends Admin.IDescription { + value: string; + uri: string; + format: Admin.DescriptionFormat + iconLink?: string +} + +/* +DescriptionEntity contains detailed description for the task/workflow. +Documentation could provide insight into the algorithms, business use case, etc +*/ +export interface DescriptionEntity extends Admin.IDescriptionEntity { + id: Identifier; + shortDescription: string; + longDescription: LongDescription + sourceCode?: SourceCode + tags?: string[] +} diff --git a/packages/console/src/models/DescriptionEntity/utils.ts b/packages/console/src/models/DescriptionEntity/utils.ts new file mode 100644 index 000000000..889a13cb0 --- /dev/null +++ b/packages/console/src/models/DescriptionEntity/utils.ts @@ -0,0 +1,19 @@ +import { Admin } from '@flyteorg/flyteidl-types'; +import { createPaginationTransformer } from 'models/AdminEntity/utils'; +import { endpointPrefixes } from 'models/Common/constants'; +import { IdentifierScope } from 'models/Common/types'; +import { makeDescriptionEntityPath } from 'models/Common/utils'; +import { DescriptionEntity } from './types'; + +/** Generate the correct path for retrieving a DescriptionEntity or list of DescriptionEntities based on the + * given scope. + */ +export function makeDescriptionPath(scope: IdentifierScope) { + return makeDescriptionEntityPath(endpointPrefixes.descriptionEntity, scope); +} + +/** Transformer to coerce an `Admin.DescriptionEntityList` into a standard shape */ +export const descriptionEntityListTransformer = createPaginationTransformer< + DescriptionEntity, + Admin.DescriptionEntityList +>('descriptionEntities'); diff --git a/packages/console/src/models/Task/types.ts b/packages/console/src/models/Task/types.ts index 47e29a675..067c42e4c 100644 --- a/packages/console/src/models/Task/types.ts +++ b/packages/console/src/models/Task/types.ts @@ -28,6 +28,7 @@ export interface TaskTemplate extends Core.ITaskTemplate { metadata?: TaskMetadata; closure?: TaskClosure; type: string; + shortDescription?: string; } /** An instance of a task which has been serialized into a `TaskClosure` */ @@ -44,4 +45,5 @@ export interface TaskClosure extends Admin.ITaskClosure { export interface Task extends Admin.ITask { id: Identifier; closure: TaskClosure; + shortDescription?: string; } diff --git a/packages/console/src/models/Workflow/types.ts b/packages/console/src/models/Workflow/types.ts index 0b880dd9d..240f1dab1 100644 --- a/packages/console/src/models/Workflow/types.ts +++ b/packages/console/src/models/Workflow/types.ts @@ -27,7 +27,7 @@ export interface CompiledWorkflowClosure extends Core.ICompiledWorkflowClosure { tasks: CompiledTask[]; } -/** A serialized representation of all inforamtion about a specific workflow +/** A serialized representation of all information about a specific workflow * version. */ export interface WorkflowClosure extends Admin.IWorkflowClosure { @@ -37,6 +37,7 @@ export interface WorkflowClosure extends Admin.IWorkflowClosure { export interface Workflow extends Admin.IWorkflow { closure?: WorkflowClosure; id: Identifier; + shortDescription?: string; } export type WorkflowId = Identifier;