From 461bc94a0bfe97580256e290c47fbab40210bf1b Mon Sep 17 00:00:00 2001 From: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com> Date: Thu, 31 Mar 2022 19:05:10 +0000 Subject: [PATCH] UX Trace Screen (#1781) --- portal-ui/src/icons/FilterIcon.tsx | 141 ++++ portal-ui/src/icons/index.ts | 1 + .../SummaryItems/RBIconButton.tsx | 2 + .../screens/Console/Common/IconsScreen.tsx | 5 + portal-ui/src/screens/Console/Trace/Trace.tsx | 659 +++++++++++------- portal-ui/tests/permissions-1/trace.ts | 20 +- 6 files changed, 584 insertions(+), 244 deletions(-) create mode 100644 portal-ui/src/icons/FilterIcon.tsx diff --git a/portal-ui/src/icons/FilterIcon.tsx b/portal-ui/src/icons/FilterIcon.tsx new file mode 100644 index 000000000..57e81fdc7 --- /dev/null +++ b/portal-ui/src/icons/FilterIcon.tsx @@ -0,0 +1,141 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { SVGProps } from "react"; + +const FilterIcon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; + +export default FilterIcon; diff --git a/portal-ui/src/icons/index.ts b/portal-ui/src/icons/index.ts index 2ee8b3fc5..728b12e24 100644 --- a/portal-ui/src/icons/index.ts +++ b/portal-ui/src/icons/index.ts @@ -181,3 +181,4 @@ export { default as LicenseDocIcon } from "./LicenseDocIcon"; export { default as SelectAllIcon } from "./SelectAllIcon"; export { default as BackIcon } from "./BackIcon"; export { default as DeleteNonCurrentIcon } from "./DeleteNonCurrentIcon"; +export { default as FilterIcon } from "./FilterIcon"; diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx index 313324e4f..04ac52949 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx @@ -79,11 +79,13 @@ const RBIconButton = (props: RBIconProps) => { disabled = false, tooltip, icon = null, + className = "", ...restProps } = props; return ( {
BackIcon + + +
+ FilterIcon +

Menu Icons

. import React, { Fragment, useState } from "react"; -import { Button, Grid, TextField } from "@mui/material"; +import { Box, Grid } from "@mui/material"; import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket"; import { AppState } from "../../../store"; import { connect } from "react-redux"; @@ -43,6 +43,9 @@ import PageHeader from "../Common/PageHeader/PageHeader"; import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper"; import moment from "moment/moment"; import PageLayout from "../Common/Layout/PageLayout"; +import { FilterIcon } from "../../../icons"; +import RBIconButton from "../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; const styles = (theme: Theme) => createStyles({ @@ -89,9 +92,18 @@ const styles = (theme: Theme) => }, formBox: { border: "1px solid #EAEAEA", - padding: 15, + padding: 25, marginBottom: 15, }, + traceCheckedIcon: { + width: "14px", + height: "14px", + marginLeft: "0px", + }, + unCheckedIcon: { + width: "14px", + height: "14px", + }, midColumnCheckboxes: { display: "flex", }, @@ -139,6 +151,8 @@ const Trace = ({ const [os, setOS] = useState(false); const [errors, setErrors] = useState(false); + const [toggleFilter, setToggleFilter] = useState(false); + const startTrace = () => { traceResetMessages(); const url = new URL(window.location.toString()); @@ -201,240 +215,417 @@ const Trace = ({ - - - { - setStatusCode(e.target.value); - }} - disabled={traceStarted} - variant="standard" - /> - { - setMethod(e.target.value); - }} - disabled={traceStarted} - variant="standard" - /> - { - setFunc(e.target.value); - }} - variant="standard" - /> - { - setPath(e.target.value); - }} - variant="standard" - /> - { - setThreshold(parseInt(e.target.value)); - }} - variant="standard" - /> - - -
Calls to trace:
-
- { - setAll(item.target.checked); - }} - value={"all"} - disabled={traceStarted} - /> - { - setS3(item.target.checked); - }} - value={"s3"} - disabled={all || traceStarted} - /> - { - setInternal(item.target.checked); - }} - value={"internal"} - disabled={all || traceStarted} - /> - { - setStorage(item.target.checked); - }} - value={"storage"} - disabled={all || traceStarted} - /> - { - setOS(item.target.checked); - }} - value={"os"} - disabled={all || traceStarted} - /> - -       |       - -
- { - setErrors(item.target.checked); - }} - value={"only_errors"} - disabled={traceStarted} - /> -
- - {!traceStarted && ( - - )} - {traceStarted && ( - - )} - -
+ + - { - const timeParse = new Date(time); - return timeFromDate(timeParse); + "& .trace-checkbox-label": { + fontSize: "14px", + fontWeight: "normal", + }, + }} + > + + Calls to Trace + + - `${fullElement.statusCode} ${fullElement.statusMsg}`, - renderFullObject: true, - }, - { - label: "Location", - elementKey: "configuration_id", - renderFunction: (fullElement: TraceMessage) => - `${fullElement.host} ${fullElement.client}`, - renderFullObject: true, - }, - { - label: "Load Time", - elementKey: "callStats.duration", - globalClass: classes.timeItem, - }, - { - label: "Upload", - elementKey: "callStats.rx", - renderFunction: niceBytes, - globalClass: classes.sizeItem, - }, - { - label: "Download", - elementKey: "callStats.tx", - renderFunction: niceBytes, - globalClass: classes.sizeItem, - }, - ]} - isLoading={false} - records={messages} - entityName="Traces" - idField="api" - customEmptyMessage={ - traceStarted - ? "No Traced elements received yet" - : "Trace is not started yet" - } - customPaperHeight={classes.tableWrapper} - autoScrollToBottom - /> + }} + > + + { + setAll(item.target.checked); + }} + value={"all"} + disabled={traceStarted} + overrideLabelClasses="trace-checkbox-label" + classes={{ + checkedIcon: classes.traceCheckedIcon, + unCheckedIcon: classes.unCheckedIcon, + }} + /> + { + setS3(item.target.checked); + }} + value={"s3"} + disabled={traceStarted} + overrideLabelClasses="trace-checkbox-label" + classes={{ + checkedIcon: classes.traceCheckedIcon, + unCheckedIcon: classes.unCheckedIcon, + }} + /> + { + setInternal(item.target.checked); + }} + value={"internal"} + disabled={all || traceStarted} + overrideLabelClasses="trace-checkbox-label" + classes={{ + checkedIcon: classes.traceCheckedIcon, + unCheckedIcon: classes.unCheckedIcon, + }} + /> + { + setStorage(item.target.checked); + }} + value={"storage"} + disabled={all || traceStarted} + overrideLabelClasses="trace-checkbox-label" + classes={{ + checkedIcon: classes.traceCheckedIcon, + unCheckedIcon: classes.unCheckedIcon, + }} + /> + { + setOS(item.target.checked); + }} + value={"os"} + disabled={all || traceStarted} + overrideLabelClasses="trace-checkbox-label" + classes={{ + checkedIcon: classes.traceCheckedIcon, + unCheckedIcon: classes.unCheckedIcon, + }} + /> + + + { + setToggleFilter(!toggleFilter); + }} + text={"Filters"} + icon={} + color={"primary"} + variant={"outlined"} + className={"filters-toggle-button"} + style={{ + width: "118px", + background: toggleFilter ? "rgba(8, 28, 66, 0.04)" : "", + }} + /> + + {!traceStarted && ( + + )} + {traceStarted && ( + + )} + + + + {toggleFilter ? ( + + + { + setStatusCode(e.target.value); + }} + disabled={traceStarted} + /> + + { + setFunc(e.target.value); + }} + disabled={traceStarted} + /> + + { + setMethod(e.target.value); + }} + disabled={traceStarted} + /> + + + + { + setPath(e.target.value); + }} + disabled={traceStarted} + /> + + + { + setThreshold(parseInt(e.target.value)); + }} + disabled={traceStarted} + /> + + + + { + setErrors(item.target.checked); + }} + value={"only_errors"} + disabled={traceStarted} + overrideLabelClasses="trace-checkbox-label" + classes={{ + checkedIcon: classes.traceCheckedIcon, + unCheckedIcon: classes.unCheckedIcon, + }} + /> + + + ) : null} + + + + Trace Results + + + + { + const timeParse = new Date(time); + return timeFromDate(timeParse); + }, + globalClass: classes.timeItem, + }, + { label: "Name", elementKey: "api" }, + { + label: "Status", + elementKey: "", + renderFunction: (fullElement: TraceMessage) => + `${fullElement.statusCode} ${fullElement.statusMsg}`, + renderFullObject: true, + }, + { + label: "Location", + elementKey: "configuration_id", + renderFunction: (fullElement: TraceMessage) => + `${fullElement.host} ${fullElement.client}`, + renderFullObject: true, + }, + { + label: "Load Time", + elementKey: "callStats.duration", + globalClass: classes.timeItem, + }, + { + label: "Upload", + elementKey: "callStats.rx", + renderFunction: niceBytes, + globalClass: classes.sizeItem, + }, + { + label: "Download", + elementKey: "callStats.tx", + renderFunction: niceBytes, + globalClass: classes.sizeItem, + }, + ]} + isLoading={false} + records={messages} + entityName="Traces" + idField="api" + customEmptyMessage={ + traceStarted + ? "No Traced elements received yet" + : "Trace is not started yet" + } + customPaperHeight={classes.tableWrapper} + autoScrollToBottom + /> +
diff --git a/portal-ui/tests/permissions-1/trace.ts b/portal-ui/tests/permissions-1/trace.ts index 84cee6f49..fe9a93393 100644 --- a/portal-ui/tests/permissions-1/trace.ts +++ b/portal-ui/tests/permissions-1/trace.ts @@ -16,11 +16,11 @@ import * as roles from "../utils/roles"; import * as elements from "../utils/elements"; -import { - monitoringElement, - supportElement, - traceElement, -} from "../utils/elements-menu"; +import { monitoringElement, traceElement } from "../utils/elements-menu"; +import { Selector } from "testcafe"; + +export const traceStartButton = Selector('[data-test-id="trace-start-button"]'); +export const traceStopButton = Selector('[data-test-id="trace-stop-button"]'); fixture("For user with Trace permissions") .page("http://localhost:9090") @@ -48,14 +48,14 @@ test("Trace page can be opened", async (t) => { test("Start button can be clicked", async (t) => { await t .navigateTo("http://localhost:9090/tools/trace") - .click(elements.startButton); + .click(traceStartButton); }); test("Stop button appears after Start button has been clicked", async (t) => { - const stopButtonExists = elements.stopButton.exists; + const stopButtonExists = traceStopButton.exists; await t .navigateTo("http://localhost:9090/tools/trace") - .click(elements.startButton) + .click(traceStartButton) .expect(stopButtonExists) .ok(); }); @@ -63,6 +63,6 @@ test("Stop button appears after Start button has been clicked", async (t) => { test("Stop button can be clicked after Start button has been clicked", async (t) => { await t .navigateTo("http://localhost:9090/tools/trace") - .click(elements.startButton) - .click(elements.stopButton); + .click(traceStartButton) + .click(traceStopButton); });