diff --git a/dashboard/src/components/AppList/AppListItem.scss b/dashboard/src/components/AppList/AppListItem.scss new file mode 100644 index 00000000000..f59d0dd7607 --- /dev/null +++ b/dashboard/src/components/AppList/AppListItem.scss @@ -0,0 +1,11 @@ +.deleted { + background-color: #c5c5c5; +} + +.failed { + background-color: #cb1515; +} + +.deployed { + background-color: #15cb2d; +} diff --git a/dashboard/src/components/AppList/AppListItem.test.tsx b/dashboard/src/components/AppList/AppListItem.test.tsx index bf5acf484ca..114cb2b35e5 100644 --- a/dashboard/src/components/AppList/AppListItem.test.tsx +++ b/dashboard/src/components/AppList/AppListItem.test.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { Link } from "react-router-dom"; import { IAppOverview } from "../../shared/types"; -import Card from "../Card"; +import InfoCard from "../InfoCard"; import AppListItem from "./AppListItem"; it("renders a app item", () => { @@ -19,23 +19,11 @@ it("renders a app item", () => { } />, ); - expect(wrapper.find(Card).key()).toBe("foo"); - expect( - wrapper - .find(Card) - .children() - .find(Link) - .props().title, - ).toBe("foo"); - expect( - wrapper - .find(Card) - .children() - .find(Link) - .props().to, - ).toBe("/apps/ns/default/foo"); - expect(wrapper.find(".ChartListItem__content__info_version").text()).toBe("1.0.0"); - expect(wrapper.find(".DEPLOYED").exists()).toBe(true); - expect(wrapper.find(".ChartListItem__content__info_repo").text()).toBe("default"); - expect(wrapper.find(".ChartListItem__content__info_other").text()).toBe("deployed"); + const card = wrapper.find(InfoCard).shallow(); + expect(card.find(Link).props().title).toBe("foo"); + expect(card.find(Link).props().to).toBe("/apps/ns/default/foo"); + expect(card.find(".type-color-light-blue").text()).toBe("1.0.0"); + expect(card.find(".deployed").exists()).toBe(true); + expect(card.find(".ListItem__content__info_tag-1").text()).toBe("default"); + expect(card.find(".ListItem__content__info_tag-2").text()).toBe("deployed"); }); diff --git a/dashboard/src/components/AppList/AppListItem.tsx b/dashboard/src/components/AppList/AppListItem.tsx index 345f949393b..8285884e96b 100644 --- a/dashboard/src/components/AppList/AppListItem.tsx +++ b/dashboard/src/components/AppList/AppListItem.tsx @@ -1,10 +1,9 @@ import * as React from "react"; -import { Link } from "react-router-dom"; import placeholder from "../../placeholder.png"; import { IAppOverview } from "../../shared/types"; -import Card, { CardContent, CardIcon } from "../Card"; -import "../ChartList/ChartListItem.css"; +import InfoCard from "../InfoCard"; +import "./AppListItem.css"; interface IAppListItemProps { app: IAppOverview; @@ -16,37 +15,16 @@ class AppListItem extends React.Component { const icon = app.icon ? app.icon : placeholder; return ( - - - - -
-

{app.releaseName}

-
-

- {app.version || "-"} -

-
- - {app.namespace} - - - {app.status.toLowerCase()} - -
-
-
-
- -
+ ); } } diff --git a/dashboard/src/components/ChartList/ChartListItem.scss b/dashboard/src/components/ChartList/ChartListItem.scss index 85bee71a851..2f5297131a5 100644 --- a/dashboard/src/components/ChartList/ChartListItem.scss +++ b/dashboard/src/components/ChartList/ChartListItem.scss @@ -1,53 +1,7 @@ -.ChartListItem { - color: #1c2b39; -} - -.ChartListItem__content__title { - margin: 0; - width: 100%; - font-weight: bold; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - color: #1c2b39; -} - -.ChartListItem__content__info { - display: flex; - justify-content: space-between; -} - -.ChartListItem__content__info_repo { - display: inline-block; - color: #fff; - border-radius: 16px; - background-color: #1c2b39; -} - -.ChartListItem__content__info_other { - display: inline-block; - color: rgb(0, 0, 0); - margin-left: 0.3em; - border-radius: 16px; - background-color: #1598cb; -} - -.DELETED { - background-color: #c5c5c5; -} - -.FAILED { - background-color: #cb1515; -} - -.DEPLOYED { - background-color: #15cb2d; -} - -.ChartListItem__content__info_repo.stable { +.ListItem__content__info_tag.stable { background-color: #1598cb; } -.ChartListItem__content__info_repo.incubator { +.ListItem__content__info_tag.incubator { background-color: #f58220; } diff --git a/dashboard/src/components/ChartList/ChartListItem.test.tsx b/dashboard/src/components/ChartList/ChartListItem.test.tsx index d0e041ca14c..e924996c8f0 100644 --- a/dashboard/src/components/ChartList/ChartListItem.test.tsx +++ b/dashboard/src/components/ChartList/ChartListItem.test.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { IChart, IRepo } from "../../shared/types"; import { CardIcon } from "../Card"; +import InfoCard from "../InfoCard"; import ChartListItem from "./ChartListItem"; jest.mock("../../placeholder.png", () => "placeholder.png"); @@ -37,12 +38,24 @@ it("should use the default placeholder for the icon if it doesn't exist", () => chartWithoutIcon.attributes.icon = undefined; const wrapper = shallow(); // Importing an image returns "undefined" - expect(wrapper.find(CardIcon).prop("src")).toBe(undefined); + expect( + wrapper + .find(InfoCard) + .shallow() + .find(CardIcon) + .prop("src"), + ).toBe(undefined); }); it("should place a dash if the version is not avaliable", () => { const chartWithoutVersion = { ...defaultChart }; chartWithoutVersion.relationships.latestChartVersion.data.app_version = ""; const wrapper = shallow(); - expect(wrapper.find(".ChartListItem__content__info_version").text()).toBe("-"); + expect( + wrapper + .find(InfoCard) + .shallow() + .find(".type-color-light-blue") + .text(), + ).toBe("-"); }); diff --git a/dashboard/src/components/ChartList/ChartListItem.tsx b/dashboard/src/components/ChartList/ChartListItem.tsx index 521cf5d0d48..6b808ddf954 100644 --- a/dashboard/src/components/ChartList/ChartListItem.tsx +++ b/dashboard/src/components/ChartList/ChartListItem.tsx @@ -1,9 +1,8 @@ import * as React from "react"; -import { Link } from "react-router-dom"; import placeholder from "../../placeholder.png"; import { IChart } from "../../shared/types"; -import Card, { CardContent, CardIcon } from "../Card"; +import InfoCard from "../InfoCard"; import "./ChartListItem.css"; @@ -18,28 +17,15 @@ class ChartListItem extends React.Component { const iconSrc = icon ? `/api/chartsvc/${icon}` : placeholder; const latestAppVersion = chart.relationships.latestChartVersion.data.app_version; return ( - - - - -
-

{name}

-
-

- {latestAppVersion || "-"} -

- - {repo.name} - -
-
-
- -
+ ); } } diff --git a/dashboard/src/components/ChartList/__snapshots__/ChartListItem.test.tsx.snap b/dashboard/src/components/ChartList/__snapshots__/ChartListItem.test.tsx.snap index 15921871ff9..362d09ae6dd 100644 --- a/dashboard/src/components/ChartList/__snapshots__/ChartListItem.test.tsx.snap +++ b/dashboard/src/components/ChartList/__snapshots__/ChartListItem.test.tsx.snap @@ -1,42 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render an item 1`] = ` - - - - -
-

- foo -

-
-

- 1.0.0 -

- -
-
-
- -
+ link="/charts/foo" + title="foo" +/> `; diff --git a/dashboard/src/components/InfoCard/InfoCard.scss b/dashboard/src/components/InfoCard/InfoCard.scss new file mode 100644 index 00000000000..59a42b42d36 --- /dev/null +++ b/dashboard/src/components/InfoCard/InfoCard.scss @@ -0,0 +1,34 @@ +.ListItem { + color: #1c2b39; +} + +.ListItem__content__title { + margin: 0; + width: 100%; + font-weight: bold; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + color: #1c2b39; +} + +.ListItem__content__info { + display: flex; + justify-content: space-between; +} + +.ListItem__content__info_tag { + float: right; + margin-left: 3px; + margin-bottom: 3px; + color: #fff; + border-radius: 16px; +} + +.ListItem__content__info_tag-1 { + background-color: #1c2b39; +} + +.ListItem__content__info_tag-2 { + background-color: #1598cb; +} diff --git a/dashboard/src/components/InfoCard/InfoCard.test.tsx b/dashboard/src/components/InfoCard/InfoCard.test.tsx new file mode 100644 index 00000000000..3083af0e4cb --- /dev/null +++ b/dashboard/src/components/InfoCard/InfoCard.test.tsx @@ -0,0 +1,43 @@ +import { shallow } from "enzyme"; +import * as React from "react"; + +import { Link } from "react-router-dom"; +import InfoCard from "./InfoCard"; + +it("should render a Card", () => { + const wrapper = shallow( + , + ); + expect(wrapper).toMatchSnapshot(); +}); + +it("should generate a dummy link if it's not provided", () => { + const wrapper = shallow( + , + ); + expect(wrapper.find(Link).props()).toMatchObject({ to: "#" }); +}); + +it("should avoid tags if they are not defined", () => { + const wrapper = shallow( + , + ); + expect(wrapper.find(".ListItem__content__info_tag")).not.toExist(); +}); diff --git a/dashboard/src/components/InfoCard/InfoCard.tsx b/dashboard/src/components/InfoCard/InfoCard.tsx new file mode 100644 index 00000000000..3eaf3306521 --- /dev/null +++ b/dashboard/src/components/InfoCard/InfoCard.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; + +import { Link } from "react-router-dom"; +import Card, { CardContent, CardIcon } from "../Card"; +import "./InfoCard.css"; + +export interface IServiceInstanceCardProps { + title: string; + info: string; + link?: string; + icon?: string; + tag1Class?: string; + tag1Content?: string; + tag2Class?: string; + tag2Content?: string; +} + +const InfoCard: React.SFC = props => { + const { title, link, icon, info, tag1Content, tag1Class, tag2Content, tag2Class } = props; + return ( + + + + +
+

{title}

+
+

{info}

+
+ {tag1Content && ( + + {tag1Content} + + )} + {tag2Content && ( + + {tag2Content} + + )} +
+
+
+
+ +
+ ); +}; + +export default InfoCard; diff --git a/dashboard/src/components/InfoCard/__snapshots__/InfoCard.test.tsx.snap b/dashboard/src/components/InfoCard/__snapshots__/InfoCard.test.tsx.snap new file mode 100644 index 00000000000..586e54d1870 --- /dev/null +++ b/dashboard/src/components/InfoCard/__snapshots__/InfoCard.test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render a Card 1`] = ` + + + + +
+

+ foo +

+
+

+ foobar +

+
+ + database + + + running + +
+
+
+
+ +
+`; diff --git a/dashboard/src/components/InfoCard/index.ts b/dashboard/src/components/InfoCard/index.ts new file mode 100644 index 00000000000..8176dc1ccfe --- /dev/null +++ b/dashboard/src/components/InfoCard/index.ts @@ -0,0 +1,3 @@ +import InfoCard from "./InfoCard"; + +export default InfoCard; diff --git a/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.scss b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.scss new file mode 100644 index 00000000000..8b6824fea20 --- /dev/null +++ b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.scss @@ -0,0 +1,7 @@ +.Provisioned { + background-color: #15cb2d; +} + +.Failed { + background-color: #cb1515; +} diff --git a/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.test.tsx b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.test.tsx new file mode 100644 index 00000000000..4086f4c288f --- /dev/null +++ b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.test.tsx @@ -0,0 +1,72 @@ +import { shallow } from "enzyme"; +import * as React from "react"; + +import InfoCard from "../InfoCard"; +import ServiceInstanceCard from "./ServiceInstanceCard"; + +it("should render a Card", () => { + const wrapper = shallow( + , + ); + expect(wrapper).toMatchSnapshot(); +}); + +it("should avoid the status tag if it's not defined", () => { + const wrapper = shallow( + , + ); + expect(wrapper.find(".ChartListItem__content__info_other")).not.toExist(); +}); + +describe("uses a different class and tag name depending on the status", () => { + [ + { + description: "Uses Provisioned when the status is alike", + status: "SuccessfullyProvisioned", + expected: "Provisioned", + }, + { + description: "Uses Failed when the status is alike", + status: "CreationFailed", + expected: "Failed", + }, + { + description: "Uses the raw status when it's unknown", + status: "StillProvisioning", + expected: "StillProvisioning", + }, + ].forEach(test => { + it(test.description, () => { + const wrapper = shallow( + , + ); + const card = wrapper.find(InfoCard); + expect(card).toExist(); + expect(card.props()).toMatchObject({ tag2Class: test.expected, tag2Content: test.expected }); + }); + }); +}); diff --git a/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.tsx b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.tsx new file mode 100644 index 00000000000..9ef8d292f31 --- /dev/null +++ b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCard.tsx @@ -0,0 +1,41 @@ +import * as React from "react"; + +import "../ChartList/ChartListItem.css"; +import InfoCard from "../InfoCard"; +import "./ServiceInstanceCard.css"; + +export interface IServiceInstanceCardProps { + name: string; + namespace: string; + servicePlanName: string; + statusReason: string | undefined; + link?: string; + icon?: string; +} + +function generalizeStatus(status: string) { + if (status.match(/Provisioned|Success/)) { + return "Provisioned"; + } + if (status.match(/Failed|Error/)) { + return "Failed"; + } + return status; +} + +const ServiceInstanceCard: React.SFC = props => { + const { name, namespace, link, icon, servicePlanName, statusReason } = props; + return ( + + ); +}; + +export default ServiceInstanceCard; diff --git a/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.test.tsx b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.test.tsx new file mode 100644 index 00000000000..b8517e149f7 --- /dev/null +++ b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.test.tsx @@ -0,0 +1,75 @@ +import { shallow } from "enzyme"; +import * as React from "react"; +import ServiceInstanceCard from "./ServiceInstanceCard"; +import ServiceInstanceCardList from "./ServiceInstanceCardList"; + +it("parses a list of instances and classes", () => { + const wrapper = shallow( + , + ); + expect(wrapper.find(ServiceInstanceCard).props()).toMatchObject({ + link: "/services/brokers/foo/instances/ns/foobar/foo", + name: "foo", + namespace: "foobar", + servicePlanName: "foo-plan", + statusReason: "Provisioned", + }); +}); diff --git a/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.tsx b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.tsx index 82a45b3e170..fc871cff9e2 100644 --- a/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.tsx +++ b/dashboard/src/components/ServiceInstanceList/ServiceInstanceCardList.tsx @@ -1,9 +1,11 @@ import * as React from "react"; -import { Link } from "react-router-dom"; import { IClusterServiceClass } from "shared/ClusterServiceClass"; import { IServiceInstance } from "shared/ServiceInstance"; -import Card, { CardContent, CardFooter, CardGrid, CardIcon } from "../Card"; +import { CardGrid } from "../Card"; +import "../ChartList/ChartListItem.css"; +import ServiceInstanceCard from "./ServiceInstanceCard"; +import "./ServiceInstanceCard.css"; export interface IServiceInstanceCardListProps { classes: IClusterServiceClass[]; @@ -20,7 +22,6 @@ const ServiceInstanceCardList: React.SFC = props instances.map(instance => { const conditions = [...instance.status.conditions]; const status = conditions.shift(); // first in list is most recent - const message = status ? status.message : ""; const svcClass = classes.find( potential => !!instance.spec.clusterServiceClassRef && @@ -31,28 +32,23 @@ const ServiceInstanceCardList: React.SFC = props svcClass && svcClass.spec.externalMetadata && svcClass.spec.externalMetadata.imageUrl; - const link = `/services/brokers/${broker}/instances/ns/${ - instance.metadata.namespace - }/${instance.metadata.name}`; + const link = + broker && + `/services/brokers/${broker}/instances/ns/${instance.metadata.namespace}/${ + instance.metadata.name + }`; - const card = ( - - - -
{instance.metadata.name}
-

- {instance.spec.clusterServicePlanExternalName} -

-

{message}

-
- - - Details - - -
+ return ( + ); - return card; })} diff --git a/dashboard/src/components/ServiceInstanceList/__snapshots__/ServiceInstanceCard.test.tsx.snap b/dashboard/src/components/ServiceInstanceList/__snapshots__/ServiceInstanceCard.test.tsx.snap new file mode 100644 index 00000000000..80a9d4b63df --- /dev/null +++ b/dashboard/src/components/ServiceInstanceList/__snapshots__/ServiceInstanceCard.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render a Card 1`] = ` + +`;