Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2.22.0 fixes #325

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion frontend/src/concepts/pipelines/apiHooks/usePipelines.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { FetchState } from '~/utilities/useFetchState';
import { FetchState, NotReadyError } from '~/utilities/useFetchState';
import { PipelineKFv2 } from '~/concepts/pipelines/kfTypes';
import usePipelineQuery from '~/concepts/pipelines/apiHooks/usePipelineQuery';
import { PipelineListPaged, PipelineOptions } from '~/concepts/pipelines/types';
Expand All @@ -21,4 +21,26 @@ const usePipelines = (
);
};

export const useSafePipelines = (
options?: PipelineOptions,
refreshRate?: number,
): FetchState<PipelineListPaged<PipelineKFv2>> => {
const { api, pipelinesServer, apiAvailable } = usePipelinesAPI();
return usePipelineQuery<PipelineKFv2>(
React.useCallback(
(opts, params) => {
if (!apiAvailable || !pipelinesServer.compatible) {
throw new NotReadyError('Pipelines is not available');
}
return api
.listPipelines(opts, params)
.then((result) => ({ ...result, items: result.pipelines }));
},
[api, apiAvailable, pipelinesServer.compatible],
),
options,
refreshRate,
);
};

export default usePipelines;
3 changes: 2 additions & 1 deletion frontend/src/concepts/pipelines/context/PipelinesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export const PipelineContextProvider = conditionalArea<PipelineContextProviderPr
);

const routeHost = routeLoaded && pipelineAPIRouteHost ? pipelineAPIRouteHost : null;
const hostPath = namespace && dspaName ? `/api/service/pipelines/${namespace}/${dspaName}` : null;
const hostPath =
routeLoaded && namespace && dspaName ? `/api/service/pipelines/${namespace}/${dspaName}` : null;
useManageElyraSecret(namespace, pipelineNamespaceCR, routeHost);

const refreshState = React.useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,111 +13,26 @@ import {
Text,
TextContent,
} from '@patternfly/react-core';
import { ExclamationCircleIcon } from '@patternfly/react-icons';
import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext';
import usePipelineRunJobs from '~/concepts/pipelines/apiHooks/usePipelineRunJobs';
import {
usePipelineActiveRuns,
usePipelineArchivedRuns,
} from '~/concepts/pipelines/apiHooks/usePipelineRuns';
import { CreatePipelineServerButton, usePipelinesAPI } from '~/concepts/pipelines/context';
import useExperiments from '~/concepts/pipelines/apiHooks/useExperiments';
import usePipelines from '~/concepts/pipelines/apiHooks/usePipelines';
import { useSafePipelines } from '~/concepts/pipelines/apiHooks/usePipelines';
import EnsureAPIAvailability from '~/concepts/pipelines/EnsureAPIAvailability';
import EnsureCompatiblePipelineServer from '~/concepts/pipelines/EnsureCompatiblePipelineServer';
import ImportPipelineButton from '~/concepts/pipelines/content/import/ImportPipelineButton';
import { ProjectObjectType, SectionType } from '~/concepts/design/utils';
import OverviewCard from '~/pages/projects/screens/detail/overview/components/OverviewCard';
import PipelinesCardItems from '~/pages/projects/screens/detail/overview/trainModels/PipelinesCardItems';
import MetricsContents from './MetricsContents';
import PipelinesOverviewCard from './PipelinesOverviewCard';
import PipelinesCardMetrics from './PipelinesCardMetrics';

const PipelinesCard: React.FC = () => {
const { pipelinesServer } = usePipelinesAPI();
const {
currentProject,
notebooks: { data: notebooks, loaded: notebooksLoaded, error: notebooksError },
} = React.useContext(ProjectDetailsContext);

const [{ items: pipelines, totalSize: pipelinesCount }, pipelinesLoaded, pipelinesError] =
usePipelines({
pageSize: 5,
});
const [{ totalSize: activeRunsCount }, activeRunsLoaded, activeRunsError] = usePipelineActiveRuns(
{
pageSize: 1,
},
);
const [{ totalSize: archivedRunsCount }, archivedRunsLoaded, archivedRunsError] =
usePipelineArchivedRuns({
pageSize: 1,
});
const [{ totalSize: scheduledCount }, scheduledLoaded, scheduledError] = usePipelineRunJobs({
pageSize: 1,
});
const [{ totalSize: experimentsCount }, experimentsLoaded, experimentsError] = useExperiments({
const [{ totalSize: pipelinesCount }] = useSafePipelines({
pageSize: 1,
});

const loaded =
!pipelinesServer.initializing &&
pipelinesLoaded &&
activeRunsLoaded &&
archivedRunsLoaded &&
scheduledLoaded &&
experimentsLoaded;

const loadError =
pipelinesError || activeRunsError || archivedRunsError || scheduledError || experimentsError;

const triggeredCount = activeRunsCount + archivedRunsCount;

const statistics = React.useMemo(
() => [
{
count: pipelinesCount,
text: pipelinesCount === 1 ? 'Pipeline' : 'Pipelines',
},
{
count: scheduledCount,
text: scheduledCount === 1 ? 'Schedule' : 'Schedules',
},
{
count: triggeredCount,
text: triggeredCount === 1 ? 'Run' : 'Runs',
},
{
count: experimentsCount,
text: experimentsCount === 1 ? 'Experiment' : 'Experiments',
},
],
[experimentsCount, pipelinesCount, scheduledCount, triggeredCount],
);

const renderContent = () => {
if (loadError) {
return (
<EmptyState variant="xs">
<EmptyStateHeader
icon={
<EmptyStateIcon
icon={() => (
<ExclamationCircleIcon
style={{
color: 'var(--pf-v5-global--danger-color--100)',
width: '32px',
height: '32px',
}}
/>
)}
/>
}
headingLevel="h3"
/>
<EmptyStateBody>{loadError.message}</EmptyStateBody>
</EmptyState>
);
}

if (!loaded) {
if (pipelinesServer.initializing) {
return (
<EmptyState variant="xs">
<EmptyStateHeader
Expand All @@ -128,7 +43,6 @@ const PipelinesCard: React.FC = () => {
</EmptyState>
);
}

if (!pipelinesServer.installed) {
return (
<>
Expand Down Expand Up @@ -167,61 +81,16 @@ const PipelinesCard: React.FC = () => {
}

return (
<EnsureCompatiblePipelineServer>
{pipelinesCount ? (
<>
<MetricsContents
title="Pipelines"
createButton={<ImportPipelineButton variant="link" />}
createText="Import pipeline"
statistics={statistics}
listItems={
<PipelinesCardItems
pipelines={pipelines}
loaded={loaded}
error={loadError}
totalCount={pipelinesCount}
currentProject={currentProject}
/>
}
/>
</>
) : (
<>
<CardBody>
<TextContent>
<Text component="small">
Pipelines are platforms for building and deploying portable and scalable
machine-learning (ML) workflows. You can import a pipeline or create one in a
workbench.
</Text>
</TextContent>
</CardBody>
<CardFooter>
<ImportPipelineButton variant="link" isInline />
</CardFooter>
</>
)}
</EnsureCompatiblePipelineServer>
<EnsureAPIAvailability>
<EnsureCompatiblePipelineServer>
<PipelinesCardMetrics />
</EnsureCompatiblePipelineServer>
</EnsureAPIAvailability>
);
};

return (
<OverviewCard
id="Pipelines"
objectType={ProjectObjectType.pipeline}
sectionType={pipelinesCount ? SectionType.training : SectionType.organize}
title="Pipelines"
popoverHeaderContent={pipelinesCount ? 'About pipelines' : undefined}
popoverBodyContent={
!pipelinesCount
? 'Pipelines are platforms for building and deploying portable and scalable machine-learning (ML) workflows. You can import a pipeline or create one in a workbench.'
: undefined
}
data-testid="section-pipelines"
>
{renderContent()}
</OverviewCard>
<PipelinesOverviewCard pipelinesCount={pipelinesCount}>{renderContent()}</PipelinesOverviewCard>
);
};

Expand Down
Loading
Loading