Skip to content

Commit

Permalink
feat: project domain default page (#352)
Browse files Browse the repository at this point in the history
* feat: project domain default page
* feat: add tests, address feedback
* feat: always render numbers block

Signed-off-by: Olga Nad <[email protected]>
  • Loading branch information
olga-union authored Apr 5, 2022
1 parent cb3c69b commit aaded00
Show file tree
Hide file tree
Showing 17 changed files with 480 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@date-io/moment": "1.3.9",
"@flyteorg/flyteidl": "0.23.1",
"@flyteorg/flyteidl": "0.24.11",
"@material-ui/core": "^4.0.0",
"@material-ui/icons": "^4.0.0",
"@material-ui/pickers": "^3.2.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ export const ExecutionDetailsAppBarContent: React.FC<{
const onCloseRelaunch = () => setShowRelaunchForm(false);
const fromExecutionNav = new URLSearchParams(history.location.search).get('fromExecutionNav');
const backLink = fromExecutionNav
? Routes.ProjectDetails.sections.executions.makeUrl(project, domain)
? Routes.ProjectDetails.sections.dashboard.makeUrl(project, domain)
: originalBackLink;
const {
recoverExecution,
recoverState: { isLoading: recovering, error, data: recoveredId },
recoverState: { isLoading: recovering, data: recoveredId },
} = useRecoverExecutionState();

React.useEffect(() => {
Expand Down
26 changes: 13 additions & 13 deletions src/components/Navigation/ProjectNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SvgIconProps } from '@material-ui/core/SvgIcon';
import ChevronRight from '@material-ui/icons/ChevronRight';
import DeviceHub from '@material-ui/icons/DeviceHub';
import LinearScale from '@material-ui/icons/LinearScale';
import TrendingFlat from '@material-ui/icons/TrendingFlat';
import Dashboard from '@material-ui/icons/Dashboard';
import classnames from 'classnames';
import { useCommonStyles } from 'components/common/styles';
import { withRouteParams } from 'components/common/withRouteParams';
Expand Down Expand Up @@ -72,46 +72,46 @@ const ProjectNavigationImpl: React.FC<ProjectNavigationRouteParams> = ({

const routes: ProjectRoute[] = [
{
icon: DeviceHub,
icon: Dashboard,
isActive: (match, location) => {
const finalMatch = match
? match
: matchPath(location.pathname, {
path: Routes.WorkflowDetails.path,
path: Routes.ProjectDashboard.path,
exact: false,
});
return !!finalMatch;
},
path: Routes.ProjectDetails.sections.workflows.makeUrl(project.value.id, domainId),
text: 'Workflows',
path: Routes.ProjectDetails.sections.dashboard.makeUrl(project.value.id, domainId),
text: 'Project Dashboard',
},
{
icon: LinearScale,
icon: DeviceHub,
isActive: (match, location) => {
const finalMatch = match
? match
: matchPath(location.pathname, {
path: Routes.TaskDetails.path,
path: Routes.WorkflowDetails.path,
exact: false,
});
return !!finalMatch;
},
path: Routes.ProjectDetails.sections.tasks.makeUrl(project.value.id, domainId),
text: 'Tasks',
path: Routes.ProjectDetails.sections.workflows.makeUrl(project.value.id, domainId),
text: 'Workflows',
},
{
icon: TrendingFlat,
icon: LinearScale,
isActive: (match, location) => {
const finalMatch = match
? match
: matchPath(location.pathname, {
path: Routes.ProjectExecutions.path,
path: Routes.TaskDetails.path,
exact: false,
});
return !!finalMatch;
},
path: Routes.ProjectDetails.sections.executions.makeUrl(project.value.id, domainId),
text: 'Executions',
path: Routes.ProjectDetails.sections.tasks.makeUrl(project.value.id, domainId),
text: 'Tasks',
},
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import Typography from '@material-ui/core/Typography';
import { makeStyles, Theme } from '@material-ui/core/styles';
import * as React from 'react';
import { Typography } from '@material-ui/core';
import { useTaskNameList, useWorkflowNameList } from 'components/hooks/useNamedEntity';
import { useWorkflowExecutions } from 'components/hooks/useWorkflowExecutions';
import { WaitForQuery } from 'components/common/WaitForQuery';
import { useInfiniteQuery, useQuery, useQueryClient } from 'react-query';
import { Admin } from 'flyteidl';
import { DomainSettingsSection } from 'components/common/DomainSettingsSection';
import { getCacheKey } from 'components/Cache/utils';
import { ErrorBoundary } from 'components/common/ErrorBoundary';
import { LargeLoadingSpinner } from 'components/common/LoadingSpinner';
Expand All @@ -11,24 +18,30 @@ import { makeWorkflowExecutionListQuery } from 'components/Executions/workflowEx
import { SortDirection } from 'models/AdminEntity/types';
import { executionSortFields } from 'models/Execution/constants';
import { Execution } from 'models/Execution/types';
import * as React from 'react';
import { useInfiniteQuery } from 'react-query';
import { BarChart } from 'components/common/BarChart';
import {
getExecutionTimeData,
getStartExecutionTime,
} from 'components/Entities/EntityExecutionsBarChart';
import classNames from 'classnames';
import { useWorkflowExecutions } from 'components/hooks/useWorkflowExecutions';
import { useExecutionShowArchivedState } from 'components/Executions/filters/useExecutionArchiveState';
import { useOnlyMyExecutionsFilterState } from 'components/Executions/filters/useOnlyMyExecutionsFilterState';
import { WaitForData } from 'components/common/WaitForData';
import { history } from 'routes/history';
import { Routes } from 'routes/routes';
import { compact } from 'lodash';
import { getProjectDomainAttributes } from 'models/Project/api';
import t from './strings';
import { failedToLoadExecutionsString } from './constants';

const useStyles = makeStyles((theme: Theme) => ({
projectStats: {
paddingTop: theme.spacing(7),
paddingBottom: theme.spacing(7),
display: 'flex',
justifyContent: 'space-evenly',
alignItems: 'center',
},
container: {
display: 'flex',
flex: '1 1 auto',
Expand All @@ -48,7 +61,8 @@ const useStyles = makeStyles((theme: Theme) => ({
paddingTop: theme.spacing(1),
},
}));
export interface ProjectExecutionsProps {

export interface ProjectDashboardProps {
projectId: string;
domainId: string;
}
Expand All @@ -58,8 +72,7 @@ const defaultSort = {
direction: SortDirection.DESCENDING,
};

/** A listing of all executions across a project/domain combination. */
export const ProjectExecutions: React.FC<ProjectExecutionsProps> = ({
export const ProjectDashboard: React.FC<ProjectDashboardProps> = ({
domainId: domain,
projectId: project,
}) => {
Expand Down Expand Up @@ -90,18 +103,18 @@ export const ProjectExecutions: React.FC<ProjectExecutionsProps> = ({
[domain, project, allFilters],
);

const query = useInfiniteQuery({
const executionsQuery = useInfiniteQuery({
...makeWorkflowExecutionListQuery({ domain, project }, config),
});

// useInfiniteQuery returns pages of items, but the table would like a single
// flat list.
const executions = React.useMemo(
() =>
query.data?.pages
? query.data.pages.reduce<Execution[]>((acc, { data }) => acc.concat(data), [])
executionsQuery.data?.pages
? executionsQuery.data.pages.reduce<Execution[]>((acc, { data }) => acc.concat(data), [])
: [],
[query.data?.pages],
[executionsQuery.data?.pages],
);

const handleBarChartItemClick = React.useCallback((item) => {
Expand All @@ -118,50 +131,89 @@ export const ProjectExecutions: React.FC<ProjectExecutionsProps> = ({
},
);

const fetch = React.useCallback(() => query.fetchNextPage(), [query]);
const fetch = React.useCallback(() => executionsQuery.fetchNextPage(), [executionsQuery]);

const { value: workflows } = useWorkflowNameList({ domain, project }, {});
const numberOfWorkflows = workflows.length;
const { value: tasks } = useTaskNameList({ domain, project }, {});
const numberOfTasks = tasks.length;

const content = query.isLoadingError ? (
<DataError error={query.error} errorTitle={failedToLoadExecutionsString} retry={fetch} />
) : query.isLoading ? (
const queryClient = useQueryClient();

const projectDomainAttributesQuery = useQuery<Admin.ProjectDomainAttributesGetResponse, Error>({
queryKey: ['projectDomainAttributes', project, domain],
queryFn: async () => {
const projectDomainAtributes = await getProjectDomainAttributes({ domain, project });
queryClient.setQueryData(
['projectDomainAttributes', project, domain],
projectDomainAtributes,
);
return projectDomainAtributes;
},
staleTime: Infinity,
});

const content = executionsQuery.isLoadingError ? (
<DataError
error={executionsQuery.error}
errorTitle={failedToLoadExecutionsString}
retry={fetch}
/>
) : executionsQuery.isLoading ? (
<LargeLoadingSpinner />
) : (
<WorkflowExecutionsTable
key={tableKey}
fetch={fetch}
value={executions}
lastError={query.error}
moreItemsAvailable={!!query.hasNextPage}
lastError={executionsQuery.error}
moreItemsAvailable={!!executionsQuery.hasNextPage}
showWorkflowName={true}
isFetching={query.isFetching}
isFetching={executionsQuery.isFetching}
data-testid="workflow-table"
/>
);

const configData =
projectDomainAttributesQuery.data?.attributes?.matchingAttributes?.workflowExecutionConfig ??
undefined;

const renderDomainSettingsSection = () => <DomainSettingsSection configData={configData} />;

return (
<div className={styles.container}>
<Typography className={classNames(styles.header, styles.marginTop)} variant="h6">
Last 100 Executions in the Project
</Typography>
<div className={styles.chartContainer}>
<WaitForData {...last100Executions}>
<BarChart
chartIds={[]}
data={getExecutionTimeData(last100Executions.value)}
startDate={getStartExecutionTime(last100Executions.value)}
onClickItem={handleBarChartItemClick}
/>
</WaitForData>
<div className={styles.projectStats}>
<Typography variant="h5">{t('workflowsTotal', numberOfWorkflows)}</Typography>
<Typography variant="h5">{t('tasksTotal', numberOfTasks)}</Typography>
</div>
<WaitForQuery query={projectDomainAttributesQuery}>
{renderDomainSettingsSection}
</WaitForQuery>
<div className={styles.container}>
<Typography className={classNames(styles.header, styles.marginTop)} variant="h6">
{t('last100ExecutionsTitle')}
</Typography>
<div className={styles.chartContainer}>
<WaitForData {...last100Executions}>
<BarChart
chartIds={[]}
data={getExecutionTimeData(last100Executions.value)}
startDate={getStartExecutionTime(last100Executions.value)}
onClickItem={handleBarChartItemClick}
/>
</WaitForData>
</div>
<Typography className={styles.header} variant="h6">
{t('allExecutionsTitle')}
</Typography>
<ExecutionFilters
{...filtersState}
showArchived={archivedFilter.showArchived}
onArchiveFilterChange={archivedFilter.setShowArchived}
onlyMyExecutionsFilterState={onlyMyExecutionsFilterState}
/>
<ErrorBoundary>{content}</ErrorBoundary>
</div>
<Typography className={styles.header} variant="h6">
All Executions in the Project
</Typography>
<ExecutionFilters
{...filtersState}
showArchived={archivedFilter.showArchived}
onArchiveFilterChange={archivedFilter.setShowArchived}
onlyMyExecutionsFilterState={onlyMyExecutionsFilterState}
/>
<ErrorBoundary>{content}</ErrorBoundary>
</div>
);
};
12 changes: 6 additions & 6 deletions src/components/Project/ProjectDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Project } from 'models/Project/types';
import * as React from 'react';
import { Redirect, Route, Switch } from 'react-router';
import { Routes } from 'routes/routes';
import { ProjectExecutions } from './ProjectExecutions';
import { ProjectDashboard } from './ProjectDashboard';
import { ProjectTasks } from './ProjectTasks';
import { ProjectWorkflows } from './ProjectWorkflows';

Expand All @@ -27,7 +27,7 @@ export interface ProjectDetailsRouteParams {
export type ProjectDetailsProps = ProjectDetailsRouteParams;

const entityTypeToComponent = {
executions: ProjectExecutions,
executions: ProjectDashboard,
tasks: ProjectTasks,
workflows: ProjectWorkflows,
};
Expand Down Expand Up @@ -59,7 +59,7 @@ const ProjectEntitiesByDomain: React.FC<{
);
};

const ProjectExecutionsByDomain: React.FC<{ project: Project }> = ({ project }) => (
const ProjectDashboardByDomain: React.FC<{ project: Project }> = ({ project }) => (
<ProjectEntitiesByDomain project={project} entityType="executions" />
);

Expand All @@ -79,15 +79,15 @@ export const ProjectDetailsContainer: React.FC<ProjectDetailsRouteParams> = ({ p
{() => {
return (
<Switch>
<Route path={Routes.ProjectDetails.sections.dashboard.path}>
<ProjectDashboardByDomain project={project.value} />
</Route>
<Route path={Routes.ProjectDetails.sections.workflows.path}>
<ProjectWorkflowsByDomain project={project.value} />
</Route>
<Route path={Routes.ProjectDetails.sections.tasks.path}>
<ProjectTasksByDomain project={project.value} />
</Route>
<Route path={Routes.ProjectDetails.sections.executions.path}>
<ProjectExecutionsByDomain project={project.value} />
</Route>
<Redirect to={Routes.ProjectDetails.sections.workflows.makeUrl(projectId)} />
</Switch>
);
Expand Down
11 changes: 11 additions & 0 deletions src/components/Project/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createLocalizedString } from 'basics/Locale';

const str = {
allExecutionsTitle: 'All Executions in the Project',
last100ExecutionsTitle: 'Last 100 Executions in the Project',
tasksTotal: (n: number) => `${n} Tasks`,
workflowsTotal: (n: number) => `${n} Workflows`,
};

export { patternKey } from 'basics/Locale';
export default createLocalizedString(str);
Loading

0 comments on commit aaded00

Please sign in to comment.