Skip to content

Commit

Permalink
Added Helm Release List Page and Nav Item for it
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitkrai03 committed Jan 16, 2020
1 parent 0150aba commit 35f2d74
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 1 deletion.
@@ -0,0 +1,53 @@
import { sortable } from '@patternfly/react-table';

export const tableColumnClasses = {
name: 'col-lg-2 col-md-3 col-sm-4 col-xs-4',
revision: 'col-lg-2 col-md-3 col-sm-3 col-xs-3',
timestamp: 'col-lg-2 col-md-4 col-sm-5 col-xs-5',
status: 'col-lg-2 col-md-2 hidden-sm hidden-xs',
chartName: 'col-lg-2 hidden-md hidden-sm hidden-xs',
chartVersion: 'col-lg-2 hidden-md hidden-sm hidden-xs',
};

const HelmReleaseHeader = () => {
return [
{
title: 'Name',
sortField: 'name',
transforms: [sortable],
props: { className: tableColumnClasses.name },
},
{
title: 'Revision',
sortField: 'version',
transforms: [sortable],
props: { className: tableColumnClasses.revision },
},
{
title: 'Timestamp',
sortField: 'info.last_deployed',
transforms: [sortable],
props: { className: tableColumnClasses.timestamp },
},
{
title: 'Status',
sortField: 'info.status',
transforms: [sortable],
props: { className: tableColumnClasses.status },
},
{
title: 'Chart Name',
sortField: 'chart.metadata.name',
transforms: [sortable],
props: { className: tableColumnClasses.chartName },
},
{
title: 'Chart Version',
sortField: 'chart.metadata.version',
transforms: [sortable],
props: { className: tableColumnClasses.chartVersion },
},
];
};

export default HelmReleaseHeader;
116 changes: 116 additions & 0 deletions frontend/packages/dev-console/src/components/helm/HelmReleaseList.tsx
@@ -0,0 +1,116 @@
import * as React from 'react';
import * as _ from 'lodash';
import { coFetchJSON } from '@console/internal/co-fetch';
import { Table, TextFilter } from '@console/internal/components/factory';
import { SortByDirection } from '@patternfly/react-table';
import { CheckBoxes } from '@console/internal/components/row-filter';
import { FirehoseResult, getQueryArgument } from '@console/internal/components/utils';
import { HelmRelease, HelmFilterType } from './helm-types';
import { helmRowFilters, getFilteredItems, useDeepCompareMemoize } from './helm-utils';
import HelmReleaseHeader from './HelmReleaseHeader';
import HelmReleaseRow from './HelmReleaseRow';

interface HelmReleaseListProps {
namespace: string;
secrets?: FirehoseResult;
}

const HelmReleaseList: React.FC<HelmReleaseListProps> = ({ namespace, secrets }) => {
const [releases, setReleases] = React.useState([]);
const [filteredReleases, setFilteredReleases] = React.useState([]);
const [fetched, setFetched] = React.useState(false);

const memoizedSecrets = useDeepCompareMemoize(secrets.data);

React.useEffect(() => {
let ignore = false;

const activeFilters =
getQueryArgument('rowFilter-helm-release-status') &&
getQueryArgument('rowFilter-helm-release-status').split(',');

const fetchHelmReleases = async () => {
const res: HelmRelease[] = await coFetchJSON('/api/console/helm/list');
const namespacedReleases = (res && res.filter((rel) => rel.namespace === namespace)) || [];

if (ignore) return;

setReleases(namespacedReleases);
setFetched(true);

if (activeFilters) {
const filteredItems = getFilteredItems(
namespacedReleases,
HelmFilterType.Row,
activeFilters,
);
setFilteredReleases(filteredItems);
} else {
setFilteredReleases(namespacedReleases);
}
};

fetchHelmReleases();

return () => {
ignore = true;
};
}, [namespace, memoizedSecrets]);

const applyRowFilter = React.useCallback(
(filter) => {
const filteredItems = getFilteredItems(releases, HelmFilterType.Row, filter);
setFilteredReleases(filteredItems);
},
[releases],
);

const applyTextFilter = React.useCallback(
(filter) => {
const filteredItems = getFilteredItems(releases, HelmFilterType.Text, filter);
setFilteredReleases(filteredItems);
},
[releases],
);

const RowsOfRowFilters = _.map(helmRowFilters, ({ items, reducer, selected, type }, i) => {
return (
<CheckBoxes
key={i}
applyFilter={applyRowFilter}
items={items}
itemCount={_.size(releases)}
numbers={_.countBy(releases, reducer)}
selected={selected}
type={type}
reduxIDs={[]}
/>
);
});

return (
<>
<div className="co-m-pane__filter-bar">
<div className="co-m-pane__filter-bar-group co-m-pane__filter-bar-group--filter">
<TextFilter label="by name" onChange={(e) => applyTextFilter(e.target.value)} />
</div>
</div>

<div className="co-m-pane__body">
{!_.isEmpty(releases) && RowsOfRowFilters}
<Table
data={filteredReleases}
defaultSortField="name"
defaultSortOrder={SortByDirection.asc}
aria-label="Helm Releases"
Header={HelmReleaseHeader}
Row={HelmReleaseRow}
loaded={fetched}
virtualize
/>
</div>
</>
);
};

export default React.memo(HelmReleaseList);
@@ -0,0 +1,46 @@
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import Helmet from 'react-helmet';
import { PageHeading, Firehose } from '@console/internal/components/utils';
import { SecretModel } from '@console/internal/models';
import ProjectListPage from '../projects/ProjectListPage';
import NamespacedPage, { NamespacedPageVariants } from '../NamespacedPage';
import HelmReleaseList from './HelmReleaseList';

type HelmReleasePageProps = RouteComponentProps<{ ns: string }>;

export const HelmReleasePage: React.FC<HelmReleasePageProps> = (props) => {
const {
match: {
params: { ns: namespace },
},
} = props;

const resources = [
{
isList: true,
namespace,
kind: SecretModel.kind,
prop: 'secrets',
optional: true,
selector: { owner: 'helm' },
},
];
return namespace ? (
<NamespacedPage variant={NamespacedPageVariants.light} hideApplications>
<Helmet>
<title>Helm Releases</title>
</Helmet>
<PageHeading title="Helm Releases" />
<Firehose resources={resources}>
<HelmReleaseList namespace={namespace} />
</Firehose>
</NamespacedPage>
) : (
<ProjectListPage title="Helm Releases">
Select a project to view the list of Helm Releases
</ProjectListPage>
);
};

export default HelmReleasePage;
@@ -0,0 +1,44 @@
import * as React from 'react';
import { Status } from '@console/shared';
import { TableRow, TableData } from '@console/internal/components/factory';
import { Timestamp } from '@console/internal/components/utils';
import { Link } from 'react-router-dom';
import { HelmRelease } from './helm-types';
import { tableColumnClasses } from './HelmReleaseHeader';

interface HelmReleaseRowProps {
obj: HelmRelease;
index: number;
key?: string;
style: object;
}

const HelmReleaseRow: React.FC<HelmReleaseRowProps> = ({ obj, index, key, style }) => {
return (
<TableRow id={obj.name} index={index} trKey={key} style={style}>
<TableData className={tableColumnClasses.name}>
<Link
to={`/helm-releases/ns/${obj.namespace}/release/${obj.name}`}
title={obj.name}
className="co-resource-item__resource-name"
data-test-id={obj.name}
>
{obj.name}
</Link>
</TableData>
<TableData className={tableColumnClasses.revision}>{obj.version}</TableData>
<TableData className={tableColumnClasses.timestamp}>
<Timestamp timestamp={obj.info.last_deployed} />
</TableData>
<TableData className={tableColumnClasses.status}>
<Status status={obj.info.status} />
</TableData>
<TableData className={tableColumnClasses.chartName}>{obj.chart.metadata.name}</TableData>
<TableData className={tableColumnClasses.chartVersion}>
{obj.chart.metadata.version}
</TableData>
</TableRow>
);
};

export default HelmReleaseRow;
34 changes: 34 additions & 0 deletions frontend/packages/dev-console/src/components/helm/helm-types.ts
@@ -0,0 +1,34 @@
export interface HelmRelease {
name: string;
namespace: string;
chart: {
files: object[];
metadata: {
name: string;
version: string;
};
templates: object[];
values: object;
};
info: {
description: string;
deleted: string;
first_deployed: string;
last_deployed: string;
status: string;
};
hooks: object[];
manifest: string;
version: string;
}

export enum HelmReleaseStatus {
Deployed = 'deployed',
Failed = 'failed',
Other = 'other',
}

export enum HelmFilterType {
Row = 'row',
Text = 'text',
}
74 changes: 74 additions & 0 deletions frontend/packages/dev-console/src/components/helm/helm-utils.ts
@@ -0,0 +1,74 @@
import * as React from 'react';
import * as _ from 'lodash';
import * as fuzzy from 'fuzzysearch';
import { HelmRelease, HelmReleaseStatus, HelmFilterType } from './helm-types';

export const HelmReleaseStatusLabels = {
[HelmReleaseStatus.Deployed]: 'Deployed',
[HelmReleaseStatus.Failed]: 'Failed',
[HelmReleaseStatus.Other]: 'Other',
};

export const otherStatuses = [
'unknown',
'uninstalled',
'superseded',
'uninstalling',
'pending-install',
'pending-upgrade',
'pending-rollback',
];

export const releaseStatusReducer = (release: HelmRelease) => {
if (otherStatuses.includes(release.info.status)) {
return HelmReleaseStatus.Other;
}
return release.info.status;
};

export const selectedStatuses = [
HelmReleaseStatus.Deployed,
HelmReleaseStatus.Failed,
HelmReleaseStatus.Other,
];

export const helmRowFilters = [
{
type: 'helm-release-status',
selected: selectedStatuses,
reducer: releaseStatusReducer,
items: selectedStatuses.map((status) => ({
id: status,
title: HelmReleaseStatusLabels[status],
})),
},
];

export const getFilteredItems = (
items: HelmRelease[],
filterType: HelmFilterType,
filter: string | string[],
) => {
switch (filterType) {
case HelmFilterType.Row:
return items.filter((release: HelmRelease) => {
return otherStatuses.includes(release.info.status)
? filter.includes(HelmReleaseStatus.Other)
: filter.includes(release.info.status);
});
case HelmFilterType.Text:
return items.filter((release: HelmRelease) => fuzzy(filter, release.name));
default:
return items;
}
};

export const useDeepCompareMemoize = (value) => {
const ref = React.useRef();

if (!_.isEqual(value, ref.current)) {
ref.current = value;
}

return ref.current;
};

0 comments on commit 35f2d74

Please sign in to comment.