Skip to content

Commit

Permalink
Add graph view to project overview workloads tab
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 committed Oct 13, 2020
1 parent 913992f commit 79f5f26
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum NamespacedPageVariants {

export interface NamespacedPageProps {
disabled?: boolean;
hideProjects?: boolean;
hideApplications?: boolean;
onNamespaceChange?: (newNamespace: string) => void;
variant?: NamespacedPageVariants;
Expand All @@ -22,12 +23,17 @@ const NamespacedPage: React.FC<NamespacedPageProps> = ({
children,
disabled,
onNamespaceChange,
hideProjects = false,
hideApplications = false,
variant = NamespacedPageVariants.default,
toolbar,
}) => (
<div className="odc-namespaced-page">
<NamespaceBar disabled={disabled} onNamespaceChange={onNamespaceChange}>
<NamespaceBar
disabled={disabled}
onNamespaceChange={onNamespaceChange}
hideProjects={hideProjects}
>
{!hideApplications && <ApplicationSelector disabled={disabled} />}
{toolbar && <div className="odc-namespaced-page__toolbar">{toolbar}</div>}
</NamespaceBar>
Expand Down
117 changes: 73 additions & 44 deletions frontend/packages/dev-console/src/components/topology/TopologyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import * as React from 'react';
import { Helmet } from 'react-helmet';
import { matchPath, match as RMatch, Link, Redirect } from 'react-router-dom';
import { Tooltip, Popover, Button } from '@patternfly/react-core';
import { ListIcon, TopologyIcon, QuestionCircleIcon } from '@patternfly/react-icons';
import { matchPath, match as RMatch, Redirect } from 'react-router-dom';
import { observer } from '@patternfly/react-topology';
import { useQueryParams } from '@console/shared/src';
import { Firehose, removeQueryArgument } from '@console/internal/components/utils';
import {
Firehose,
history,
removeQueryArgument,
setQueryArgument,
} from '@console/internal/components/utils';
import NamespacedPage, { NamespacedPageVariants } from '../NamespacedPage';
import ProjectsExistWrapper from '../ProjectsExistWrapper';
import CreateProjectListPage from '../projects/CreateProjectListPage';
import { TopologyDataRenderer } from './TopologyDataRenderer';
import TopologyShortcuts from './TopologyShortcuts';
import { LAST_TOPOLOGY_VIEW_LOCAL_STORAGE_KEY } from './components/const';
import {
LAST_TOPOLOGY_VIEW_LOCAL_STORAGE_KEY,
LAST_TOPOLOGY_WORKLOADS_VIEW_LOCAL_STORAGE_KEY,
} from './components/const';
import { TOPOLOGY_SEARCH_FILTER_KEY } from './filters';
import DataModelProvider from './data-transforms/DataModelProvider';
import ModelContext, { ExtensibleModel } from './data-transforms/ModelContext';
import { TopologyPageToolbar } from './TopologyPageToolbar';

import './TopologyPage.scss';

export interface TopologyPageProps {
match: RMatch<{
name?: string;
}>;
workloadsPage?: boolean;
}

const setTopologyActiveView = (id: string) => {
Expand All @@ -32,10 +38,17 @@ const getTopologyActiveView = () => {
return localStorage.getItem(LAST_TOPOLOGY_VIEW_LOCAL_STORAGE_KEY);
};

const setTopologyWorkloadsActiveView = (id: string) => {
localStorage.setItem(LAST_TOPOLOGY_WORKLOADS_VIEW_LOCAL_STORAGE_KEY, id);
};

const getTopologyWorkloadsActiveView = () => {
return localStorage.getItem(LAST_TOPOLOGY_WORKLOADS_VIEW_LOCAL_STORAGE_KEY);
};

export const TopologyPageContext: React.FC<TopologyPageProps> = observer(({ match }) => {
const queryParams = useQueryParams();
const namespace = match.params.name;
const dataModelContext = React.useContext<ExtensibleModel>(ModelContext);
const showListView = !!matchPath(match.path, {
path: '*/list',
exact: true,
Expand Down Expand Up @@ -66,6 +79,14 @@ export const TopologyPageContext: React.FC<TopologyPageProps> = observer(({ matc
);
}

const onViewChange = (graphView: boolean) => {
const url = `/topology/${namespace ? `ns/${namespace}` : 'all-namespaces'}/${
graphView ? 'graph' : 'list'
}${queryParams ? `?${queryParams.toString()}` : ''}`;

history.push(url);
};

return (
<>
<Helmet>
Expand All @@ -74,40 +95,7 @@ export const TopologyPageContext: React.FC<TopologyPageProps> = observer(({ matc
<NamespacedPage
variant={showListView ? NamespacedPageVariants.light : NamespacedPageVariants.default}
onNamespaceChange={handleNamespaceChange}
toolbar={
namespace && !dataModelContext.isEmptyModel ? (
<>
{!showListView && namespace && (
<Popover
aria-label="Shortcuts"
bodyContent={TopologyShortcuts}
position="left"
maxWidth="100vw"
>
<Button
type="button"
variant="link"
className="odc-topology__shortcuts-button"
icon={<QuestionCircleIcon />}
data-test-id="topology-view-shortcuts"
>
View shortcuts
</Button>
</Popover>
)}
<Tooltip position="left" content={showListView ? 'Topology View' : 'List View'}>
<Link
className="pf-c-button pf-m-plain odc-topology__view-switcher"
to={`/topology/${namespace ? `ns/${namespace}` : 'all-namespaces'}${
showListView ? '/graph' : '/list'
}${queryParams ? `?${queryParams.toString()}` : ''}`}
>
{showListView ? <TopologyIcon size="md" /> : <ListIcon size="md" />}
</Link>
</Tooltip>
</>
) : null
}
toolbar={<TopologyPageToolbar showGraphView={showGraphView} onViewChange={onViewChange} />}
>
<Firehose resources={[{ kind: 'Project', prop: 'projects', isList: true }]}>
<ProjectsExistWrapper title="Topology">
Expand All @@ -125,11 +113,52 @@ export const TopologyPageContext: React.FC<TopologyPageProps> = observer(({ matc
);
});

export const TopologyPage: React.FC<TopologyPageProps> = ({ match }) => {
type TopologyWorkloadsPageProps = {
match: any;
};

export const TopologyWorkloadsPage: React.FC<TopologyWorkloadsPageProps> = ({ match }) => {
const namespace = match.params.name;
const queryParams = useQueryParams();
const view = queryParams.get('view');
if (!view) {
setQueryArgument('view', getTopologyWorkloadsActiveView() || 'list');
}
const showGraphView = queryParams.get('view') === 'graph';
const onViewChange = (graphView: boolean) => {
if (graphView) {
setQueryArgument('view', 'graph');
} else {
setQueryArgument('view', 'list');
}
};

React.useEffect(() => setTopologyWorkloadsActiveView(showGraphView ? 'graph' : 'list'), [
showGraphView,
]);

return (
<DataModelProvider namespace={namespace}>
<NamespacedPage
variant={showGraphView ? NamespacedPageVariants.default : NamespacedPageVariants.light}
hideProjects
toolbar={<TopologyPageToolbar showGraphView={showGraphView} onViewChange={onViewChange} />}
>
<TopologyDataRenderer showGraphView={showGraphView} />
</NamespacedPage>
</DataModelProvider>
);
};

export const TopologyPage: React.FC<TopologyPageProps> = ({ match, workloadsPage = false }) => {
const namespace = match.params.name;
return (
<DataModelProvider namespace={namespace}>
<TopologyPageContext match={match} />
{workloadsPage ? (
<TopologyWorkloadsPage match={match} />
) : (
<TopologyPageContext match={match} />
)}
</DataModelProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react';
import { observer } from '@patternfly/react-topology';
import { Tooltip, Popover, Button } from '@patternfly/react-core';
import { ListIcon, TopologyIcon, QuestionCircleIcon } from '@patternfly/react-icons';
import TopologyShortcuts from './TopologyShortcuts';
import ModelContext, { ExtensibleModel } from './data-transforms/ModelContext';

interface TopologyPageToolbarProps {
showGraphView: boolean;
onViewChange: (graphView: boolean) => void;
}

export const TopologyPageToolbar: React.FC<TopologyPageToolbarProps> = observer(
({ showGraphView, onViewChange }) => {
const dataModelContext = React.useContext<ExtensibleModel>(ModelContext);
const { namespace, isEmptyModel } = dataModelContext;

if (!namespace || isEmptyModel) {
return null;
}

return (
<>
{showGraphView ? (
<Popover
aria-label="Shortcuts"
bodyContent={TopologyShortcuts}
position="left"
maxWidth="100vw"
>
<Button
type="button"
variant="link"
className="odc-topology__shortcuts-button"
icon={<QuestionCircleIcon />}
data-test-id="topology-view-shortcuts"
>
View shortcuts
</Button>
</Popover>
) : null}
<Tooltip position="left" content={showGraphView ? 'List View' : 'Topology View'}>
<Button
variant="link"
className="pf-m-plain odc-topology__view-switcher"
onClick={() => onViewChange(!showGraphView)}
>
{showGraphView ? <ListIcon size="md" /> : <TopologyIcon size="md" />}
</Button>
</Tooltip>
</>
);
},
);
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { Link } from 'react-router-dom';
import { Tooltip } from '@patternfly/react-core';
import { TopologyPageContext } from '../TopologyPage';
import { TopologyPage, TopologyPageContext, TopologyWorkloadsPage } from '../TopologyPage';
import NamespacedPage from '../../NamespacedPage';
import CreateProjectListPage from '../../projects/CreateProjectListPage';
import { TopologyDataRenderer } from '../TopologyDataRenderer';
Expand Down Expand Up @@ -50,6 +48,16 @@ describe('Topology page tests', () => {
};
});

it('should render TopologyWorkloadsPage page when workloads is true', () => {
const wrapper = shallow(<TopologyPage {...topologyProps} workloadsPage />);
expect(wrapper.find(TopologyWorkloadsPage).exists()).toBe(true);
});

it('should render TopologyPageContext page when workloads is unset', () => {
const wrapper = shallow(<TopologyPage {...topologyProps} />);
expect(wrapper.find(TopologyPageContext).exists()).toBe(true);
});

it('should render topology page', () => {
topologyProps.match.path = '/topology/ns/topology-test/list';
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
Expand All @@ -66,69 +74,4 @@ describe('Topology page tests', () => {
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
expect(wrapper.find(CreateProjectListPage).exists()).toBe(true);
});

it('should render view shortcuts button on topology page toolbar', () => {
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
const namespacesPageWrapper = wrapper.find(NamespacedPage).shallow();
expect(namespacesPageWrapper.find('[data-test-id="topology-view-shortcuts"]').exists()).toBe(
true,
);
});

it('should not render view shortcuts button on topology list page toolbar', () => {
topologyProps.match.path = '/topology/ns/topology-test/list';
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
const namespacesPageWrapper = wrapper.find(NamespacedPage).shallow();
expect(namespacesPageWrapper.find('[data-test-id="topology-view-shortcuts"]').exists()).toBe(
false,
);
});

it('should show the topology icon when on topology list page', () => {
topologyProps.match.path = '/topology/ns/topology-test/list';
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
const namespacesPageWrapper = wrapper.find(NamespacedPage).shallow();
expect(namespacesPageWrapper.find(Tooltip).props().content).toBe('Topology View');
expect(namespacesPageWrapper.find(Link).props().to).toContain(
'/topology/ns/topology-test/graph',
);
});

it('should show the topology list icon when on topology page', () => {
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
const namespacesPageWrapper = wrapper.find(NamespacedPage).shallow();
expect(namespacesPageWrapper.find(Tooltip).props().content).toBe('List View');
expect(namespacesPageWrapper.find(Link).props().to).toContain(
'/topology/ns/topology-test/list',
);
});

it('should not contain view switcher when when no project is selected', () => {
topologyProps.match.params.name = '';
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
const namespacesPageWrapper = wrapper.find(NamespacedPage).shallow();
expect(namespacesPageWrapper.find(Link).exists()).toBe(false);
});
});

describe('Topology page tests', () => {
beforeEach(() => {
spyOn(React, 'useContext').and.returnValue({ isEmptyModel: true });
topologyProps = {
match: {
params: {
name: 'topology-test',
},
isExact: true,
path: '/topology/ns/topology-test/graph',
url: '',
},
};
});

it('should not contain view switcher when no model', () => {
const wrapper = shallow(<TopologyPageContext {...topologyProps} />);
const namespacesPageWrapper = wrapper.find(NamespacedPage).shallow();
expect(namespacesPageWrapper.find(Link).exists()).toBe(false);
});
});

0 comments on commit 79f5f26

Please sign in to comment.