Skip to content

Commit

Permalink
Merge pull request #1522 from weaveworks/1520-source-detail
Browse files Browse the repository at this point in the history
Add GitRepositoryDetail page
  • Loading branch information
jpellizzari committed Feb 25, 2022
2 parents 5747caf + f0b1c8c commit 0fc37a2
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 55 deletions.
6 changes: 6 additions & 0 deletions ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { V2Routes } from "./lib/types";
import Error from "./pages/Error";
import Automations from "./pages/v2/Automations";
import FluxRuntime from "./pages/v2/FluxRuntime";
import GitRepositoryDetail from "./pages/v2/GitRepositoryDetail";
import KustomizationDetail from "./pages/v2/KustomizationDetail";
import Sources from "./pages/v2/Sources";

Expand Down Expand Up @@ -62,6 +63,11 @@ export default function App() {
path={V2Routes.FluxRuntime}
component={FluxRuntime}
/>
<Route
exact
path={V2Routes.GitRepo}
component={withName(GitRepositoryDetail)}
/>
<Redirect exact from="/" to={V2Routes.Automations} />
<Route exact path="*" component={Error} />
</Switch>
Expand Down
17 changes: 17 additions & 0 deletions ui/components/AutomationsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Automation } from "../hooks/automations";
import { formatURL } from "../lib/nav";
import { AutomationType, V2Routes } from "../lib/types";
import DataTable, { SortType } from "./DataTable";
import KubeStatusIndicator from "./KubeStatusIndicator";
import Link from "./Link";

type Props = {
Expand Down Expand Up @@ -46,6 +47,22 @@ function AutomationsTable({ className, automations }: Props) {
label: "Namespace",
value: "namespace",
},
{
label: "Cluster",
value: "cluster",
},
{
label: "Status",
value: (a: Automation) =>
a.conditions.length > 0 ? (
<KubeStatusIndicator conditions={a.conditions} />
) : null,
},
{
label: "Revision",
value: "lastAttemptedRevision",
},
{ label: "Last Synced At", value: "lastHandledReconciledAt" },
]}
rows={automations}
/>
Expand Down
25 changes: 18 additions & 7 deletions ui/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ export enum SortType {
bool,
}

type field = {
type Sorter = (k: any) => any;

type Field = {
label: string;
value: string | ((k: any) => string | JSX.Element);
sortType?: SortType;
sortValue?: (k: any) => any;
sortValue?: Sorter;
};

/** DataTable Properties */
export interface Props {
/** CSS MUI Overrides or other styling. */
className?: string;
/** A list of objects with four fields: `label`, which is a string representing the column header, `value`, which can be a string, or a function that extracts the data needed to fill the table cell, `sortType`, which determines the sorting function to be used, and `sortValue`, which customizes your input to the search function */
fields: field[];
fields: Field[];
/** A list of data that will be iterated through to create the columns described in `fields`. */
rows: any[];
/** index of field to initially sort against. */
Expand Down Expand Up @@ -71,9 +73,18 @@ const TableButton = styled(Button)`
}
`;

export const sortWithType = (rows: any[], sort: field) => {
const sortFn = sort.sortValue;
return rows.sort((a: field, b: field) => {
type Row = any;

function defaultSortFunc(sort: Field): Sorter {
return (a: Row) => {
return a[sort.value as string];
};
}

export const sortWithType = (rows: Row[], sort: Field) => {
const sortFn = sort.sortValue || defaultSortFunc(sort);

return rows.sort((a: Row, b: Row) => {
switch (sort.sortType) {
case SortType.number:
return sortFn(a) - sortFn(b);
Expand Down Expand Up @@ -111,7 +122,7 @@ function UnstyledDataTable({
}

type labelProps = {
field: field;
field: Field;
};
function SortableLabel({ field }: labelProps) {
return (
Expand Down
40 changes: 40 additions & 0 deletions ui/components/InfoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import _ from "lodash";
import * as React from "react";
import styled from "styled-components";
import Text from "./Text";

const InfoList = styled(
({
items,
className,
}: {
className?: string;
items: { [key: string]: any };
}) => {
return (
<table className={className}>
<tbody>
{_.map(items, (v, k) => (
<tr key={k}>
<td>
<Text capitalize bold>
{k}:
</Text>
</td>
<td>{v || "-"}</td>
</tr>
))}
</tbody>
</table>
);
}
)`
tbody tr td:first-child {
min-width: 200px;
}
tr {
height: 16px;
}
`;

export default InfoList;
18 changes: 18 additions & 0 deletions ui/components/Interval.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from "react";
import styled from "styled-components";
import { Interval as IntervalType } from "../lib/api/core/types.pb";

type Props = {
className?: string;
interval: IntervalType;
};

function Interval({ className, interval }: Props) {
return (
<span className={className}>
{interval.hours}h {interval.minutes}m
</span>
);
}

export default styled(Interval).attrs({ className: Interval.name })``;
10 changes: 4 additions & 6 deletions ui/components/KubeStatusIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,23 @@ type Props = {
conditions: Condition[];
};

export function computeReady(conditions: Condition[]) {
export function computeReady(conditions: Condition[]): boolean {
const ready =
_.find(conditions, { type: "Ready" }) ||
// Deployment conditions work slightly differently;
// they show "Available" instead of 'Ready'
_.find(conditions, { type: "Available" });
return ready?.status;
return ready?.status == "True";
}

export function computeMessage(conditions: Condition[]) {
const readyCondition = _.find(conditions, (c) => c.type === "Ready");

if (readyCondition?.status === "False") {
return readyCondition.message;
}
return readyCondition.message;
}

function KubeStatusIndicator({ className, conditions }: Props) {
const ready = computeReady(conditions) === "True";
const ready = computeReady(conditions);
const readyText = ready ? "Ready" : computeMessage(conditions);
const color = ready ? "success" : "alert";

Expand Down
17 changes: 13 additions & 4 deletions ui/components/ReconciledObjectsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
GroupVersionKind,
UnstructuredObject,
} from "../lib/api/core/types.pb";
import DataTable from "./DataTable";
import KubeStatusIndicator from "./KubeStatusIndicator";
import DataTable, { SortType } from "./DataTable";
import KubeStatusIndicator, {
computeMessage,
computeReady,
} from "./KubeStatusIndicator";

type Props = {
className?: string;
Expand All @@ -35,9 +38,11 @@ function ReconciledObjectsTable({
return (
<div className={className}>
<DataTable
sortFields={["name", "type", "namespace", "status"]}
fields={[
{ value: "name", label: "Name" },
{
value: "name",
label: "Name",
},
{
label: "Type",
value: (u: UnstructuredObject) => `${u.groupVersionKind.kind}`,
Expand All @@ -52,10 +57,14 @@ function ReconciledObjectsTable({
u.conditions.length > 0 ? (
<KubeStatusIndicator conditions={u.conditions} />
) : null,
sortType: SortType.bool,
sortValue: ({ conditions }) => computeReady(conditions),
},
{
label: "Message",
value: (u: UnstructuredObject) => _.first(u.conditions)?.message,
sortType: SortType.string,
sortValue: ({ conditions }) => computeMessage(conditions),
},
]}
rows={objs}
Expand Down
105 changes: 105 additions & 0 deletions ui/components/SourceDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import _ from "lodash";
import * as React from "react";
import styled from "styled-components";
import { useListAutomations } from "../hooks/automations";
import { useListSources } from "../hooks/sources";
import { SourceRefSourceKind } from "../lib/api/core/types.pb";
import Alert from "./Alert";
import AutomationsTable from "./AutomationsTable";
import Flex from "./Flex";
import Icon, { IconType } from "./Icon";
import InfoList from "./InfoList";
import { computeMessage, computeReady } from "./KubeStatusIndicator";
import LoadingPage from "./LoadingPage";
import Text from "./Text";

type Props = {
className?: string;
type: SourceRefSourceKind;
name: string;
namespace: string;
children?: JSX.Element;
info: <T>(s: T) => { [key: string]: any };
};

function SourceDetail({ className, name, info }: Props) {
const { data: sources, isLoading, error } = useListSources();
const { data: automations } = useListAutomations();

if (isLoading) {
return <LoadingPage />;
}

const s = _.find(sources, { name });

const items = info(s);

const relevantAutomations = _.filter(automations, (a) => {
if (!s) {
return false;
}

if (a?.sourceRef?.kind == s.type && a.sourceRef.name == name) {
return true;
}

return false;
});

const ok = computeReady(s.conditions);
const msg = computeMessage(s.conditions);

return (
<div className={className}>
<Flex align wide between>
<div>
<h2>{s.name}</h2>
</div>
<div className="page-status">
{ok ? (
<Icon
color="success"
size="medium"
type={IconType.CheckMark}
text={msg}
/>
) : (
<Icon
color="alert"
size="medium"
type={IconType.ErrorIcon}
text={`Error: ${msg}`}
/>
)}
</div>
</Flex>
{error && (
<Alert severity="error" title="Error" message={error.message} />
)}
<div>
<h3>{s.type}</h3>
</div>
<div>
<InfoList items={items} />
</div>
<div>
<AutomationsTable automations={relevantAutomations} />
</div>
</div>
);
}

export default styled(SourceDetail).attrs({ className: SourceDetail.name })`
h3 {
margin-bottom: 24px;
}
${InfoList} {
margin-bottom: 60px;
}
.page-status ${Icon} ${Text} {
color: ${(props) => props.theme.colors.black};
font-weight: normal;
}
`;
2 changes: 1 addition & 1 deletion ui/components/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const Text = styled.span<Props>`
else if (props.semiBold) return "600";
else return "400";
}};
text-transform: ${(props) => (props.capitalize ? "uppercase" : "none")};
text-transform: ${(props) => (props.capitalize ? "capitalize" : "none")};
font-style: ${(props) => (props.italic ? "italic" : "normal")};
color: ${(props) => props.theme.colors[props.color as any]};
`;
Expand Down
34 changes: 34 additions & 0 deletions ui/components/__tests__/DataTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,41 @@ describe("DataTable", () => {
const firstRow = screen.getAllByRole("row")[1];
expect(firstRow.innerHTML).toMatch(/No/);
});
it("sorts by value when no sortValue property exists", () => {
const rows = [
{
name: "b",
},
{
name: "c",
},
{
name: "a",
},
];

const fields = [
{
label: "Name",
value: "name",
},
];

render(
withTheme(<DataTable defaultSort={0} fields={fields} rows={rows} />)
);

let firstRow = screen.getAllByRole("row")[1];
expect(firstRow.innerHTML).toMatch(/a/);

const nameButton = screen.getByText("Name");
fireEvent.click(nameButton);

firstRow = screen.getAllByRole("row")[1];
expect(firstRow.innerHTML).toMatch(/c/);
});
});

describe("snapshots", () => {
it("renders", () => {
const tree = renderer
Expand Down

0 comments on commit 0fc37a2

Please sign in to comment.