From e97b8fa75bd6414106f2b7d8fca4aafb34c82200 Mon Sep 17 00:00:00 2001 From: benstov Date: Wed, 29 May 2019 16:54:24 +0200 Subject: [PATCH] improvement(dashboard): add task and test info pane to overview page --- dashboard/src/components/button.tsx | 28 ++++++ .../{info-card.tsx => entity-card.tsx} | 8 +- .../{info-pane.tsx => entity-result.tsx} | 35 +++---- dashboard/src/components/module.tsx | 94 +++++++++++++++---- .../{node-info.tsx => entity-result.tsx} | 53 +++++++---- dashboard/src/containers/graph.tsx | 13 ++- dashboard/src/containers/overview.tsx | 24 ++++- dashboard/src/context/ui.tsx | 23 ++++- dashboard/src/styles/icons.scss | 2 +- dashboard/src/styles/variables.ts | 18 ++++ 10 files changed, 234 insertions(+), 64 deletions(-) create mode 100644 dashboard/src/components/button.tsx rename dashboard/src/components/{info-card.tsx => entity-card.tsx} (95%) rename dashboard/src/components/{info-pane.tsx => entity-result.tsx} (89%) rename dashboard/src/containers/{node-info.tsx => entity-result.tsx} (71%) diff --git a/dashboard/src/components/button.tsx b/dashboard/src/components/button.tsx new file mode 100644 index 0000000000..b9ce9ef9fa --- /dev/null +++ b/dashboard/src/components/button.tsx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 Garden Technologies, Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import styled from "@emotion/styled" +import { colors } from "../styles/variables" + +export const TertiaryButton = styled.button` + cursor: pointer; + padding: 0; + font-size: 0.8125rem; + line-height: 1.1875rem; + text-align: center; + letter-spacing: 0.01em; + color: ${colors.buttons.tertiary.default.color}; + background: none; + + &:hover { + color: ${colors.buttons.tertiary.hover.color}; + } + &:active{ + color: ${colors.buttons.tertiary.pressed.color}; + } + ` diff --git a/dashboard/src/components/info-card.tsx b/dashboard/src/components/entity-card.tsx similarity index 95% rename from dashboard/src/components/info-card.tsx rename to dashboard/src/components/entity-card.tsx index 7820255b4b..d49e08a754 100644 --- a/dashboard/src/components/info-card.tsx +++ b/dashboard/src/components/entity-card.tsx @@ -12,10 +12,10 @@ import { Entity } from "../containers/overview" import { colors } from "../styles/variables" import { Facebook } from "react-content-loader" -interface InfoCardProps { +interface EntityCardProps { type: EntityType } -const InfoCard = styled.div` +const EntityCard = styled.div` max-height: 13rem; background-color: ${props => (props && props.type && colors.cardTypes[props.type] || "white")}; margin-right: 1rem; @@ -105,7 +105,7 @@ export default ({ }: Props) => { return ( - +
{type.toUpperCase()} @@ -123,6 +123,6 @@ export default ({ )} {!isLoading && children} - + ) } diff --git a/dashboard/src/components/info-pane.tsx b/dashboard/src/components/entity-result.tsx similarity index 89% rename from dashboard/src/components/info-pane.tsx rename to dashboard/src/components/entity-result.tsx index ae31651f97..58ef994c59 100644 --- a/dashboard/src/components/info-pane.tsx +++ b/dashboard/src/components/entity-result.tsx @@ -12,11 +12,11 @@ import { capitalize } from "lodash" import { css } from "emotion" import moment from "moment" import styled from "@emotion/styled" -import Card from "../components/card" +import Card from "./card" import { colors } from "../styles/variables" -import { RenderedNode } from "garden-cli/src/config-graph" import { WarningNotification } from "./notifications" import { ActionIcon } from "./ActionIcon" +import { EntityResultSupportedTypes } from "../context/ui" const Term = styled.div` background-color: ${colors.gardenBlack}; @@ -100,28 +100,31 @@ const Header = styled.div` ` interface Props { - node: RenderedNode - clearGraphNodeSelection: () => void - onRefresh?: () => void - loading?: boolean + type: EntityResultSupportedTypes + name: string + moduleName: string output?: string | null startedAt?: Date | null completedAt?: Date | null duration?: string | null + onClose: () => void + onRefresh?: () => void + loading?: boolean } // TODO: Split up into something InfoPane and InfoPaneWithResults. Props are kind of messy. -export const InfoPane: React.FC = ({ - clearGraphNodeSelection, - loading, - onRefresh, - node, +export default ({ + type, + name, + moduleName, output, startedAt, completedAt, duration, -}) => { - const { name, moduleName, type } = node + onClose, + onRefresh, + loading, +}: Props) => { let outputEl: React.ReactNode = null if (output) { @@ -175,7 +178,7 @@ export const InfoPane: React.FC = ({ {onRefresh && ( )} - + @@ -211,8 +214,8 @@ export const InfoPane: React.FC = ({ {moment(completedAt).fromNow()} )} - - {(type === "test" || type === "run") && outputEl !== null && outputEl} + {/* we only show the output if has content and only for these types */} + {(type === "test" || type === "run" || type === "task") && outputEl !== null && outputEl} ) diff --git a/dashboard/src/components/module.tsx b/dashboard/src/components/module.tsx index 75dd252411..a0576b5ce9 100644 --- a/dashboard/src/components/module.tsx +++ b/dashboard/src/components/module.tsx @@ -8,11 +8,12 @@ import React, { useState, useContext } from "react" import styled from "@emotion/styled" +import moment from "moment" import { ModuleModel } from "../containers/overview" -import InfoCard from "./info-card" +import EntityCard from "./entity-card" import { UiStateContext } from "../context/ui" import Ingresses from "./ingresses" -import moment from "moment" +import { TertiaryButton } from "./button" const Module = styled.div` padding: 1.2rem; @@ -25,10 +26,10 @@ const Module = styled.div` max-width: 20rem; ` -type InfoCardsProps = { +type EntityCardsProps = { visible: boolean, } -const InfoCards = styled.div` +const EntityCards = styled.div` padding-top: .75rem; display: flex; flex-wrap: wrap; @@ -152,11 +153,32 @@ export default ({ }: ModuleProp) => { const { state: { overview: { filters } }, + actions: { selectEntity }, } = useContext(UiStateContext) const [showFullDescription, setDescriptionState] = useState(false) const toggleDescriptionState = () => (setDescriptionState(!showFullDescription)) + const handleSelectEntity = ( + { + moduleName, + entityName, + entityType, + }: + { + moduleName: string, + entityName: string, + entityType: "test" | "task", + }) => { + if (moduleName && entityName && entityType) { + selectEntity({ + type: entityType, + name: entityName, + module: moduleName, + }) + } + } + return (
@@ -174,9 +196,9 @@ export default ({ )} - + {services.map(service => ( - - + ))} - - + + {tests.map(test => ( - } +
+
+ +
+
-
+ ))} -
- + + {tasks.map(task => ( - } +
+
+ +
+
-
+ ))} -
- + ) } + +const ShowResultButton = ({ + entityName, + entityType, + moduleName, + onClick, +}: { + entityName: string, + entityType: "test" | "task", + moduleName: string, + onClick, +}) => { + const handleClick = () => onClick({ entityName, moduleName, entityType }) + return ( + + Show result + + ) +} diff --git a/dashboard/src/containers/node-info.tsx b/dashboard/src/containers/entity-result.tsx similarity index 71% rename from dashboard/src/containers/node-info.tsx rename to dashboard/src/containers/entity-result.tsx index f69ef47400..a66eee50b9 100644 --- a/dashboard/src/containers/node-info.tsx +++ b/dashboard/src/containers/entity-result.tsx @@ -9,12 +9,11 @@ import React, { useContext, useEffect } from "react" import { DataContext } from "../context/data" import { getDuration } from "../util/helpers" -import { UiStateContext } from "../context/ui" -import { RenderedNode } from "garden-cli/src/config-graph" -import { InfoPane } from "../components/info-pane" +import EntityResult from "../components/entity-result" import { TaskResultOutput } from "garden-cli/src/commands/get/get-task-result" import { TestResultOutput } from "garden-cli/src/commands/get/get-test-result" import { ErrorNotification } from "../components/notifications" +import { EntityResultSupportedTypes } from "../context/ui" const ErrorMsg = ({ error, type }) => ( @@ -22,10 +21,6 @@ const ErrorMsg = ({ error, type }) => ( ) -export interface Props { - node: RenderedNode -} - function prepareData(data: TestResultOutput | TaskResultOutput) { const startedAt = data.startedAt const completedAt = data.completedAt @@ -38,25 +33,28 @@ function prepareData(data: TestResultOutput | TaskResultOutput) { return { duration, startedAt, completedAt, output } } +interface Props { + type: EntityResultSupportedTypes + name: string + moduleName: string + onClose: () => void +} + /** * Returns the InfoPane for a given node type. * * If the node is of type "test" or "run", it loads the results as well. */ -export const NodeInfo: React.FC = ({ node }) => { - const { name, moduleName, type } = node +export default ({ name, moduleName, type, onClose }: Props) => { const { actions: { loadTestResult, loadTaskResult }, store: { testResult, taskResult }, } = useContext(DataContext) - const { - actions: { clearGraphNodeSelection }, - } = useContext(UiStateContext) const loadResults = () => { if (type === "test") { loadTestResult({ name, module: moduleName }, true) - } else if (type === "run") { + } else if (type === "run" || type === "task") { loadTaskResult({ name }, true) } } @@ -64,8 +62,15 @@ export const NodeInfo: React.FC = ({ node }) => { useEffect(loadResults, [name, moduleName]) // Here we just render the node data since only nodes of types test and run have results - if (!(type === "test" || type === "run")) { - return + if (!(type === "test" || type === "run" || type === "task")) { + return ( + + ) } const result = type === "test" ? testResult : taskResult @@ -76,16 +81,26 @@ export const NodeInfo: React.FC = ({ node }) => { // Loading. Either data hasn't been loaded at all or cache contains stale data if (!result.data || result.data.name !== name) { - return + return ( + + ) } // Render info pane with result data return ( - ) diff --git a/dashboard/src/containers/graph.tsx b/dashboard/src/containers/graph.tsx index d1d61895b4..73851a900e 100644 --- a/dashboard/src/containers/graph.tsx +++ b/dashboard/src/containers/graph.tsx @@ -12,8 +12,8 @@ import Graph from "../components/graph" import PageError from "../components/page-error" import { EventContext } from "../context/events" import { DataContext } from "../context/data" -import { UiStateContext, StackGraphSupportedFilterKeys } from "../context/ui" -import { NodeInfo } from "./node-info" +import { UiStateContext, StackGraphSupportedFilterKeys, EntityResultSupportedTypes } from "../context/ui" +import EntityResult from "./entity-result" import Spinner from "../components/spinner" import { Filters } from "../components/group-filter" import { capitalize } from "lodash" @@ -33,7 +33,7 @@ export default () => { useEffect(loadGraph, []) const { - actions: { selectGraphNode, stackGraphToggleItemsView }, + actions: { selectGraphNode, stackGraphToggleItemsView, clearGraphNodeSelection }, state: { selectedGraphNode, isSidebarOpen, stackGraph: { filters } }, } = useContext(UiStateContext) @@ -58,7 +58,12 @@ export default () => { if (node) { moreInfoPane = (
- +
) } diff --git a/dashboard/src/containers/overview.tsx b/dashboard/src/containers/overview.tsx index 2c721accb6..a0d13767e5 100644 --- a/dashboard/src/containers/overview.tsx +++ b/dashboard/src/containers/overview.tsx @@ -12,6 +12,7 @@ import styled from "@emotion/styled" import { ServiceIngress } from "garden-cli/src/types/service" import { RunState } from "garden-cli/src/commands/get/get-status" import Module from "../components/module" +import EntityResult from "../containers/entity-result" import { default as ViewIngress } from "../components/view-ingress" import { DataContext } from "../context/data" import Spinner from "../components/spinner" @@ -30,7 +31,6 @@ const Overview = styled.div` ` const Modules = styled.div` - margin-top: 1rem; display: flex; flex-wrap: wrap; overflow-y: scroll; @@ -79,11 +79,21 @@ export default () => { } = useContext(DataContext) const { - state: { overview: { selectedIngress } } } = useContext(UiStateContext) + state: { + overview: { selectedIngress, selectedEntity }, + }, + actions: { + selectEntity, + }, + } = useContext(UiStateContext) useEffect(loadConfig, []) useEffect(loadStatus, []) + const clearSelectedEntity = () => { + selectEntity(null) + } + const isLoadingConfig = !config.data || config.loading let modulesContainerComponent: React.ReactNode = null @@ -192,6 +202,16 @@ export default () => { } } + {selectedEntity && ( +
+ +
+ )} ) diff --git a/dashboard/src/context/ui.tsx b/dashboard/src/context/ui.tsx index 28d43a4542..3a78054994 100644 --- a/dashboard/src/context/ui.tsx +++ b/dashboard/src/context/ui.tsx @@ -14,6 +14,7 @@ interface UiState { isSidebarOpen: boolean overview: { selectedIngress: ServiceIngress | null, + selectedEntity: SelectedEntity | null, filters: { [key in OverviewSupportedFilterKeys]: boolean }, @@ -27,17 +28,24 @@ interface UiState { } export type SelectGraphNode = (node: string) => void +export type SelectEntity = (selectedEntity: SelectedEntity | null) => void export type SelectIngress = (ingress: ServiceIngress | null) => void - export type OverviewSupportedFilterKeys = "modules" | "modulesInfo" | "services" | "servicesInfo" | "tasks" | "tasksInfo" | "tests" | "testsInfo" export type StackGraphSupportedFilterKeys = Exclude +export type EntityResultSupportedTypes = StackGraphSupportedFilterKeys | "task" +export type SelectedEntity = { + type: EntityResultSupportedTypes, + name: string, + module: string, +} interface UiActions { toggleSidebar: () => void overviewToggleItemsView: (filterKey: OverviewSupportedFilterKeys) => void stackGraphToggleItemsView: (filterKey: StackGraphSupportedFilterKeys) => void selectGraphNode: SelectGraphNode + selectEntity: SelectEntity selectIngress: SelectIngress clearGraphNodeSelection: () => void } @@ -45,6 +53,7 @@ interface UiActions { const INITIAL_UI_STATE: UiState = { overview: { selectedIngress: null, + selectedEntity: null, filters: { modules: true, modulesInfo: true, @@ -126,6 +135,17 @@ const useUiState = () => { }, }) } + + const selectEntity = (selectedEntity: SelectedEntity | null) => { + setState({ + ...uiState, + overview: { + ...uiState.overview, + selectedEntity, + }, + }) + } + const clearGraphNodeSelection = () => { setState({ ...uiState, @@ -142,6 +162,7 @@ const useUiState = () => { selectGraphNode, clearGraphNodeSelection, selectIngress, + selectEntity, }, } } diff --git a/dashboard/src/styles/icons.scss b/dashboard/src/styles/icons.scss index 5c295ba756..e5a3f32d43 100644 --- a/dashboard/src/styles/icons.scss +++ b/dashboard/src/styles/icons.scss @@ -11,7 +11,7 @@ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTIwMCIgaGVpZ2h0PSIxMjAwIiB2aWV3Qm94PSIwIDAgMTIwMCAxMjAwIj48Zz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2MDAgNjAwKSBzY2FsZSgtMC42OSAwLjY5KSByb3RhdGUoMCkgdHJhbnNsYXRlKC02MDAgLTYwMCkiIHN0eWxlPSJmaWxsOiNkZmRmZGYiPjxzdmcgZmlsbD0iI2RmZGZkZiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA0OCA0OCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZmlsbD0iI2RmZGZkZiIgZD0iTTQ2LDM5LjhMMjAuMywxNi43Yy0wLjItMC4yLTAuMi0wLjUsMC0wLjZsMy40LTMuNGMwLjQtMC40LDAuNS0xLDAuMi0xLjRsLTIuNC0yLjRjLTAuMy0wLjMtMC4zLTAuOCwwLTEuMiAgYzEuMS0xLjUsMi44LTMuNiw1LjItNS41YzAuOS0wLjcsMC41LTItMC41LTEuNmMtMywwLjktNi43LDIuNC05LjQsNC45Yy0wLjEsMC0wLjEsMC4xLTAuMiwwLjFsLTkuOSw5LjljLTAuNCwwLjQtMC41LDEtMC4yLDEuNCAgbDAuOCwwLjhjMCwwLjItMC4xLDAuNS0wLjMsMC43Yy0wLjIsMC4yLTAuNSwwLjMtMC43LDAuM2wtMS4yLTAuNGMtMC4zLTAuMy0wLjktMC4zLTEuNCwwLjJsLTIuNCwyLjRjLTAuNCwwLjQtMC41LDEtMC4yLDEuNCAgTDcuMSwyOGMwLjMsMC4zLDAuOSwwLjMsMS40LTAuMmwyLjQtMi40YzAuNC0wLjQsMC41LTEsMC4yLTEuNGwtMC40LTEuMmMwLTAuMiwwLjEtMC41LDAuMy0wLjdjMC4yLTAuMiwwLjUtMC4zLDAuNy0wLjNsMC44LDAuOCAgYzAuMywwLjMsMC45LDAuMywxLjQtMC4ybDEuNy0xLjdjMC4yLTAuMiwwLjUtMC4yLDAuNiwwbDIzLjIsMjUuNmMxLjIsMS4zLDMuMiwxLjQsNC4zLDAuM2wyLjUtMi41QzQ3LjQsNDMsNDcuMyw0MSw0NiwzOS44eiI+PC9wYXRoPjwvc3ZnPjwvZz48L2c+PC9zdmc+"); } - &--run { + &--run, &--task { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTIwMCIgaGVpZ2h0PSIxMjAwIiB2aWV3Qm94PSIwIDAgMTIwMCAxMjAwIj48Zz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2MDAgNjAwKSBzY2FsZSgwLjY5IDAuNjkpIHJvdGF0ZSgwKSB0cmFuc2xhdGUoLTYwMCAtNjAwKSIgc3R5bGU9ImZpbGw6I2RmZGZkZiI+PHN2ZyBmaWxsPSIjZGZkZmRmIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+SWNvbnM8L3RpdGxlPjxjaXJjbGUgY3g9Ijc4LjM0IiBjeT0iMTQuMzYiIHI9IjkuMiI+PC9jaXJjbGU+PHBhdGggZD0iTTkwLDQxLjg5SDgwLjIyTDc4LjM1LDI5LjgyYTYuNjYsNi42NiwwLDAsMC0uMTgtMS4xOWwwLS4xMnYwYTYuNjgsNi42OCwwLDAsMC0xLjc4LTMuMDYsNi45LDYuOSwwLDAsMC0xLjc0LTEuMThsLTUuODctM2gwYTYuNDEsNi40MSwwLDAsMC0yLjk1LS43NEg2NWwtMTYuNTQuMmE1LDUsMCwwLDAtMy43OSwxLjc2TDM0LjE0LDM1YTUuMTIsNS4xMiwwLDAsMCwuMDgsNi44NSw1LDUsMCwwLDAsNy4zOC0uMjRsOS4xOS0xMC44Mkg1Ni40TDM5LjIzLDUyLjU2aDBsLjE4LS4yMywwLDAtLjE0LjE4LS4wOS4xMmgwTDI4LjQyLDY2LjI0SDEwYTUsNSwwLDAsMCwwLDEwaDIwLjhhNSw1LDAsMCwwLDMuOTEtMS44OWw5LjYzLTEyLjIsMTAuNzYsNy4xMWgwbDIuNiwxLjcyLTEwLjg3LDE2YTUuMTIsNS4xMiwwLDAsMCwuOTIsNi44OUE1LDUsMCwwLDAsNTUsOTIuNjdMNjguOCw3Mi4zOGE1LDUsMCwwLDAtMS4zNi02Ljk1bC05LjEzLTYsMTItMTYuMjVMNzEsNDcuNjNBNSw1LDAsMCwwLDc2LDUxLjg1SDkwYTUsNSwwLDAsMCwwLTEwWiI+PC9wYXRoPjwvc3ZnPjwvZz48L2c+PC9zdmc+"); } diff --git a/dashboard/src/styles/variables.ts b/dashboard/src/styles/variables.ts index 20aa58063f..991123dee6 100644 --- a/dashboard/src/styles/variables.ts +++ b/dashboard/src/styles/variables.ts @@ -76,4 +76,22 @@ export const colors = { test: "", run: "", }, + buttons: { + primary: {}, + secondary: {}, + tertiary: { + default: { + color: "#109CF1", + }, + hover: { + color: "#34AFF9", + }, + pressed: { + color: "#098EDF", + }, + disabled: { + color: "#109CF1", + }, + }, + }, }