Skip to content

Commit

Permalink
[ui] Hide Jobs item from nav if there are no jobs
Browse files Browse the repository at this point in the history
[INTERNAL_BRANCH=dish/hide-jobs-nav-cloud]
  • Loading branch information
hellendag committed Aug 8, 2024
1 parent 6985c0d commit e37e599
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Box} from '@dagster-io/ui-components';
import {ReactNode} from 'react';
import {ReactElement} from 'react';
import {useHistory} from 'react-router-dom';
import {FeatureFlag} from 'shared/app/FeatureFlags.oss';

Expand All @@ -11,14 +11,15 @@ import {
jobsPathMatcher,
locationPathMatcher,
} from './activePathMatchers';
import {JobStateForNav} from './useJobStateForNav';
import {DeploymentStatusIcon} from '../../nav/DeploymentStatusIcon';
import {featureEnabled} from '../Flags';
import {ShortcutHandler} from '../ShortcutHandler';

export type AppNavLinkType = {
key: string;
path: string;
element: ReactNode;
element: ReactElement;
};

export const AppTopNavLinks = ({links}: {links: AppNavLinkType[]}) => {
Expand All @@ -42,7 +43,13 @@ export const AppTopNavLinks = ({links}: {links: AppNavLinkType[]}) => {
);
};

export const navLinks = () => {
type Config = {
jobState?: JobStateForNav;
};

export const navLinks = (config: Config): AppNavLinkType[] => {
const {jobState = 'unknown'} = config;

const overview = {
key: 'overview',
path: '/overview',
Expand All @@ -63,16 +70,6 @@ export const navLinks = () => {
),
};

const jobs = {
key: 'jobs',
path: '/jobs',
element: (
<TopNavLink to="/jobs" data-cy="AppTopNav_JobsLink" isActive={jobsPathMatcher}>
Jobs
</TopNavLink>
),
};

const assets = {
key: 'assets',
path: '/assets',
Expand All @@ -98,6 +95,19 @@ export const navLinks = () => {
};

if (featureEnabled(FeatureFlag.flagSettingsPage)) {
const jobs =
jobState === 'has-jobs'
? {
key: 'jobs',
path: '/jobs',
element: (
<TopNavLink to="/jobs" data-cy="AppTopNav_JobsLink" isActive={jobsPathMatcher}>
Jobs
</TopNavLink>
),
}
: null;

const deployment = {
key: 'deployment',
path: '/deployment',
Expand All @@ -114,7 +124,10 @@ export const navLinks = () => {
</TopNavLink>
),
};
return [overview, assets, jobs, automation, runs, deployment];

return [overview, runs, assets, jobs, automation, deployment].filter(
(link): link is AppNavLinkType => !!link,
);
}

const deployment = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {memo} from 'react';

import {AppTopNavLinks, navLinks} from './AppTopNavLinks';
import {useJobStateForNav} from './useJobStateForNav';

export const AppTopNavRightOfLogo = memo(() => {
return <AppTopNavLinks links={navLinks()} />;
const jobState = useJobStateForNav();
return <AppTopNavLinks links={navLinks({jobState})} />;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {useMemo} from 'react';

import {isHiddenAssetGroupJob} from '../../asset-graph/Utils';
import {useRepositoryOptions} from '../../workspace/WorkspaceContext';

export type JobStateForNav = 'unknown' | 'has-jobs' | 'no-jobs';

/**
* Determine whether the viewer has any jobs in any of their code locations. We use
* this information to determine whether to show the "Jobs" item in the top navigation
* at all. If there are no jobs, we won't show it.
*/
export const useJobStateForNav = () => {
const {options, loading} = useRepositoryOptions();
return useMemo(() => {
if (loading) {
return 'unknown';
}

const hasJobs = options.some((option) =>
option.repository.pipelines.some((job) => !isHiddenAssetGroupJob(job.name)),
);

return hasJobs ? 'has-jobs' : 'no-jobs';
}, [options, loading]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {__ANONYMOUS_ASSET_JOB_PREFIX} from '../../asset-graph/Utils';
import {
buildPipeline,
buildRepository,
buildRepositoryLocation,
buildWorkspaceLocationEntry,
} from '../../graphql/types';
import {buildWorkspaceMocks} from '../../workspace/__fixtures__/Workspace.fixtures';

export const workspaceWithJob = buildWorkspaceMocks([
buildWorkspaceLocationEntry({
name: 'some_workspace',
locationOrLoadError: buildRepositoryLocation({
name: 'location_with_job',
repositories: [
buildRepository({
name: 'repo_with_job',
pipelines: [
buildPipeline({
name: 'some_job',
isJob: true,
}),
],
}),
],
}),
}),
]);

export const workspaceWithNoJobs = buildWorkspaceMocks([
buildWorkspaceLocationEntry({
name: 'some_workspace',
locationOrLoadError: buildRepositoryLocation({
name: 'location_without_job',
repositories: [
buildRepository({
name: 'repo_without_job',
pipelines: [],
}),
],
}),
}),
]);

export const workspaceWithDunderJob = buildWorkspaceMocks([
buildWorkspaceLocationEntry({
name: 'some_workspace',
locationOrLoadError: buildRepositoryLocation({
name: 'location_without_job',
repositories: [
buildRepository({
name: `${__ANONYMOUS_ASSET_JOB_PREFIX}_pseudo_job`,
pipelines: [],
}),
],
}),
}),
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {MockedProvider} from '@apollo/client/testing';
import {render, screen} from '@testing-library/react';

import {WorkspaceProvider} from '../../workspace/WorkspaceContext';
import {useJobStateForNav} from '../AppTopNav/useJobStateForNav';
import {
workspaceWithDunderJob,
workspaceWithJob,
workspaceWithNoJobs,
} from '../__fixtures__/useJobStateForNav.fixtures';

describe('useJobStateForNav', () => {
const Test = () => {
const value = useJobStateForNav();
return <div>{value}</div>;
};

it('returns `unknown` if still loading, then finds jobs and returns `has-jobs`', async () => {
render(
<MockedProvider mocks={workspaceWithJob}>
<WorkspaceProvider>
<Test />
</WorkspaceProvider>
</MockedProvider>,
);

expect(screen.getByText(/unknown/i)).toBeVisible();

const found = await screen.findByText(/has-jobs/i);
expect(found).toBeVisible();
});

it('returns `no-jobs` if no jobs found after loading', async () => {
render(
<MockedProvider mocks={workspaceWithNoJobs}>
<WorkspaceProvider>
<Test />
</WorkspaceProvider>
</MockedProvider>,
);

expect(screen.getByText(/unknown/i)).toBeVisible();

const found = await screen.findByText(/no-jobs/i);
expect(found).toBeVisible();
});

it('returns `no-jobs` if only dunder job found', async () => {
render(
<MockedProvider mocks={workspaceWithDunderJob}>
<WorkspaceProvider>
<Test />
</WorkspaceProvider>
</MockedProvider>,
);

expect(screen.getByText(/unknown/i)).toBeVisible();

const found = await screen.findByText(/no-jobs/i);
expect(found).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ const useVisibleRepos = (

const getRepositoryOptionHash = (a: DagsterRepoOption) =>
`${a.repository.name}:${a.repositoryLocation.name}`;

export const useRepositoryOptions = () => {
const {allRepos: options, loading} = React.useContext(WorkspaceContext);
return {options, loading};
Expand Down

0 comments on commit e37e599

Please sign in to comment.