Skip to content

Commit

Permalink
Add capability to collapse all grouping in topology view
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 committed Jul 23, 2020
1 parent 77856f4 commit 0df4138
Show file tree
Hide file tree
Showing 21 changed files with 311 additions and 181 deletions.
17 changes: 4 additions & 13 deletions frontend/packages/dev-console/src/components/topology/Topology.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,7 @@ import ConnectedTopologyEdgePanel from './TopologyEdgePanel';
import { layoutFactory, COLA_LAYOUT, COLA_FORCE_LAYOUT } from './layouts/layoutFactory';
import { TYPE_APPLICATION_GROUP, componentFactory } from './components';
import TopologyFilterBar from './filters/TopologyFilterBar';
import {
getTopologySearchQuery,
TOPOLOGY_SEARCH_FILTER_KEY,
useDisplayFilters,
useAppliedDisplayFilters,
} from './filters';
import { getTopologySearchQuery, TOPOLOGY_SEARCH_FILTER_KEY, useDisplayFilters } from './filters';
import TopologyHelmReleasePanel from './helm/TopologyHelmReleasePanel';
import { TYPE_HELM_RELEASE, TYPE_HELM_WORKLOAD } from './helm/components/const';
import { TYPE_OPERATOR_BACKED_SERVICE } from './operators/components/const';
Expand Down Expand Up @@ -128,7 +123,6 @@ const Topology: React.FC<ComponentProps> = ({
const createResourceAccess: string[] = useAddToProjectAccess(namespace);
const [dragHint, setDragHint] = React.useState<string>('');
const filters = useDisplayFilters();
const appliedFilters = useAppliedDisplayFilters();
const [displayFilterers, setDisplayFilterers] = React.useState<TopologyApplyDisplayOptions[]>(
null,
);
Expand Down Expand Up @@ -198,19 +192,16 @@ const Topology: React.FC<ComponentProps> = ({
React.useEffect(() => {
Promise.all(topologyFilterPromises)
.then((res) => {
const updateFilters = [...filters];
const updateFilters = [...filters.displayOptions];
res.forEach((getter) => {
const extFilters = getter();
extFilters.forEach((filter) => {
if (!updateFilters.find((f) => f.id === filter.id)) {
if (appliedFilters[filter.id] !== undefined) {
filter.value = appliedFilters[filter.id];
}
updateFilters.push(filters.find((f) => f.id === filter.id) || filter);
updateFilters.push(filter);
}
});
});
onFiltersChange(updateFilters);
onFiltersChange({ ...filters, displayOptions: updateFilters });
setFiltersLoaded(true);
})
.catch(() => {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,18 @@ describe('topology model ', () => {
expect(newModel.nodes.filter((n) => n.group).length).toBe(2);
expect(newModel.nodes.filter((n) => n.group && n.collapsed).length).toBe(2);
});

it('should flag application groups as collapsed when expand groups is false', () => {
const topologyTransformedData = getTransformedTopologyData();
getFilterById(EXPAND_APPLICATION_GROUPS_FILTER_ID, filters).value = true;
filters.groupsExpanded = false;
const newModel = updateModelFromFilters(
topologyTransformedData,
filters,
ALL_APPLICATIONS_KEY,
filterers,
);
expect(newModel.nodes.filter((n) => n.group).length).toBe(2);
expect(newModel.nodes.filter((n) => n.group && n.collapsed).length).toBe(2);
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Model, NodeModel, createAggregateEdges } from '@patternfly/react-topology';
import { ALL_APPLICATIONS_KEY } from '@console/shared/src';
import {
getFilterById,
EXPAND_APPLICATION_GROUPS_FILTER_ID,
DEFAULT_SUPPORTED_FILTER_IDS,
isExpanded,
} from '../filters';
import { TYPE_APPLICATION_GROUP, TYPE_AGGREGATE_EDGE } from '../components/const';
import { TopologyApplyDisplayOptions, DisplayFilters } from '../topology-types';
Expand Down Expand Up @@ -34,8 +34,8 @@ export const updateModelFromFilters = (
edges: [...model.edges],
};
const supportedFilters = [...DEFAULT_SUPPORTED_FILTER_IDS];
const expandGroups = getFilterById(EXPAND_APPLICATION_GROUPS_FILTER_ID, filters)?.value ?? true;
let appGroupFound = false;
const expanded = isExpanded(EXPAND_APPLICATION_GROUPS_FILTER_ID, filters);
dataModel.nodes.forEach((d) => {
d.visible = true;
if (displayFilterers) {
Expand All @@ -49,7 +49,7 @@ export const updateModelFromFilters = (
appGroupFound = true;
supportedFilters.push(EXPAND_APPLICATION_GROUPS_FILTER_ID);
}
d.collapsed = !expandGroups;
d.collapsed = !expanded;
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@import '../../../../../../public/style/vars';

.odc-topology-filter-dropdown {
&__expand-groups-switcher {
display: flex;
align-items: center;

.pf-c-select__menu-group-title {
flex: 1;
}

.pf-c-switch {
margin-right: var(--pf-global--spacer--sm);
}

.pf-m-disabled {
color: var(--pf-c-check__label--disabled--Color);
}
}

&__group {
&:not(:first-of-type) {
margin-top: var(--pf-global--spacer--sm);
border-top: 1px solid var(--pf-global--BorderColor--300);
}
}

&__expand-groups-label .pf-c-select__menu-group-title {
padding-top: 0;
padding-bottom: 0;
}
}
Original file line number Diff line number Diff line change
@@ -1,76 +1,111 @@
import * as React from 'react';
import { Select, SelectGroup, SelectOption, SelectVariant } from '@patternfly/react-core';
import { Select, SelectGroup, SelectOption, SelectVariant, Switch } from '@patternfly/react-core';
import { TopologyDisplayFilterType, DisplayFilters } from '../topology-types';

import './FilterDropdown.scss';

type FilterDropdownProps = {
filters: DisplayFilters;
supportedFilters: string[];
onChange: (filter: DisplayFilters) => void;
opened?: boolean; // Use only for testing
};

const FilterDropdown: React.FC<FilterDropdownProps> = ({ filters, supportedFilters, onChange }) => {
const [isOpen, setIsOpen] = React.useState(false);
const selected = filters.filter((f) => f.value).map((f) => f.id);
const FilterDropdown: React.FC<FilterDropdownProps> = ({
filters,
supportedFilters,
onChange,
opened = false,
}) => {
const [isOpen, setIsOpen] = React.useState(opened);

const onToggle = (open: boolean): void => setIsOpen(open);
const onSelect = (e: React.MouseEvent, key: string) => {
const index = filters.findIndex((f) => f.id === key);
const filter = { ...filters[index], value: (e.target as HTMLInputElement).checked };
onChange([...filters.slice(0, index), filter, ...filters.slice(index + 1)]);
const index = filters?.displayOptions?.findIndex((f) => f.id === key) ?? -1;
if (index === -1) {
return;
}
const filter = {
...filters.displayOptions[index],
value: (e.target as HTMLInputElement).checked,
};
onChange({
...filters,
displayOptions: [
...filters.displayOptions.slice(0, index),
filter,
...filters.displayOptions.slice(index + 1),
],
});
};

const ShowFiltersKeyValue = filters
.filter((f) => f.type === TopologyDisplayFilterType.show && supportedFilters.includes(f.id))
.sort((a, b) => a.priority - b.priority)
.reduce((acc, f) => {
acc[f.id] = f.label;
return acc;
}, {});
const ExpandFiltersKeyValue = filters
const onGroupsExpandedChange = (value: boolean) => {
onChange({
...filters,
groupsExpanded: value,
});
};

const expandFilters = filters.displayOptions
.filter((f) => f.type === TopologyDisplayFilterType.expand && supportedFilters.includes(f.id))
.sort((a, b) => a.priority - b.priority)
.reduce((acc, f) => {
acc[f.id] = f.label;
return acc;
}, {});
const options = [];
if (Object.keys(ShowFiltersKeyValue).length) {
options.push(
<SelectGroup key="show" label="Show">
{Object.keys(ShowFiltersKeyValue).map((key) => (
<SelectOption key={key} value={key}>
{ShowFiltersKeyValue[key]}
</SelectOption>
))}
</SelectGroup>,
);
}
if (Object.keys(ExpandFiltersKeyValue).length) {
options.push(
<SelectGroup key="expand" label="Expand">
{Object.keys(ExpandFiltersKeyValue).map((key) => (
<SelectOption key={key} value={key}>
{ExpandFiltersKeyValue[key]}
</SelectOption>
))}
</SelectGroup>,
);
}
.sort((a, b) => a.priority - b.priority);

const showFilters = filters.displayOptions
.filter((f) => f.type === TopologyDisplayFilterType.show && supportedFilters.includes(f.id))
.sort((a, b) => a.priority - b.priority);

const selectContent = (
<div className="odc-topology-filter-dropdown">
{expandFilters.length && (
<div className="odc-topology-filter-dropdown__group">
<span className="odc-topology-filter-dropdown__expand-groups-switcher">
<span className="pf-c-select__menu-group-title">Expand</span>
<Switch
aria-label="Collapse Groups"
isChecked={filters.groupsExpanded}
onChange={onGroupsExpandedChange}
/>
</span>
<SelectGroup className="odc-topology-filter-dropdown__expand-groups-label">
{expandFilters.map((filter) => (
<SelectOption
key={filter.id}
value={filter.id}
isDisabled={!filters.groupsExpanded}
isChecked={filter.value}
>
{filter.label}
</SelectOption>
))}
</SelectGroup>
</div>
)}
{showFilters.length && (
<div className="odc-topology-filter-dropdown__group">
<SelectGroup label="Show">
{showFilters.map((filter) => (
<SelectOption key={filter.id} value={filter.id} isChecked={filter.value}>
{filter.label}
</SelectOption>
))}
</SelectGroup>
</div>
)}
</div>
);

return (
<Select
className="odc-filter-dropdown__select"
className="odc-topology-filter-dropdown__select"
variant={SelectVariant.checkbox}
customContent={selectContent}
onToggle={onToggle}
selections={selected}
isOpen={isOpen}
onSelect={onSelect}
placeholderText="Display Options"
isGrouped
isCheckboxSelectionBadgeHidden
>
{...options}
</Select>
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { SelectOption } from '@patternfly/react-core';
import * as _ from 'lodash';
import { mount, shallow } from 'enzyme';
import { SelectOption, Switch } from '@patternfly/react-core';
import FilterDropdown from '../FilterDropdown';
import { DisplayFilters } from '../../topology-types';
import { DEFAULT_TOPOLOGY_FILTERS } from '../const';
Expand All @@ -9,40 +10,56 @@ describe(FilterDropdown.displayName, () => {
let dropdownFilter: DisplayFilters;
let onChange: () => void;
beforeEach(() => {
dropdownFilter = [...DEFAULT_TOPOLOGY_FILTERS];
dropdownFilter = _.cloneDeep(DEFAULT_TOPOLOGY_FILTERS);
onChange = jasmine.createSpy();
});

it('should exists', () => {
const wrapper = shallow(
<FilterDropdown
filters={dropdownFilter}
supportedFilters={dropdownFilter.map((f) => f.id)}
supportedFilters={dropdownFilter.displayOptions.map((f) => f.id)}
onChange={onChange}
/>,
);
expect(wrapper.exists()).toBeTruthy();
});

it('should have the correct number of filters', () => {
const wrapper = shallow(
const wrapper = mount(
<FilterDropdown
filters={dropdownFilter}
supportedFilters={dropdownFilter.map((f) => f.id)}
supportedFilters={dropdownFilter.displayOptions.map((f) => f.id)}
onChange={onChange}
opened
/>,
);
expect(wrapper.find(SelectOption)).toHaveLength(Object.keys(DEFAULT_TOPOLOGY_FILTERS).length);
expect(wrapper.find(SelectOption)).toHaveLength(
Object.keys(DEFAULT_TOPOLOGY_FILTERS.displayOptions).length,
);
});

it('should hide unsupported filters', () => {
const wrapper = shallow(
const wrapper = mount(
<FilterDropdown
filters={dropdownFilter}
supportedFilters={[dropdownFilter[0].id]}
supportedFilters={[dropdownFilter.displayOptions[0].id]}
onChange={onChange}
opened
/>,
);
expect(wrapper.find(SelectOption)).toHaveLength(1);
});

it('should contain the show expand groups switch', () => {
const wrapper = mount(
<FilterDropdown
filters={dropdownFilter}
supportedFilters={dropdownFilter.displayOptions.map((f) => f.id)}
onChange={onChange}
opened
/>,
);
expect(wrapper.find(Switch)).toHaveLength(1);
});
});

0 comments on commit 0df4138

Please sign in to comment.