Skip to content

Commit

Permalink
[ui] Code location: Graphs page
Browse files Browse the repository at this point in the history
  • Loading branch information
hellendag committed Aug 20, 2024
1 parent 98b39b8 commit a1d22c7
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Box} from '@dagster-io/ui-components';
import {useMemo} from 'react';
import {Switch} from 'react-router-dom';

import {CodeLocationGraphsList} from './CodeLocationGraphsList';
import {CodeLocationOpsView} from './CodeLocationOpsView';
import {CodeLocationSearchableList, SearchableListRow} from './CodeLocationSearchableList';
import {Route} from '../app/Route';
Expand Down Expand Up @@ -32,6 +33,9 @@ export const CodeLocationDefinitionsMain = ({repoAddress, repository}: Props) =>
<Route path="/locations/:repoPath/resources">
<CodeLocationResourcesList repoAddress={repoAddress} repository={repository} />
</Route>
<Route path="/locations/:repoPath/graphs">
<CodeLocationGraphsList repoAddress={repoAddress} />
</Route>
<Route path="/locations/:repoPath/ops">
<CodeLocationOpsView repoAddress={repoAddress} />
</Route>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {useQuery} from '@apollo/client';
import {Box, NonIdealState, SpinnerWithText} from '@dagster-io/ui-components';
import {useMemo} from 'react';

import {CodeLocationSearchableList, SearchableListRow} from './CodeLocationSearchableList';
import {PythonErrorInfo} from '../app/PythonErrorInfo';
import {useBlockTraceOnQueryResult} from '../performance/TraceContext';
import {WORSKPACE_GRAPHS_QUERY} from '../workspace/WorkspaceGraphsQuery';
import {extractGraphsForRepo} from '../workspace/extractGraphsForRepo';
import {repoAddressAsHumanString} from '../workspace/repoAddressAsString';
import {repoAddressToSelector} from '../workspace/repoAddressToSelector';
import {RepoAddress} from '../workspace/types';
import {
WorkspaceGraphsQuery,
WorkspaceGraphsQueryVariables,
} from '../workspace/types/WorkspaceGraphsQuery.types';
import {workspacePathFromAddress} from '../workspace/workspacePath';

interface Props {
repoAddress: RepoAddress;
}

export const CodeLocationGraphsList = (props: Props) => {
const {repoAddress} = props;

const selector = repoAddressToSelector(repoAddress);

const queryResult = useQuery<WorkspaceGraphsQuery, WorkspaceGraphsQueryVariables>(
WORSKPACE_GRAPHS_QUERY,
{variables: {selector}},
);

useBlockTraceOnQueryResult(queryResult, 'WorkspaceGraphsQuery');
const {data, loading} = queryResult;

const graphs = useMemo(() => {
const repo = data?.repositoryOrError;
if (!repo || repo.__typename !== 'Repository') {
return [];
}

return extractGraphsForRepo(repo);
}, [data]);

const repoString = repoAddressAsHumanString(repoAddress);

if (loading) {
return (
<Box padding={64} flex={{direction: 'row', justifyContent: 'center'}}>
<SpinnerWithText label="Loading graphs…" />
</Box>
);
}

if (!data || !data.repositoryOrError) {
return (
<Box padding={64}>
<NonIdealState
icon="graph"
title="An unexpected error occurred"
description={`An error occurred while loading graphs for ${repoString}`}
/>
</Box>
);
}

if (data.repositoryOrError.__typename === 'PythonError') {
return (
<Box padding={64}>
<PythonErrorInfo error={data.repositoryOrError} />
</Box>
);
}

if (data.repositoryOrError.__typename === 'RepositoryNotFoundError') {
return (
<Box padding={64}>
<NonIdealState
icon="op"
title="Repository not found"
description={`The repository ${repoString} could not be found in this workspace.`}
/>
</Box>
);
}

if (!graphs.length) {
return (
<Box padding={64}>
<NonIdealState
icon="graph"
title="No graphs found"
description={`The repository ${repoString} does not contain any graphs.`}
/>
</Box>
);
}

return (
<CodeLocationSearchableList
items={graphs}
placeholder="Search graphs by name…"
nameFilter={(graph, value) => graph.name.toLowerCase().includes(value)}
renderRow={(graph) => (
<SearchableListRow
iconName="graph"
label={graph.name}
path={workspacePathFromAddress(repoAddress, graph.path)}
/>
)}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import faker from 'faker';

import {
buildCompositeSolidDefinition,
buildDagsterLibraryVersion,
buildPipeline,
buildRepository,
Expand All @@ -16,8 +17,13 @@ import {
import {OPS_ROOT_QUERY} from '../../ops/OpsRoot';
import {OpsRootQuery, OpsRootQueryVariables} from '../../ops/types/OpsRoot.types';
import {buildQueryMock} from '../../testing/mocking';
import {WORSKPACE_GRAPHS_QUERY} from '../../workspace/WorkspaceGraphsQuery';
import {repoAddressToSelector} from '../../workspace/repoAddressToSelector';
import {RepoAddress} from '../../workspace/types';
import {
WorkspaceGraphsQuery,
WorkspaceGraphsQueryVariables,
} from '../../workspace/types/WorkspaceGraphsQuery.types';

export const buildEmptyWorkspaceLocationEntry = (config: {time: number; locationName: string}) => {
const {time, locationName} = config;
Expand Down Expand Up @@ -107,5 +113,40 @@ export const buildSampleOpsRootQuery = (config: {repoAddress: RepoAddress; opCou
}),
},
delay: 2000,
maxUsageCount: 20,
});
};

export const buildSampleRepositoryGraphsQuery = (config: {
repoAddress: RepoAddress;
jobCount: number;
opCount: number;
}) => {
const {repoAddress, jobCount, opCount} = config;
return buildQueryMock<WorkspaceGraphsQuery, WorkspaceGraphsQueryVariables>({
query: WORSKPACE_GRAPHS_QUERY,
variables: {
selector: repoAddressToSelector(repoAddress),
},
data: {
repositoryOrError: buildRepository({
usedSolids: new Array(opCount).fill(null).map(() => {
return buildUsedSolid({
definition: buildCompositeSolidDefinition({
name: faker.random.words(2).split(' ').join('-').toLowerCase(),
}),
});
}),
pipelines: new Array(jobCount).fill(null).map(() => {
return buildPipeline({
id: faker.datatype.uuid(),
graphName: faker.random.words(2).split(' ').join('-').toLowerCase(),
isJob: true,
});
}),
}),
},
delay: 2000,
maxUsageCount: 100,
});
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {MockedProvider} from '@apollo/client/testing';
import {Meta} from '@storybook/react';
import {useMemo} from 'react';
import {MemoryRouter} from 'react-router';
import {RecoilRoot} from 'recoil';

Expand All @@ -9,6 +10,7 @@ import {CodeLocationDefinitionsRoot} from '../CodeLocationDefinitionsRoot';
import {
buildSampleOpsRootQuery,
buildSampleRepository,
buildSampleRepositoryGraphsQuery,
} from '../__fixtures__/CodeLocationPages.fixtures';

// eslint-disable-next-line import/no-default-export
Expand All @@ -31,10 +33,18 @@ export const Default = () => {
resourceCount: 100,
});

const mocks = useMemo(
() => [
buildSampleOpsRootQuery({repoAddress, opCount: 500}),
buildSampleRepositoryGraphsQuery({repoAddress, jobCount: 500, opCount: 500}),
],
[repoAddress],
);

return (
<RecoilRoot>
<MemoryRouter initialEntries={[workspacePathFromAddress(repoAddress, '/jobs')]}>
<MockedProvider mocks={[buildSampleOpsRootQuery({repoAddress, opCount: 500})]}>
<MockedProvider mocks={mocks}>
<div style={{height: '500px', overflow: 'hidden'}}>
<CodeLocationDefinitionsRoot
repoAddress={buildRepoAddress(repoName, locationName)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {gql} from '@apollo/client';

import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorFragment';

const REPOSITORY_GRAPHS_FRAGMENT = gql`
fragment RepositoryGraphsFragment on Repository {
id
usedSolids {
definition {
... on CompositeSolidDefinition {
id
name
description
}
}
invocations {
pipeline {
id
name
}
solidHandle {
handleID
}
}
}
pipelines {
id
name
isJob
graphName
}
}
`;

export const WORSKPACE_GRAPHS_QUERY = gql`
query WorkspaceGraphsQuery($selector: RepositorySelector!) {
repositoryOrError(repositorySelector: $selector) {
... on Repository {
id
...RepositoryGraphsFragment
}
...PythonErrorFragment
}
}
${REPOSITORY_GRAPHS_FRAGMENT}
${PYTHON_ERROR_FRAGMENT}
`;
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import {gql, useQuery} from '@apollo/client';
import {useQuery} from '@apollo/client';
import {Box, Colors, NonIdealState, Spinner, TextInput} from '@dagster-io/ui-components';
import {useMemo} from 'react';

import {Graph, VirtualizedGraphTable} from './VirtualizedGraphTable';
import {VirtualizedGraphTable} from './VirtualizedGraphTable';
import {WORSKPACE_GRAPHS_QUERY} from './WorkspaceGraphsQuery';
import {WorkspaceHeader} from './WorkspaceHeader';
import {extractGraphsForRepo} from './extractGraphsForRepo';
import {repoAddressAsHumanString} from './repoAddressAsString';
import {repoAddressToSelector} from './repoAddressToSelector';
import {RepoAddress} from './types';
import {
WorkspaceGraphsQuery,
WorkspaceGraphsQueryVariables,
} from './types/WorkspaceGraphsRoot.types';
import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorFragment';
} from './types/WorkspaceGraphsQuery.types';
import {FIFTEEN_SECONDS, useQueryRefreshAtInterval} from '../app/QueryRefresh';
import {useTrackPageView} from '../app/analytics';
import {isHiddenAssetGroupJob} from '../asset-graph/Utils';
import {useDocumentTitle} from '../hooks/useDocumentTitle';
import {useQueryPersistedState} from '../hooks/useQueryPersistedState';
import {useBlockTraceOnQueryResult} from '../performance/TraceContext';
Expand Down Expand Up @@ -52,32 +52,7 @@ export const WorkspaceGraphsRoot = ({repoAddress}: {repoAddress: RepoAddress}) =
return [];
}

const jobGraphNames = new Set<string>(
repo.pipelines
.filter((p) => p.isJob && !isHiddenAssetGroupJob(p.name))
.map((p) => p.graphName),
);

const items: Graph[] = Array.from(jobGraphNames).map((graphName) => ({
name: graphName,
path: `/graphs/${graphName}`,
description: null,
}));

repo.usedSolids.forEach((s) => {
if (s.definition.__typename === 'CompositeSolidDefinition') {
const invocation = s.invocations[0];
if (invocation) {
items.push({
name: s.definition.name,
path: `/graphs/${invocation.pipeline.name}/${invocation.solidHandle.handleID}/`,
description: s.definition.description,
});
}
}
});

return items.sort((a, b) => a.name.localeCompare(b.name));
return extractGraphsForRepo(repo);
}, [data]);

const filteredBySearch = useMemo(() => {
Expand Down Expand Up @@ -150,40 +125,3 @@ export const WorkspaceGraphsRoot = ({repoAddress}: {repoAddress: RepoAddress}) =
</Box>
);
};

const WORSKPACE_GRAPHS_QUERY = gql`
query WorkspaceGraphsQuery($selector: RepositorySelector!) {
repositoryOrError(repositorySelector: $selector) {
... on Repository {
id
usedSolids {
definition {
... on CompositeSolidDefinition {
id
name
description
}
}
invocations {
pipeline {
id
name
}
solidHandle {
handleID
}
}
}
pipelines {
id
name
isJob
graphName
}
}
...PythonErrorFragment
}
}
${PYTHON_ERROR_FRAGMENT}
`;
Loading

0 comments on commit a1d22c7

Please sign in to comment.