Skip to content

Commit

Permalink
feat(ui): add filters dropdown for filtering minor releases (#1954)
Browse files Browse the repository at this point in the history
Ref: SRX-ATM6BG
  • Loading branch information
AminSlk authored Sep 17, 2024
1 parent 6c718dd commit 49fa7a7
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 37 deletions.
2 changes: 2 additions & 0 deletions services/frontend-service/src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export const makeRelease = (
createdAt: new Date(2002),
undeployVersion: undeployVersion,
prNumber: '666',
isMinor: false,
isPrepublish: false,
});

const date = new Date(2023, 6, 12);
Expand Down
30 changes: 24 additions & 6 deletions services/frontend-service/src/ui/Pages/Home/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,18 @@ describe('App', () => {
getWrapper();

// then apps are sorted and Service Lane is called
expect(mock_ServiceLane.ServiceLane.getCallArgument(0, 0)).toStrictEqual({ application: sampleApps.app1 });
expect(mock_ServiceLane.ServiceLane.getCallArgument(1, 0)).toStrictEqual({ application: sampleApps.app2 });
expect(mock_ServiceLane.ServiceLane.getCallArgument(2, 0)).toStrictEqual({ application: sampleApps.app3 });
expect(mock_ServiceLane.ServiceLane.getCallArgument(0, 0)).toStrictEqual({
application: sampleApps.app1,
hideMinors: false,
});
expect(mock_ServiceLane.ServiceLane.getCallArgument(1, 0)).toStrictEqual({
application: sampleApps.app2,
hideMinors: false,
});
expect(mock_ServiceLane.ServiceLane.getCallArgument(2, 0)).toStrictEqual({
application: sampleApps.app3,
hideMinors: false,
});
});
it('Renders Spinner', () => {
// given
Expand Down Expand Up @@ -95,9 +104,18 @@ describe('App', () => {
getWrapper();

// then apps are sorted and Service Lane is called
expect(mock_ServiceLane.ServiceLane.getCallArgument(0, 0)).toStrictEqual({ application: sampleApps.app1 });
expect(mock_ServiceLane.ServiceLane.getCallArgument(1, 0)).toStrictEqual({ application: sampleApps.app2 });
expect(mock_ServiceLane.ServiceLane.getCallArgument(2, 0)).toStrictEqual({ application: sampleApps.app3 });
expect(mock_ServiceLane.ServiceLane.getCallArgument(0, 0)).toStrictEqual({
application: sampleApps.app1,
hideMinors: false,
});
expect(mock_ServiceLane.ServiceLane.getCallArgument(1, 0)).toStrictEqual({
application: sampleApps.app2,
hideMinors: false,
});
expect(mock_ServiceLane.ServiceLane.getCallArgument(2, 0)).toStrictEqual({
application: sampleApps.app3,
hideMinors: false,
});
});
});

Expand Down
4 changes: 2 additions & 2 deletions services/frontend-service/src/ui/Pages/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useSearchParams } from 'react-router-dom';
import { useApplicationsFilteredAndSorted, useGlobalLoadingState } from '../../utils/store';
import React from 'react';
import { TopAppBar } from '../../components/TopAppBar/TopAppBar';
import { hideWithoutWarnings } from '../../utils/Links';
import { hideWithoutWarnings, hideMinors } from '../../utils/Links';

export const Home: React.FC = () => {
const [params] = useSearchParams();
Expand All @@ -39,7 +39,7 @@ export const Home: React.FC = () => {
<TopAppBar showAppFilter={true} showTeamFilter={true} showWarningFilter={true} />
<main className="main-content">
{apps.map((app) => (
<ServiceLane application={app} key={app.name} />
<ServiceLane application={app} hideMinors={hideMinors(params)} key={app.name} />
))}
</main>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const extendRelease = (props: Partial<Release>): Release => ({
describe('Service Lane', () => {
const getNode = (overrides: { application: Application }) => (
<MemoryRouter>
<ServiceLane {...overrides} />
<ServiceLane {...overrides} hideMinors={false} />
</MemoryRouter>
);
const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
Expand Down Expand Up @@ -254,7 +254,7 @@ const data: TestDataDiff[] = [
describe('Service Lane Diff', () => {
const getNode = (overrides: { application: Application }) => (
<MemoryRouter>
<ServiceLane {...overrides} />
<ServiceLane {...overrides} hideMinors={false} />
</MemoryRouter>
);
const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
Expand Down Expand Up @@ -300,7 +300,12 @@ describe('Service Lane Diff', () => {
});
});

type TestDataImportantRels = { name: string; releases: Release[]; currentlyDeployedVersion: number };
type TestDataImportantRels = {
name: string;
releases: Release[];
currentlyDeployedVersion: number;
minorReleaseIndex: number;
};

const dataImportantRels: TestDataImportantRels[] = [
{
Expand All @@ -316,6 +321,7 @@ const dataImportantRels: TestDataImportantRels[] = [
makeRelease(2),
makeRelease(1), // not important
],
minorReleaseIndex: 7,
},
{
name: 'Gets latest release first, then deployed release and 4 trailing releases',
Expand All @@ -330,6 +336,7 @@ const dataImportantRels: TestDataImportantRels[] = [
makeRelease(2),
makeRelease(1), // not important
],
minorReleaseIndex: 7,
},
{
name: 'jumps over not important second release',
Expand All @@ -344,19 +351,36 @@ const dataImportantRels: TestDataImportantRels[] = [
makeRelease(2),
makeRelease(1), // not important
],
minorReleaseIndex: 7,
},
{
name: 'Minor release should be ignored',
currentlyDeployedVersion: 9,
releases: [
makeRelease(9),
makeRelease(7),
makeRelease(6),
makeRelease(5),
makeRelease(4),
makeRelease(3),
makeRelease(2),
makeRelease(1), // not important
],
minorReleaseIndex: 1,
},
];

describe('Service Lane Important Releases', () => {
const getNode = (overrides: { application: Application }) => (
<MemoryRouter>
<ServiceLane {...overrides} />
<ServiceLane {...overrides} hideMinors={true} />
</MemoryRouter>
);
const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
describe.each(dataImportantRels)('Service Lane important releases', (testcase) => {
it(testcase.name, () => {
// given
testcase.releases[testcase.minorReleaseIndex].isMinor = true;
const sampleApp: Application = {
releases: testcase.releases,
name: 'test2',
Expand Down Expand Up @@ -421,6 +445,10 @@ describe('Service Lane Important Releases', () => {
{ app: 'test2', version: testcase.releases[7].version },
Spy.IGNORE
);
mock_ReleaseCard.ReleaseCard.wasNotCalledWith(
{ app: 'test2', version: testcase.releases[testcase.minorReleaseIndex].version },
Spy.IGNORE
);
});
});
});
Expand Down Expand Up @@ -493,7 +521,7 @@ const dataUndeploy: TestDataUndeploy[] = (() => {
describe('Service Lane ⋮ menu', () => {
const getNode = (overrides: { application: Application }) => (
<MemoryRouter>
<ServiceLane {...overrides} />
<ServiceLane {...overrides} hideMinors={false} />
</MemoryRouter>
);
const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
Expand Down Expand Up @@ -704,7 +732,7 @@ const dataAppLockSummary: TestDataAppLockSummary[] = (() => {
describe('Service Lane AppLockSummary', () => {
const getNode = (overrides: { application: Application }) => (
<MemoryRouter>
<ServiceLane {...overrides} />
<ServiceLane {...overrides} hideMinors={false} />
</MemoryRouter>
);
const getWrapper = (overrides: { application: Application }) => render(getNode(overrides));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
useCurrentlyExistsAtGroup,
useDeployedReleases,
useFilteredApplicationLocks,
useMinorsForApp,
useNavigateWithSearchParams,
useTeamLocksFilterByTeam,
useVersionsForApp,
Expand All @@ -39,7 +40,17 @@ import { EnvSelectionDialog } from '../SelectionDialog/SelectionDialogs';
// we could update this dynamically based on viewport width
const numberOfDisplayedReleasesOnHome = 6;

const getReleasesToDisplay = (deployedReleases: number[], allReleases: number[]): number[] => {
const getReleasesToDisplay = (
deployedReleases: number[],
allReleases: number[],
minorReleases: number[],
ignoreMinors: boolean
): number[] => {
if (ignoreMinors) {
allReleases = allReleases.filter(
(version) => !minorReleases.includes(version) || deployedReleases.includes(version)
);
}
// all deployed releases are important and the latest release is also important
const importantReleases = deployedReleases.includes(allReleases[0])
? deployedReleases
Expand Down Expand Up @@ -93,8 +104,8 @@ const deriveUndeployMessage = (undeploySummary: UndeploySummary): string | undef
}
};

export const ServiceLane: React.FC<{ application: Application }> = (props) => {
const { application } = props;
export const ServiceLane: React.FC<{ application: Application; hideMinors: boolean }> = (props) => {
const { application, hideMinors } = props;
const deployedReleases = useDeployedReleases(application.name);
const allReleases = useVersionsForApp(application.name);
const { navCallback } = useNavigateWithSearchParams('releasehistory/' + application.name);
Expand Down Expand Up @@ -126,7 +137,8 @@ export const ServiceLane: React.FC<{ application: Application }> = (props) => {
break;
}
}, [application.name, application.undeploySummary]);
const releases = getReleasesToDisplay(deployedReleases, allReleases);
const minorReleases = useMinorsForApp(application.name);
const releases = getReleasesToDisplay(deployedReleases, allReleases, minorReleases, hideMinors);

const releases_lane =
!!releases &&
Expand Down
39 changes: 25 additions & 14 deletions services/frontend-service/src/ui/components/TopAppBar/TopAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ import { Textfield } from '../textfield';
import React, { useCallback } from 'react';
import { SideBar } from '../SideBar/SideBar';
import { useSearchParams } from 'react-router-dom';
import { Dropdown } from '../dropdown/dropdown';
import { Checkbox } from '../dropdown/checkbox';
import { TeamsFilterDropdown, FiltersDropdown } from '../dropdown/dropdown';
import classNames from 'classnames';
import { useAllWarnings, useKuberpultVersion, useShownWarnings } from '../../utils/store';
import { Warning } from '../../../api/api';
import { hideWithoutWarnings, KuberpultGitHubLink, setHideWithoutWarnings } from '../../utils/Links';
import {
hideMinors,
setHideMinors,
hideWithoutWarnings,
KuberpultGitHubLink,
setHideWithoutWarnings,
} from '../../utils/Links';

export type TopAppBarProps = {
showAppFilter: boolean;
Expand All @@ -48,17 +53,23 @@ export const TopAppBar: React.FC<TopAppBarProps> = (props) => {
const hideWithoutWarningsValue = hideWithoutWarnings(params);
const allWarnings: Warning[] = useAllWarnings();
const shownWarnings: Warning[] = useShownWarnings(teamsParam, appNameParam);
const hideMinorsValue = hideMinors(params);

const onWarningsFilterClick = useCallback((): void => {
setHideWithoutWarnings(params, !hideWithoutWarningsValue);
setParams(params);
}, [hideWithoutWarningsValue, params, setParams]);

const onMinorsFilterClick = useCallback((): void => {
setHideMinors(params, !hideMinorsValue);
setParams(params);
}, [hideMinorsValue, params, setParams]);

const renderedWarnings =
allWarnings.length === 0 || !props.showWarningFilter ? (
''
) : (
<div className="service-lane__warning">
<div className="service-lane__warning mdc-top-app-bar__section top-app-bar--narrow-filter">
{shownWarnings.length} warnings shown ({allWarnings.length} total).
</div>
);
Expand Down Expand Up @@ -92,22 +103,22 @@ export const TopAppBar: React.FC<TopAppBarProps> = (props) => {
const renderedTeamsFilter =
props.showTeamFilter === true ? (
<div className="mdc-top-app-bar__section top-app-bar--narrow-filter">
<Dropdown className={'top-app-bar-search-field'} placeholder={'Teams'} leadingIcon={'search'} />
<TeamsFilterDropdown
className={'top-app-bar-search-field'}
placeholder={'Teams'}
leadingIcon={'search'}
/>
</div>
) : (
<div className="mdc-top-app-bar__section top-app-bar--narrow-filter"></div>
);
const renderedWarningsFilter =
props.showWarningFilter === true ? (
<div className="mdc-top-app-bar__section top-app-bar--narrow-filter">
<Checkbox
enabled={hideWithoutWarningsValue}
onClick={onWarningsFilterClick}
id="warningFilter"
label="hide apps without warnings"
classes=""
/>
</div>
<FiltersDropdown
hideWithoutWarningsValue={hideWithoutWarningsValue}
hideMinorsValue={hideMinorsValue}
onWarningsFilterClick={onWarningsFilterClick}
onMinorsFilterClick={onMinorsFilterClick}></FiltersDropdown>
) : (
<div className="mdc-top-app-bar__section top-app-bar--narrow-filter"></div>
);
Expand Down
17 changes: 17 additions & 0 deletions services/frontend-service/src/ui/components/dropdown/dropdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ div.mdc-select {
padding-left: 1em;
}

.dropdown-button {
height: 70px;
border-radius: $border-radius-medium;
border: 1px solid blue;
padding-left: 10px;
width: 160px;
text-align: left;
box-sizing: border-box;
justify-content: left;
font-size: small;
.mdc-button__label {
color: black;
font-family: 'Inter';
text-transform: none;
}
}

.confirmation-dialog-footer {
display: flex;
flex-direction: row-reverse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You should have received a copy of the MIT License
along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
Copyright freiheit.com*/
import { DropdownSelect, DropdownSelectProps } from './dropdown';
import { TeamsFilterDropdownSelect, DropdownSelectProps } from './dropdown';
import { getByTestId, render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

Expand All @@ -25,7 +25,7 @@ const getNode = (overrides?: {}): JSX.Element | any => {
return (
<div>
<MemoryRouter>
<DropdownSelect {...defaultProps} {...overrides} />
<TeamsFilterDropdownSelect {...defaultProps} {...overrides} />
</MemoryRouter>
</div>
);
Expand Down
Loading

0 comments on commit 49fa7a7

Please sign in to comment.