From ca87fe00567f1571019eda1c64f398522f3eee0f Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 11:20:38 +0100 Subject: [PATCH 01/12] custom notification page --- pages/custom/notifications/index.tsx | 100 +++++++++++++++++++++++++++ translations/en-GB.json | 15 +++- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 pages/custom/notifications/index.tsx diff --git a/pages/custom/notifications/index.tsx b/pages/custom/notifications/index.tsx new file mode 100644 index 0000000..e991534 --- /dev/null +++ b/pages/custom/notifications/index.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; +import { StringParam, useQueryParam, withDefault } from 'use-query-params'; + +import { EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import type { + StoredTableConfig, + SubscriptionListItem, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + DEFAULT_PAGE_SIZE, + SUBSCRIPTIONS_TABLE_LOCAL_STORAGE_KEY, + SortOrder, + WfoFilterTabs, + WfoSubscriptionListTab, + WfoSubscriptionsList, + subscriptionListTabs, + useDataDisplayParams, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; + +export default function NotificationsPage() { + const t = useTranslations('notifications'); + + const [tableDefaults, setTableDefaults] = + useState>(); + + const getStoredTableConfig = useStoredTableConfig( + SUBSCRIPTIONS_TABLE_LOCAL_STORAGE_KEY, + ); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // the fist time before the useEffect has populated the tableDefaults. Better is to create a way for + // the query to wait for the values to be available + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: 'startDate', + order: SortOrder.DESC, + }, + }); + + const [activeTab, setActiveTab] = useQueryParam( + 'activeTab', + withDefault(StringParam, WfoSubscriptionListTab.ACTIVE), + ); + + const selectedTab = ((): WfoSubscriptionListTab => { + return ( + subscriptionListTabs.find(({ id }) => id === activeTab)?.id || + WfoSubscriptionListTab.ACTIVE + ); + })(); + + const handleChangeSubscriptionsTab = ( + updatedSubscriptionsTab: WfoSubscriptionListTab, + ) => { + setActiveTab(updatedSubscriptionsTab); + setDataDisplayParam('pageIndex', 0); + }; + + const alwaysOnFilters = subscriptionListTabs.find( + ({ id }) => id === activeTab, + )?.alwaysOnFilters; + + return ( + <> + + + + + + + + + + + ); +} diff --git a/translations/en-GB.json b/translations/en-GB.json index 0967ef4..4ff5e49 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -1 +1,14 @@ -{} +{ + "notifications": { + "title": "Notifications", + + "tabs": { + "active": "Active", + "terminated": "Tab 2", + "transient": "Tab 3", + "all": "All" + }, + + "loadingStatus": "Loading status" + } +} From d0148f901f5a38db64ec16ed4c3be773852821d0 Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 11:41:27 +0100 Subject: [PATCH 02/12] Replace subsctions with notfications in sidebar menu --- pages/_app.tsx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pages/_app.tsx b/pages/_app.tsx index af300ed..a0aa0a9 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,12 +4,15 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import { QueryClientConfig } from 'react-query/types/core/types'; +import { get } from 'http'; import { SessionProvider } from 'next-auth/react'; import { NextAdapter } from 'next-query-params'; import App, { AppContext, AppInitialProps, AppProps } from 'next/app'; import Head from 'next/head'; +import { useRouter } from 'next/router'; import { QueryParamProvider } from 'use-query-params'; +import type { EuiSideNavItemType } from '@elastic/eui'; import { EuiProvider, EuiThemeColorMode } from '@elastic/eui'; import '@elastic/eui/dist/eui_theme_dark.min.css'; import '@elastic/eui/dist/eui_theme_light.min.css'; @@ -49,6 +52,7 @@ function CustomApp({ pageProps, orchestratorConfig, }: AppProps & AppOwnProps) { + const router = useRouter(); const [queryClient] = useState(() => new QueryClient(queryClientConfig)); const [themeMode, setThemeMode] = useState( @@ -72,6 +76,33 @@ function CustomApp({ } }, []); + const getMenuItems = ( + defaultMenuItems: EuiSideNavItemType[], + ): EuiSideNavItemType[] => { + const subscriptionsMenuItemIndex = defaultMenuItems.findIndex( + (menuItem) => menuItem.id === '4', + ); + + const notificationsMenuItem = { + name: 'Notifications', + id: '4', + isSelected: router.pathname === '/custom/notifications', + href: '/custom/notifications', + onClick: (e) => { + e.preventDefault(); + router.push('/custom/notifications'); + }, + }; + + defaultMenuItems.splice( + subscriptionsMenuItemIndex, + 1, + notificationsMenuItem, + ); + + return defaultMenuItems; + }; + return ( Date: Mon, 25 Mar 2024 13:59:42 +0100 Subject: [PATCH 03/12] Custom settings page --- pages/custom/custom-settings.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pages/custom/custom-settings.tsx diff --git a/pages/custom/custom-settings.tsx b/pages/custom/custom-settings.tsx new file mode 100644 index 0000000..ca2f1f5 --- /dev/null +++ b/pages/custom/custom-settings.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import { EuiHorizontalRule, EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { + WfoFlushSettings, + WfoModifySettings, + WfoStatus, +} from '@orchestrator-ui/orchestrator-ui-components'; + +export default function CustomSettingsPage() { + return ( + <> + + + + + + + + + + + ); +} From 16b86fd2ad57d70f30395deeff947a225c7ac8d2 Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 14:07:39 +0100 Subject: [PATCH 04/12] Custom jobslist based on tasklist --- pages/custom/jobs/[jobId].tsx | 0 pages/custom/jobs/index.tsx | 198 ++++++++++++++++++++++++++++ pages/custom/jobs/new/[jobName].tsx | 0 translations/en-GB.json | 6 +- 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 pages/custom/jobs/[jobId].tsx create mode 100644 pages/custom/jobs/index.tsx create mode 100644 pages/custom/jobs/new/[jobName].tsx diff --git a/pages/custom/jobs/[jobId].tsx b/pages/custom/jobs/[jobId].tsx new file mode 100644 index 0000000..e69de29 diff --git a/pages/custom/jobs/index.tsx b/pages/custom/jobs/index.tsx new file mode 100644 index 0000000..8671ed8 --- /dev/null +++ b/pages/custom/jobs/index.tsx @@ -0,0 +1,198 @@ +import React, { useContext, useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { StringParam, useQueryParam, withDefault } from 'use-query-params'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPageHeader, + EuiSpacer, +} from '@elastic/eui'; +import { + ACTIVE_TASKS_LIST_TABLE_LOCAL_STORAGE_KEY, + COMPLETED_TASKS_LIST_TABLE_LOCAL_STORAGE_KEY, + DEFAULT_PAGE_SIZE, + StoredTableConfig, + WfoFilterTabs, + WfoIsAllowedToRender, + WfoStartTaskButtonComboBox, + WfoTableColumns, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { PATH_TASKS } from '@orchestrator-ui/orchestrator-ui-components'; +import { + ProcessListItem, + WfoProcessesList, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { PolicyResource } from '@orchestrator-ui/orchestrator-ui-components'; +import { ConfirmationDialogContext } from '@orchestrator-ui/orchestrator-ui-components'; +import { + useCheckEngineStatus, + useDataDisplayParams, + useMutateProcess, + useOrchestratorTheme, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoRefresh } from '@orchestrator-ui/orchestrator-ui-components'; +import { + WfoTasksListTabType, + defaultTasksListTabs, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { getTasksListTabTypeFromString } from '@orchestrator-ui/orchestrator-ui-components'; +import { SortOrder } from '@orchestrator-ui/orchestrator-ui-components'; + +export default function JobsListPage() { + const router = useRouter(); + const t = useTranslations('jobsList'); + const [activeTab, setActiveTab] = useQueryParam( + 'activeTab', + withDefault(StringParam, WfoTasksListTabType.ACTIVE), + ); + + const [tableDefaults, setTableDefaults] = + useState>(); + + const selectedTasksListTab = getTasksListTabTypeFromString(activeTab); + + const localStorageKey = + selectedTasksListTab === WfoTasksListTabType.ACTIVE + ? ACTIVE_TASKS_LIST_TABLE_LOCAL_STORAGE_KEY + : COMPLETED_TASKS_LIST_TABLE_LOCAL_STORAGE_KEY; + + const getStoredTableConfig = + useStoredTableConfig(localStorageKey); + + const { theme } = useOrchestratorTheme(); + const { showConfirmDialog } = useContext(ConfirmationDialogContext); + const { retryAllProcesses } = useMutateProcess(); + const { isEngineRunningNow } = useCheckEngineStatus(); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: 'lastModifiedAt', + order: SortOrder.DESC, + }, + }); + + const handleChangeTasksListTab = ( + updatedTasksListTab: WfoTasksListTabType, + ) => { + setActiveTab(updatedTasksListTab); + setDataDisplayParam('pageIndex', 0); + }; + + const alwaysOnFilters = defaultTasksListTabs.find( + ({ id }) => id === selectedTasksListTab, + )?.alwaysOnFilters; + + if (!selectedTasksListTab) { + router.replace(PATH_TASKS); + return null; + } + + const handleRerunAllButtonClick = async () => { + if (await isEngineRunningNow()) { + showConfirmDialog({ + question: t('rerunAllQuestion'), + confirmAction: () => { + retryAllProcesses.mutate(); + }, + }); + } + }; + + // Changing the order of the keys, resulting in an updated column order in the table + const handleOverrideTableColumns: ( + defaultTableColumns: WfoTableColumns, + ) => WfoTableColumns = (defaultTableColumns) => ({ + workflowName: { + field: 'workflowName', + name: t('jobName'), + render: (value, { processId }) => ( + {value} + ), + }, + lastStep: defaultTableColumns.lastStep, + lastStatus: defaultTableColumns.lastStatus, + workflowTarget: defaultTableColumns.workflowTarget, + productTag: defaultTableColumns.tag, + productName: defaultTableColumns.productName, + customer: defaultTableColumns.customer, + customerAbbreviation: defaultTableColumns.customerAbbreviation, + subscriptions: defaultTableColumns.subscriptions, + createdBy: defaultTableColumns.createdBy, + assignee: defaultTableColumns.assignee, + processId: defaultTableColumns.processId, + startedAt: defaultTableColumns.startedAt, + lastModifiedAt: defaultTableColumns.lastModifiedAt, + }); + + return ( + <> + + + + + + + + + + {' '} + + ( + + )} + > + {t('rerunAll')} + + + + + + + + + + + + + + + ); +} diff --git a/pages/custom/jobs/new/[jobName].tsx b/pages/custom/jobs/new/[jobName].tsx new file mode 100644 index 0000000..e69de29 diff --git a/translations/en-GB.json b/translations/en-GB.json index 4ff5e49..d7e8617 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -10,5 +10,9 @@ }, "loadingStatus": "Loading status" - } + }, + "jobsList": { + "rerunAll": "Rerun all jobs", + "rerunAllQuestion": "Sure?", + "jobName": "Job name" } } From bdc8864780328117475d070d91278e506a9e4f00 Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 14:27:57 +0100 Subject: [PATCH 05/12] Custom job detail page --- pages/custom/jobs/[jobId].tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pages/custom/jobs/[jobId].tsx b/pages/custom/jobs/[jobId].tsx index e69de29..40f2321 100644 --- a/pages/custom/jobs/[jobId].tsx +++ b/pages/custom/jobs/[jobId].tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { useRouter } from 'next/router'; + +import { WfoProcessDetailPage } from '@orchestrator-ui/orchestrator-ui-components'; + +export default function JobDetailPage() { + const router = useRouter(); + const { jobId } = router.query; + + return ( + <> + {(jobId && typeof jobId === 'string' && ( + + )) ||
Invalid taskId
} + + ); +} From d98efd8444ee048eaa2bcde7f820070d5137820b Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 14:30:14 +0100 Subject: [PATCH 06/12] Custom start job page --- pages/custom/jobs/new/[jobName].tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pages/custom/jobs/new/[jobName].tsx b/pages/custom/jobs/new/[jobName].tsx index e69de29..efd033b 100644 --- a/pages/custom/jobs/new/[jobName].tsx +++ b/pages/custom/jobs/new/[jobName].tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { useRouter } from 'next/router'; + +import { WfoStartProcessPage } from '@orchestrator-ui/orchestrator-ui-components'; + +export default function StartJobsPage() { + const router = useRouter(); + const { jobName } = router.query; + + if (jobName && typeof jobName === 'string') { + return ; + } + + return
Invalid arguments provided
; +} From 668d49dbb36bc7ec4cc0f1070cecb275387d7030 Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 14:47:34 +0100 Subject: [PATCH 07/12] Add custom routine pages based on workflow pages --- pages/custom/routines/[routineId].tsx | 18 ++++ pages/custom/routines/index.tsx | 108 ++++++++++++++++++++ pages/custom/routines/new/[routineName].tsx | 16 +++ translations/en-GB.json | 10 +- 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 pages/custom/routines/[routineId].tsx create mode 100644 pages/custom/routines/index.tsx create mode 100644 pages/custom/routines/new/[routineName].tsx diff --git a/pages/custom/routines/[routineId].tsx b/pages/custom/routines/[routineId].tsx new file mode 100644 index 0000000..2c4538a --- /dev/null +++ b/pages/custom/routines/[routineId].tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { useRouter } from 'next/router'; + +import { WfoProcessDetailPage } from '@orchestrator-ui/orchestrator-ui-components'; + +export default function RoutineDetailPage() { + const router = useRouter(); + const { routineId } = router.query; + + return ( + <> + {(routineId && typeof routineId === 'string' && ( + + )) ||
Invalid workflowId
} + + ); +} diff --git a/pages/custom/routines/index.tsx b/pages/custom/routines/index.tsx new file mode 100644 index 0000000..44cced8 --- /dev/null +++ b/pages/custom/routines/index.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; +import { useRouter } from 'next/router'; +import { StringParam, useQueryParam, withDefault } from 'use-query-params'; + +import { EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { + ACTIVE_PROCESSES_LIST_TABLE_LOCAL_STORAGE_KEY, + COMPLETED_PROCESSES_LIST_TABLE_LOCAL_STORAGE_KEY, + DEFAULT_PAGE_SIZE, + PATH_WORKFLOWS, + SortOrder, + WfoFilterTabs, + WfoProcessesList, + WfoWorkflowsListTabType, + defaultWorkflowsListTabs, + getWorkflowsListTabTypeFromString, + useDataDisplayParams, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import type { + ProcessListItem, + StoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; + +export default function RoutineListPage() { + const router = useRouter(); + const t = useTranslations('routines'); + const [activeTab, setActiveTab] = useQueryParam( + 'activeTab', + withDefault(StringParam, WfoWorkflowsListTabType.ACTIVE), + ); + + const [tableDefaults, setTableDefaults] = + useState>(); + + const selectedWorkflowsListTab = + getWorkflowsListTabTypeFromString(activeTab); + + const localStorageKey = + selectedWorkflowsListTab === WfoWorkflowsListTabType.ACTIVE + ? ACTIVE_PROCESSES_LIST_TABLE_LOCAL_STORAGE_KEY + : COMPLETED_PROCESSES_LIST_TABLE_LOCAL_STORAGE_KEY; + + const getStoredTableConfig = + useStoredTableConfig(localStorageKey); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: 'lastModifiedAt', + order: SortOrder.DESC, + }, + }); + + const handleChangeWorkflowsListTab = ( + updatedWorkflowsListTab: WfoWorkflowsListTabType, + ) => { + setActiveTab(updatedWorkflowsListTab); + setDataDisplayParam('pageIndex', 0); + }; + + const alwaysOnFilters = defaultWorkflowsListTabs.find( + ({ id }) => id === selectedWorkflowsListTab, + )?.alwaysOnFilters; + + if (!selectedWorkflowsListTab) { + router.replace('/custom/routines'); + return null; + } + + return ( + <> + + + + + + + + + + + ); +} diff --git a/pages/custom/routines/new/[routineName].tsx b/pages/custom/routines/new/[routineName].tsx new file mode 100644 index 0000000..f3070e8 --- /dev/null +++ b/pages/custom/routines/new/[routineName].tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { useRouter } from 'next/router'; + +import { WfoStartProcessPage } from '@orchestrator-ui/orchestrator-ui-components'; + +export default function StartRoutinePage() { + const router = useRouter(); + const { routineName } = router.query; + + if (routineName && typeof routineName === 'string') { + return ; + } + + return
Invalid arguments provided
; +} diff --git a/translations/en-GB.json b/translations/en-GB.json index d7e8617..6455a9e 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -14,5 +14,13 @@ "jobsList": { "rerunAll": "Rerun all jobs", "rerunAllQuestion": "Sure?", - "jobName": "Job name" } + "jobName": "Job name" + }, + "routines": { + "title": "Routines", + "tabs": { + "active": "Started", + "completed": "Stopped" + } + } } From 8ab5dbc66d06313b53c123f6033dc2c90936f621 Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 14:48:06 +0100 Subject: [PATCH 08/12] Custom notification detail page --- .../custom/notifications/[notificationId].tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 pages/custom/notifications/[notificationId].tsx diff --git a/pages/custom/notifications/[notificationId].tsx b/pages/custom/notifications/[notificationId].tsx new file mode 100644 index 0000000..51368a9 --- /dev/null +++ b/pages/custom/notifications/[notificationId].tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { useRouter } from 'next/router'; + +import { + TreeProvider, + WfoSubscription, +} from '@orchestrator-ui/orchestrator-ui-components'; + +export default function WfoNotificationDetailPage() { + const router = useRouter(); + const { notificationId } = router.query; + + return ( + (notificationId && ( + + + + )) || <> + ); +} From 0a66123be379f05928a0ee0a1730ebf7ee5dd2da Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 16:20:18 +0100 Subject: [PATCH 09/12] Custom configuration pages based on metadata pages --- .../custom/configuration/configurationTabs.ts | 24 ++ .../custom-fixed-version-types.tsx | 211 ++++++++++++++ .../configuration/custom-product-blocks.tsx | 264 ++++++++++++++++++ .../custom/configuration/custom-products.tsx | 0 .../custom/configuration/custom-routines.tsx | 0 translations/en-GB.json | 26 ++ 6 files changed, 525 insertions(+) create mode 100644 pages/custom/configuration/configurationTabs.ts create mode 100644 pages/custom/configuration/custom-fixed-version-types.tsx create mode 100644 pages/custom/configuration/custom-product-blocks.tsx create mode 100644 pages/custom/configuration/custom-products.tsx create mode 100644 pages/custom/configuration/custom-routines.tsx diff --git a/pages/custom/configuration/configurationTabs.ts b/pages/custom/configuration/configurationTabs.ts new file mode 100644 index 0000000..d709e76 --- /dev/null +++ b/pages/custom/configuration/configurationTabs.ts @@ -0,0 +1,24 @@ +import type { MetaDataTab } from '@orchestrator-ui/orchestrator-ui-components'; + +export const configurationTabs: MetaDataTab[] = [ + { + id: 1, + translationKey: 'fixedVersionTypes', + path: '/custom/configuration/custom-fixed-version-types', + }, + { + id: 2, + translationKey: 'products', + path: '/custom/configuration/custom-products', + }, + { + id: 3, + translationKey: 'productBlocks', + path: '/custom/configuration/custom-product-blocks', + }, + { + id: 4, + translationKey: 'routines', + path: '/custom/configuration/custom-routines', + }, +]; diff --git a/pages/custom/configuration/custom-fixed-version-types.tsx b/pages/custom/configuration/custom-fixed-version-types.tsx new file mode 100644 index 0000000..45c5c49 --- /dev/null +++ b/pages/custom/configuration/custom-fixed-version-types.tsx @@ -0,0 +1,211 @@ +import React, { useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; + +import { EuiBadgeGroup } from '@elastic/eui'; +import type { Pagination } from '@elastic/eui/src/components'; +import { + DEFAULT_PAGE_SIZE, + DEFAULT_PAGE_SIZES, + METADATA_RESOURCE_TYPES_TABLE_LOCAL_STORAGE_KEY, + WfoProductBlockBadge, + WfoTableWithFilter, + getDataSortHandler, + getPageChangeHandler, + getQueryStringHandler, +} from '@orchestrator-ui/orchestrator-ui-components'; +import type { + StoredTableConfig, + WfoDataSorting, + WfoTableColumns, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useDataDisplayParams, + useShowToastMessage, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useGetResourceTypesQuery, + useLazyGetResourceTypesQuery, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + BadgeType, + GraphqlQueryVariables, + ResourceTypeDefinition, + SortOrder, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { getQueryVariablesForExport } from '@orchestrator-ui/orchestrator-ui-components'; +import { + csvDownloadHandler, + getCsvFileNameWithDate, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoFirstPartUUID } from '@orchestrator-ui/orchestrator-ui-components'; +import { mapSortableAndFilterableValuesToTableColumnConfig } from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoMetadataPageLayout } from '@orchestrator-ui/orchestrator-ui-components'; + +import { configurationTabs } from './configurationTabs'; + +export const RESOURCE_TYPE_FIELD_ID = 'resourceTypeId'; +export const RESOURCE_TYPE_FIELD_TYPE = 'resourceType'; +export const RESOURCE_TYPE_FIELD_DESCRIPTION = 'description'; +export const RESOURCE_TYPE_FIELD_PRODUCT_BLOCKS = 'productBlocks'; + +export default function WfoResourceTypesPage() { + const t = useTranslations('configurations.fixedVersionTypes'); + const tError = useTranslations('errors'); + const { showToastMessage } = useShowToastMessage(); + const [tableDefaults, setTableDefaults] = + useState>(); + + const getStoredTableConfig = useStoredTableConfig( + METADATA_RESOURCE_TYPES_TABLE_LOCAL_STORAGE_KEY, + ); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // the fist time before the useEffect has populated the tableDefaults. Better is to create a way for + // the query to wait for the values to be available + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: RESOURCE_TYPE_FIELD_TYPE, + order: SortOrder.ASC, + }, + }); + + const tableColumns: WfoTableColumns = { + resourceTypeId: { + field: RESOURCE_TYPE_FIELD_ID, + name: t('resourceId'), + width: '90', + render: (value) => , + renderDetails: (value) => value, + }, + resourceType: { + field: RESOURCE_TYPE_FIELD_TYPE, + name: t('type'), + width: '200', + render: (value) => ( + + {value} + + ), + }, + description: { + field: RESOURCE_TYPE_FIELD_DESCRIPTION, + name: t('description'), + }, + productBlocks: { + field: RESOURCE_TYPE_FIELD_PRODUCT_BLOCKS, + name: t('usedInProductBlocks'), + render: (productBlocks) => ( + <> + {productBlocks.map((productBlock, index) => ( + + {productBlock.name} + + ))} + + ), + renderDetails: (productBlocks) => ( + + {productBlocks.map((productBlock, index) => ( + + {productBlock.name} + + ))} + + ), + }, + }; + + const { pageSize, pageIndex, sortBy, queryString } = dataDisplayParams; + const graphqlQueryVariables: GraphqlQueryVariables = + { + first: pageSize, + after: pageIndex * pageSize, + sortBy: sortBy, + query: queryString || undefined, + }; + const { data, isFetching, isError } = useGetResourceTypesQuery( + graphqlQueryVariables, + ); + + const [getResourceTypesTrigger, { isFetching: isFetchingCsv }] = + useLazyGetResourceTypesQuery(); + + const getResourceTypesForExport = () => + getResourceTypesTrigger( + getQueryVariablesForExport(graphqlQueryVariables), + ).unwrap(); + + const dataSorting: WfoDataSorting = { + field: sortBy?.field ?? RESOURCE_TYPE_FIELD_TYPE, + sortOrder: sortBy?.order ?? SortOrder.ASC, + }; + + const { totalItems, sortFields, filterFields } = data?.pageInfo || {}; + + const pagination: Pagination = { + pageSize: pageSize, + pageIndex: pageIndex, + pageSizeOptions: DEFAULT_PAGE_SIZES, + totalItemCount: totalItems ? totalItems : 0, + }; + + return ( + + + data={data ? data.resourceTypes : []} + tableColumns={mapSortableAndFilterableValuesToTableColumnConfig( + tableColumns, + sortFields, + filterFields, + )} + dataSorting={dataSorting} + defaultHiddenColumns={tableDefaults?.hiddenColumns} + onUpdateDataSort={getDataSortHandler( + setDataDisplayParam, + )} + onUpdatePage={getPageChangeHandler( + setDataDisplayParam, + )} + onUpdateQueryString={getQueryStringHandler( + setDataDisplayParam, + )} + pagination={pagination} + isLoading={isFetching} + hasError={isError} + queryString={queryString} + localStorageKey={ + METADATA_RESOURCE_TYPES_TABLE_LOCAL_STORAGE_KEY + } + onExportData={csvDownloadHandler( + getResourceTypesForExport, + (data) => data.resourceTypes, + (data) => data.pageInfo, + Object.keys(tableColumns), + getCsvFileNameWithDate('ResourceTypes'), + showToastMessage, + tError, + )} + exportDataIsLoading={isFetchingCsv} + /> + + ); +} diff --git a/pages/custom/configuration/custom-product-blocks.tsx b/pages/custom/configuration/custom-product-blocks.tsx new file mode 100644 index 0000000..b66971f --- /dev/null +++ b/pages/custom/configuration/custom-product-blocks.tsx @@ -0,0 +1,264 @@ +import React, { useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; + +import { EuiBadgeGroup } from '@elastic/eui'; +import type { Pagination } from '@elastic/eui/src/components'; +import { + DEFAULT_PAGE_SIZE, + DEFAULT_PAGE_SIZES, + METADATA_PRODUCT_BLOCKS_TABLE_LOCAL_STORAGE_KEY, + StoredTableConfig, + WfoDataSorting, + WfoDateTime, + WfoFirstPartUUID, + WfoProductBlockBadge, + WfoProductStatusBadge, + WfoTableColumns, + WfoTableWithFilter, + getDataSortHandler, + getPageChangeHandler, + getQueryStringHandler, + mapSortableAndFilterableValuesToTableColumnConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useDataDisplayParams, + useShowToastMessage, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useGetProductBlocksQuery, + useLazyGetProductBlocksQuery, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + BadgeType, + GraphqlQueryVariables, + ProductBlockDefinition, + SortOrder, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + csvDownloadHandler, + getCsvFileNameWithDate, + getQueryVariablesForExport, + parseDateToLocaleDateTimeString, + parseIsoString, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoMetadataPageLayout } from '@orchestrator-ui/orchestrator-ui-components'; + +import { configurationTabs } from './configurationTabs'; + +const PRODUCT_BLOCK_FIELD_ID: keyof ProductBlockDefinition = 'productBlockId'; +const PRODUCT_BLOCK_FIELD_NAME: keyof ProductBlockDefinition = 'name'; + +const PRODUCT_BLOCK_FIELD_TAG: keyof ProductBlockDefinition = 'tag'; +const PRODUCT_BLOCK_FIELD_DESCRIPTION: keyof ProductBlockDefinition = + 'description'; +const PRODUCT_BLOCK_FIELD_STATUS: keyof ProductBlockDefinition = 'status'; +const PRODUCT_BLOCK_FIELD_CREATED_AT: keyof ProductBlockDefinition = + 'createdAt'; +const PRODUCT_BLOCK_FIELD_END_DATE: keyof ProductBlockDefinition = 'endDate'; +const PRODUCT_BLOCK_FIELD_RESOURCE_TYPES: keyof ProductBlockDefinition = + 'resourceTypes'; +const PRODUCT_BLOCK_FIELD_PRODUCT_BLOCKS: keyof ProductBlockDefinition = + 'dependsOn'; + +export default function WfoProductBlocksPage() { + const t = useTranslations('configurations.productBlocks'); + const tError = useTranslations('errors'); + const { showToastMessage } = useShowToastMessage(); + + const [tableDefaults, setTableDefaults] = + useState>(); + + const getStoredTableConfig = useStoredTableConfig( + METADATA_PRODUCT_BLOCKS_TABLE_LOCAL_STORAGE_KEY, + ); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // the fist time before the useEffect has populated the tableDefaults. Better is to create a way for + // the query to wait for the values to be available + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: PRODUCT_BLOCK_FIELD_NAME, + order: SortOrder.ASC, + }, + }); + + const tableColumns: WfoTableColumns = { + productBlockId: { + field: PRODUCT_BLOCK_FIELD_ID, + name: t('id'), + width: '90', + render: (value) => , + renderDetails: (value) => value, + }, + name: { + field: PRODUCT_BLOCK_FIELD_NAME, + name: t('name'), + width: '200', + render: (name) => ( + + {name} + + ), + }, + tag: { + field: PRODUCT_BLOCK_FIELD_TAG, + name: t('tag'), + width: '140', + }, + description: { + field: PRODUCT_BLOCK_FIELD_DESCRIPTION, + name: t('description'), + width: '400', + }, + status: { + field: PRODUCT_BLOCK_FIELD_STATUS, + name: t('status'), + width: '90', + render: (value) => , + }, + dependsOn: { + field: PRODUCT_BLOCK_FIELD_PRODUCT_BLOCKS, + name: t('dependingProductBlocks'), + render: (dependsOn) => ( + <> + {dependsOn.map((productBlock, index) => ( + + {productBlock.name} + + ))} + + ), + }, + resourceTypes: { + field: PRODUCT_BLOCK_FIELD_RESOURCE_TYPES, + name: t('resourceTypes'), + render: (resourceTypes) => ( + <> + {resourceTypes.map((resourceType, index) => ( + + {resourceType.resourceType} + + ))} + + ), + renderDetails: (resourceTypes) => ( + + {resourceTypes.map((resourceType, index) => ( + + {resourceType.resourceType} + + ))} + + ), + }, + createdAt: { + field: PRODUCT_BLOCK_FIELD_CREATED_AT, + name: t('createdAt'), + render: (date) => , + renderDetails: parseIsoString(parseDateToLocaleDateTimeString), + clipboardText: parseIsoString(parseDateToLocaleDateTimeString), + }, + endDate: { + field: PRODUCT_BLOCK_FIELD_END_DATE, + name: t('endDate'), + render: (date) => , + renderDetails: parseIsoString(parseDateToLocaleDateTimeString), + clipboardText: parseIsoString(parseDateToLocaleDateTimeString), + }, + }; + + const { pageSize, pageIndex, sortBy, queryString } = dataDisplayParams; + const graphqlQueryVariables: GraphqlQueryVariables = + { + first: pageSize, + after: pageIndex * pageSize, + sortBy: sortBy, + query: queryString || undefined, + }; + const { data, isFetching, isError } = useGetProductBlocksQuery( + graphqlQueryVariables, + ); + const [getProductBlocksTrigger, { isFetching: isFetchingCsv }] = + useLazyGetProductBlocksQuery(); + const getProductBlocksForExport = () => + getProductBlocksTrigger( + getQueryVariablesForExport(graphqlQueryVariables), + ).unwrap(); + + const dataSorting: WfoDataSorting = { + field: sortBy?.field ?? PRODUCT_BLOCK_FIELD_NAME, + sortOrder: sortBy?.order ?? SortOrder.ASC, + }; + + const { totalItems, sortFields, filterFields } = data?.pageInfo ?? {}; + + const pagination: Pagination = { + pageSize: pageSize, + pageIndex: pageIndex, + pageSizeOptions: DEFAULT_PAGE_SIZES, + totalItemCount: totalItems ? totalItems : 0, + }; + + return ( + + + data={data?.productBlocks || []} + tableColumns={mapSortableAndFilterableValuesToTableColumnConfig( + tableColumns, + sortFields, + filterFields, + )} + dataSorting={dataSorting} + defaultHiddenColumns={tableDefaults?.hiddenColumns} + onUpdateDataSort={getDataSortHandler( + setDataDisplayParam, + )} + onUpdatePage={getPageChangeHandler( + setDataDisplayParam, + )} + onUpdateQueryString={getQueryStringHandler( + setDataDisplayParam, + )} + pagination={pagination} + isLoading={isFetching} + hasError={isError} + queryString={queryString} + localStorageKey={ + METADATA_PRODUCT_BLOCKS_TABLE_LOCAL_STORAGE_KEY + } + onExportData={csvDownloadHandler( + getProductBlocksForExport, + (data) => data.productBlocks, + (data) => data.pageInfo, + Object.keys(tableColumns), + getCsvFileNameWithDate('ProductBlocks'), + showToastMessage, + tError, + )} + exportDataIsLoading={isFetchingCsv} + /> + + ); +} diff --git a/pages/custom/configuration/custom-products.tsx b/pages/custom/configuration/custom-products.tsx new file mode 100644 index 0000000..e69de29 diff --git a/pages/custom/configuration/custom-routines.tsx b/pages/custom/configuration/custom-routines.tsx new file mode 100644 index 0000000..e69de29 diff --git a/translations/en-GB.json b/translations/en-GB.json index 6455a9e..a7b4969 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -22,5 +22,31 @@ "active": "Started", "completed": "Stopped" } + }, + "configurations": { + "fixedVersionTypes": { + "resourceId": "Fixed version ID", + "type": "Fixed version type", + "description": "Description", + "usedInProductBlocks": "Used where" + }, + "productBlocks": { + "id": "ID", + "name": "Name", + "tag": "Tag", + "description": "Description", + "status": "Status", + "dependingProductBlocks": "Depends on", + "resourceTypes": "Fixed inputs" + } + }, + "metadata": { + "title": "Configurations", + "tabs": { + "fixedVersionTypes": "Fixed version types", + "products": "Products", + "productBlocks": "Product blocks", + "routines": "Routines" + } } } From 34c91674b9bf1d34c6a28f42b72a969ba75aa3cc Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 16:36:46 +0100 Subject: [PATCH 10/12] Custom metadata->configuration pages --- .../custom/configuration/custom-products.tsx | 245 ++++++++++++++++++ .../custom/configuration/custom-routines.tsx | 243 +++++++++++++++++ translations/en-GB.json | 18 ++ 3 files changed, 506 insertions(+) diff --git a/pages/custom/configuration/custom-products.tsx b/pages/custom/configuration/custom-products.tsx index e69de29..bea346d 100644 --- a/pages/custom/configuration/custom-products.tsx +++ b/pages/custom/configuration/custom-products.tsx @@ -0,0 +1,245 @@ +import React, { useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; + +import type { Pagination } from '@elastic/eui/src/components'; +import type { + WfoDataSorting, + WfoTableColumns, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + DEFAULT_PAGE_SIZE, + DEFAULT_PAGE_SIZES, + METADATA_PRODUCT_TABLE_LOCAL_STORAGE_KEY, + StoredTableConfig, + WfoDateTime, + WfoProductBlockBadge, + WfoProductStatusBadge, + WfoTableWithFilter, + getDataSortHandler, + getPageChangeHandler, + getQueryStringHandler, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoFirstPartUUID } from '@orchestrator-ui/orchestrator-ui-components'; +import { mapSortableAndFilterableValuesToTableColumnConfig } from '@orchestrator-ui/orchestrator-ui-components'; +import { + useDataDisplayParams, + useShowToastMessage, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useGetProductsQuery, + useLazyGetProductsQuery, +} from '@orchestrator-ui/orchestrator-ui-components'; +import type { + GraphqlQueryVariables, + ProductDefinition, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + BadgeType, + SortOrder, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + getQueryVariablesForExport, + parseDateToLocaleDateTimeString, + parseIsoString, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + csvDownloadHandler, + getCsvFileNameWithDate, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoMetadataPageLayout } from '@orchestrator-ui/orchestrator-ui-components'; + +import { configurationTabs } from './configurationTabs'; + +const PRODUCT_FIELD_PRODUCT_ID = 'productId'; +const PRODUCT_FIELD_NAME = 'name'; +const PRODUCT_FIELD_DESCRIPTION = 'description'; +const PRODUCT_FIELD_TAG = 'tag'; +const PRODUCT_FIELD_PRODUCT_TYPE = 'productType'; +const PRODUCT_FIELD_STATUS = 'status'; +const PRODUCT_FIELD_PRODUCT_BLOCKS = 'productBlocks'; +const PRODUCT_FIELD_FIXED_INPUTS = 'fixedInputs'; +const PRODUCT_FIELD_CREATED_AT = 'createdAt'; + +export default function WfoProductsPage() { + const t = useTranslations('configurations.products'); + const tError = useTranslations('errors'); + const { showToastMessage } = useShowToastMessage(); + const [tableDefaults, setTableDefaults] = + useState>(); + + const getStoredTableConfig = useStoredTableConfig( + METADATA_PRODUCT_TABLE_LOCAL_STORAGE_KEY, + ); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // the fist time before the useEffect has populated the tableDefaults. Better is to create a way for + // the query to wait for the values to be available + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: PRODUCT_FIELD_NAME, + order: SortOrder.ASC, + }, + }); + + const tableColumns: WfoTableColumns = { + productId: { + field: PRODUCT_FIELD_PRODUCT_ID, + name: t('id'), + width: '90', + render: (value) => , + renderDetails: (value) => value, + }, + name: { + field: PRODUCT_FIELD_NAME, + name: t('name'), + width: '200', + }, + tag: { + field: PRODUCT_FIELD_TAG, + name: t('tag'), + width: '120', + render: (value) => ( + + {value} + + ), + }, + description: { + field: PRODUCT_FIELD_DESCRIPTION, + name: t('description'), + width: '400', + }, + productType: { + field: PRODUCT_FIELD_PRODUCT_TYPE, + name: t('productType'), + }, + status: { + field: PRODUCT_FIELD_STATUS, + name: t('status'), + width: '90', + render: (value) => , + }, + fixedInputs: { + field: PRODUCT_FIELD_FIXED_INPUTS, + name: t('fixedInputs'), + render: (fixedInputs) => ( + <> + {fixedInputs.map((fixedInput, index) => ( + + {`${fixedInput.name}: ${fixedInput.value}`} + + ))} + + ), + }, + productBlocks: { + field: PRODUCT_FIELD_PRODUCT_BLOCKS, + name: t('productBlocks'), + render: (productBlocks) => ( + <> + {productBlocks.map((block, index) => ( + + {block.name} + + ))} + + ), + }, + createdAt: { + field: PRODUCT_FIELD_CREATED_AT, + name: t('createdAt'), + render: (date) => , + renderDetails: parseIsoString(parseDateToLocaleDateTimeString), + clipboardText: parseIsoString(parseDateToLocaleDateTimeString), + }, + }; + + const { pageSize, pageIndex, sortBy, queryString } = dataDisplayParams; + const graphqlQueryVariables: GraphqlQueryVariables = { + first: pageSize, + after: pageIndex * pageSize, + sortBy: sortBy, + query: queryString || undefined, + }; + const { data, isFetching, isError } = useGetProductsQuery( + graphqlQueryVariables, + ); + const [getProductsTrigger, { isFetching: isFetchingCsv }] = + useLazyGetProductsQuery(); + const getProductsForExport = () => + getProductsTrigger( + getQueryVariablesForExport(graphqlQueryVariables), + ).unwrap(); + + const { totalItems, sortFields, filterFields } = data?.pageInfo ?? {}; + + const pagination: Pagination = { + pageSize: pageSize, + pageIndex: pageIndex, + pageSizeOptions: DEFAULT_PAGE_SIZES, + totalItemCount: totalItems ? totalItems : 0, + }; + + const dataSorting: WfoDataSorting = { + field: sortBy?.field ?? PRODUCT_FIELD_NAME, + sortOrder: sortBy?.order ?? SortOrder.ASC, + }; + + return ( + + + data={data?.products ?? []} + tableColumns={mapSortableAndFilterableValuesToTableColumnConfig( + tableColumns, + sortFields, + filterFields, + )} + dataSorting={dataSorting} + defaultHiddenColumns={tableDefaults?.hiddenColumns} + onUpdateDataSort={getDataSortHandler( + setDataDisplayParam, + )} + onUpdatePage={getPageChangeHandler( + setDataDisplayParam, + )} + onUpdateQueryString={getQueryStringHandler( + setDataDisplayParam, + )} + pagination={pagination} + isLoading={isFetching} + hasError={isError} + queryString={queryString} + localStorageKey={METADATA_PRODUCT_TABLE_LOCAL_STORAGE_KEY} + onExportData={csvDownloadHandler( + getProductsForExport, + (data) => data?.products ?? [], + (data) => data?.pageInfo || {}, + Object.keys(tableColumns), + getCsvFileNameWithDate('Products'), + showToastMessage, + tError, + )} + exportDataIsLoading={isFetchingCsv} + /> + + ); +} diff --git a/pages/custom/configuration/custom-routines.tsx b/pages/custom/configuration/custom-routines.tsx index e69de29..55c442b 100644 --- a/pages/custom/configuration/custom-routines.tsx +++ b/pages/custom/configuration/custom-routines.tsx @@ -0,0 +1,243 @@ +import React, { useEffect, useState } from 'react'; + +import { useTranslations } from 'next-intl'; + +import { EuiBadgeGroup } from '@elastic/eui'; +import type { Pagination } from '@elastic/eui/src/components'; +import type { + WfoDataSorting, + WfoTableColumns, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + DEFAULT_PAGE_SIZE, + DEFAULT_PAGE_SIZES, + METADATA_WORKFLOWS_TABLE_LOCAL_STORAGE_KEY, + StoredTableConfig, + WfoDateTime, + WfoProductBlockBadge, + WfoTableWithFilter, + WfoWorkflowTargetBadge, + getDataSortHandler, + getPageChangeHandler, + getQueryStringHandler, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useDataDisplayParams, + useShowToastMessage, + useStoredTableConfig, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + useGetWorkflowsQuery, + useLazyGetWorkflowsQuery, +} from '@orchestrator-ui/orchestrator-ui-components'; +import type { + GraphqlQueryVariables, + WorkflowDefinition, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + BadgeType, + SortOrder, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + getQueryVariablesForExport, + onlyUnique, + parseDateToLocaleDateTimeString, + parseIsoString, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { + csvDownloadHandler, + getCsvFileNameWithDate, +} from '@orchestrator-ui/orchestrator-ui-components'; +import { mapSortableAndFilterableValuesToTableColumnConfig } from '@orchestrator-ui/orchestrator-ui-components'; +import { WfoMetadataPageLayout } from '@orchestrator-ui/orchestrator-ui-components'; +import { + graphQlWorkflowListMapper, + mapWorkflowDefinitionToWorkflowListItem, +} from '@orchestrator-ui/orchestrator-ui-components'; + +import { configurationTabs } from './configurationTabs'; + +export type WorkflowListItem = Pick< + WorkflowDefinition, + 'name' | 'description' | 'target' | 'createdAt' +> & { + productTags: string[]; +}; + +export default function RoutinesPage() { + const t = useTranslations('configurations.routines'); + const tError = useTranslations('errors'); + const { showToastMessage } = useShowToastMessage(); + + const [tableDefaults, setTableDefaults] = + useState>(); + + const getStoredTableConfig = useStoredTableConfig( + METADATA_WORKFLOWS_TABLE_LOCAL_STORAGE_KEY, + ); + + useEffect(() => { + const storedConfig = getStoredTableConfig(); + + if (storedConfig) { + setTableDefaults(storedConfig); + } + }, [getStoredTableConfig]); + + const { dataDisplayParams, setDataDisplayParam } = + useDataDisplayParams({ + // TODO: Improvement: A default pageSize value is set to avoid a graphql error when the query is executed + // the fist time before the useEffect has populated the tableDefaults. Better is to create a way for + // the query to wait for the values to be available + // https://github.com/workfloworchestrator/orchestrator-ui/issues/261 + pageSize: tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE, + sortBy: { + field: 'name', + order: SortOrder.ASC, + }, + }); + + const tableColumns: WfoTableColumns = { + name: { + field: 'name', + name: t('name'), + width: '200', + render: (name) => ( + + {name} + + ), + }, + description: { + field: 'description', + name: t('description'), + width: '300', + }, + target: { + field: 'target', + name: t('target'), + width: '90', + render: (target) => , + }, + productTags: { + field: 'productTags', + name: t('productTags'), + render: (productTags) => ( + <> + {productTags + ?.filter(onlyUnique) + .map((productTag, index) => ( + + {productTag} + + ))} + + ), + renderDetails: (productTags) => ( + + {productTags + ?.filter(onlyUnique) + .map((productTag, index) => ( + + {productTag} + + ))} + + ), + }, + createdAt: { + field: 'createdAt', + name: t('createdAt'), + width: '110', + render: (date) => , + renderDetails: parseIsoString(parseDateToLocaleDateTimeString), + clipboardText: parseIsoString(parseDateToLocaleDateTimeString), + }, + }; + + const { pageSize, pageIndex, sortBy, queryString } = dataDisplayParams; + + const workflowListQueryVariables: GraphqlQueryVariables = + { + first: pageSize, + after: pageIndex * pageSize, + sortBy: graphQlWorkflowListMapper(sortBy), + query: queryString || undefined, + }; + const { data, isFetching, isError } = useGetWorkflowsQuery( + workflowListQueryVariables, + ); + + const [getWorkflowsTrigger, { isFetching: isFetchingCsv }] = + useLazyGetWorkflowsQuery(); + + const getWorkflowsForExport = () => + getWorkflowsTrigger( + getQueryVariablesForExport(workflowListQueryVariables), + ).unwrap(); + + const dataSorting: WfoDataSorting = { + field: sortBy?.field ?? 'name', + sortOrder: sortBy?.order ?? SortOrder.ASC, + }; + + const { totalItems, sortFields, filterFields } = data?.pageInfo || {}; + + const pagination: Pagination = { + pageSize: pageSize, + pageIndex: pageIndex, + pageSizeOptions: DEFAULT_PAGE_SIZES, + totalItemCount: totalItems ? totalItems : 0, + }; + + return ( + + + data={ + data + ? mapWorkflowDefinitionToWorkflowListItem( + data.workflows, + ) + : [] + } + tableColumns={mapSortableAndFilterableValuesToTableColumnConfig( + tableColumns, + sortFields, + filterFields, + )} + dataSorting={dataSorting} + defaultHiddenColumns={tableDefaults?.hiddenColumns} + onUpdateDataSort={getDataSortHandler( + setDataDisplayParam, + )} + onUpdatePage={getPageChangeHandler( + setDataDisplayParam, + )} + onUpdateQueryString={getQueryStringHandler( + setDataDisplayParam, + )} + pagination={pagination} + isLoading={isFetching} + hasError={isError} + queryString={queryString} + localStorageKey={METADATA_WORKFLOWS_TABLE_LOCAL_STORAGE_KEY} + onExportData={csvDownloadHandler( + getWorkflowsForExport, + (data) => data.workflows, + (data) => data.pageInfo, + Object.keys(tableColumns), + getCsvFileNameWithDate('Workflows'), + showToastMessage, + tError, + )} + exportDataIsLoading={isFetchingCsv} + /> + + ); +} diff --git a/translations/en-GB.json b/translations/en-GB.json index a7b4969..d5e24b7 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -38,6 +38,24 @@ "status": "Status", "dependingProductBlocks": "Depends on", "resourceTypes": "Fixed inputs" + }, + "products": { + "id": "ID", + "name": "Name", + "tag": "Tag", + "description": "Description", + "productType": "Product type", + "status": "Status", + "fixedInputs": "Fixed inputs", + "productBlocks": "Product blocks", + "createdAt": "Created at" + }, + "routines": { + "name": "Routine", + "description": "Description", + "target": "Goal", + "createdAt": "Created at", + "productTags": "Product tags" } }, "metadata": { From f369e49d20a4d6b4a8d5971b3c36bcb786e95388 Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 16:56:15 +0100 Subject: [PATCH 11/12] Custom menu items --- pages/_app.tsx | 139 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 24 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index a0aa0a9..37404c7 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -76,31 +76,122 @@ function CustomApp({ } }, []); - const getMenuItems = ( - defaultMenuItems: EuiSideNavItemType[], - ): EuiSideNavItemType[] => { - const subscriptionsMenuItemIndex = defaultMenuItems.findIndex( - (menuItem) => menuItem.id === '4', - ); - - const notificationsMenuItem = { - name: 'Notifications', - id: '4', - isSelected: router.pathname === '/custom/notifications', - href: '/custom/notifications', - onClick: (e) => { - e.preventDefault(); - router.push('/custom/notifications'); + const getMenuItems = (): EuiSideNavItemType[] => { + return [ + { + name: 'Start', + id: '2', + isSelected: router.pathname === '/', + onClick: (e) => { + e.preventDefault(); + router.push('/'); + }, }, - }; - - defaultMenuItems.splice( - subscriptionsMenuItemIndex, - 1, - notificationsMenuItem, - ); - - return defaultMenuItems; + { + name: 'Routines', + id: '3', + isSelected: router.pathname === '/custom/routines', + href: '/custom/routines', + onClick: (e) => { + e.preventDefault(); + router.push('/custom/routines'); + }, + }, + { + name: 'Notifications', + id: '4', + isSelected: router.pathname === '/custom/notifications', + href: '/custom/notifications', + onClick: (e) => { + e.preventDefault(); + router.push('/custom/notifications'); + }, + }, + { + name: 'Configuration', + id: '5', + href: '/custom/configuration/custom-fixed-version-types', + onClick: () => { + router.push( + '/custom/configuration/custom-fixed-version-types', + ); + }, + items: [ + { + name: 'Fixed Version Types', + id: '5.1', + isSelected: + router.pathname === + '/custom/configuration/custom-fixed-version-types', + onClick: (e) => { + e.preventDefault(); + router.push( + '/custom/configuration/custom-fixed-version-types', + ); + }, + }, + { + name: 'Product blocks', + id: '5.2', + isSelected: + router.pathname === + '/custom/configuration/custom-product-blocks', + onClick: (e) => { + e.preventDefault(); + router.push( + '/custom/configuration/custom-product-blocks', + ); + }, + }, + { + name: 'Products', + id: '5.3', + isSelected: + router.pathname === + '/custom/configuration/custom-products', + onClick: (e) => { + e.preventDefault(); + router.push( + '/custom/configuration/custom-products', + ); + }, + }, + { + name: 'Routines', + id: '5.4', + isSelected: + router.pathname === + '/custom/configuration/custom-routines', + onClick: (e) => { + e.preventDefault(); + router.push( + '/custom/configuration/custom-routines', + ); + }, + }, + ], + }, + { + name: 'Jobs', + isSelected: router.pathname === '/custom/jobs', + id: '6', + onClick: (e) => { + e.preventDefault(); + router.push('/custom/jobs'); + }, + href: '/custom/jobs', + }, + { + name: 'Settings', + isSelected: router.pathname === '/custom/custom-settings', + id: '7', + onClick: (e) => { + e.preventDefault(); + router.push('/custom/custom-settings'); + }, + href: '/custom/custom-settings', + }, + ]; }; return ( From c90fca23f99357c170131b4186ce8bc18716d33f Mon Sep 17 00:00:00 2001 From: Ruben van Leeuwen Date: Mon, 25 Mar 2024 17:04:45 +0100 Subject: [PATCH 12/12] TS Fixes --- pages/_app.tsx | 1 - .../configuration/custom-product-blocks.tsx | 23 ++++++++----------- pages/custom/routines/index.tsx | 1 - 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 37404c7..c0734ae 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,7 +4,6 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import { QueryClientConfig } from 'react-query/types/core/types'; -import { get } from 'http'; import { SessionProvider } from 'next-auth/react'; import { NextAdapter } from 'next-query-params'; import App, { AppContext, AppInitialProps, AppProps } from 'next/app'; diff --git a/pages/custom/configuration/custom-product-blocks.tsx b/pages/custom/configuration/custom-product-blocks.tsx index b66971f..70e7de9 100644 --- a/pages/custom/configuration/custom-product-blocks.tsx +++ b/pages/custom/configuration/custom-product-blocks.tsx @@ -47,20 +47,15 @@ import { WfoMetadataPageLayout } from '@orchestrator-ui/orchestrator-ui-componen import { configurationTabs } from './configurationTabs'; -const PRODUCT_BLOCK_FIELD_ID: keyof ProductBlockDefinition = 'productBlockId'; -const PRODUCT_BLOCK_FIELD_NAME: keyof ProductBlockDefinition = 'name'; - -const PRODUCT_BLOCK_FIELD_TAG: keyof ProductBlockDefinition = 'tag'; -const PRODUCT_BLOCK_FIELD_DESCRIPTION: keyof ProductBlockDefinition = - 'description'; -const PRODUCT_BLOCK_FIELD_STATUS: keyof ProductBlockDefinition = 'status'; -const PRODUCT_BLOCK_FIELD_CREATED_AT: keyof ProductBlockDefinition = - 'createdAt'; -const PRODUCT_BLOCK_FIELD_END_DATE: keyof ProductBlockDefinition = 'endDate'; -const PRODUCT_BLOCK_FIELD_RESOURCE_TYPES: keyof ProductBlockDefinition = - 'resourceTypes'; -const PRODUCT_BLOCK_FIELD_PRODUCT_BLOCKS: keyof ProductBlockDefinition = - 'dependsOn'; +const PRODUCT_BLOCK_FIELD_ID = 'productBlockId'; +const PRODUCT_BLOCK_FIELD_NAME = 'name'; +const PRODUCT_BLOCK_FIELD_TAG = 'tag'; +const PRODUCT_BLOCK_FIELD_DESCRIPTION = 'description'; +const PRODUCT_BLOCK_FIELD_STATUS = 'status'; +const PRODUCT_BLOCK_FIELD_CREATED_AT = 'createdAt'; +const PRODUCT_BLOCK_FIELD_END_DATE = 'endDate'; +const PRODUCT_BLOCK_FIELD_RESOURCE_TYPES = 'resourceTypes'; +const PRODUCT_BLOCK_FIELD_PRODUCT_BLOCKS = 'dependsOn'; export default function WfoProductBlocksPage() { const t = useTranslations('configurations.productBlocks'); diff --git a/pages/custom/routines/index.tsx b/pages/custom/routines/index.tsx index 44cced8..4264583 100644 --- a/pages/custom/routines/index.tsx +++ b/pages/custom/routines/index.tsx @@ -9,7 +9,6 @@ import { ACTIVE_PROCESSES_LIST_TABLE_LOCAL_STORAGE_KEY, COMPLETED_PROCESSES_LIST_TABLE_LOCAL_STORAGE_KEY, DEFAULT_PAGE_SIZE, - PATH_WORKFLOWS, SortOrder, WfoFilterTabs, WfoProcessesList,