From 92f244f9cc0b652d8701e184c6aa8e0c40c9ff66 Mon Sep 17 00:00:00 2001 From: Nicolas Inchauspe Date: Mon, 19 Jun 2023 17:47:34 +0200 Subject: [PATCH] console: fix operation details modal layout [DSF-424]: https://hasurahq.atlassian.net/browse/DSF-424?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9495 GitOrigin-RevId: 561bb3060c7b31bafa7f9a5be8e3fe08f7462f16 --- frontend/libs/console/legacy-ce/src/index.ts | 8 +- .../Services/Metrics/Common/CustomCopy.js | 51 ++- .../Services/Metrics/Common/Modal.js | 328 +++++++++--------- .../Services/Metrics/Common/Modal.stories.tsx | 94 +++++ 4 files changed, 301 insertions(+), 180 deletions(-) create mode 100644 frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.stories.tsx diff --git a/frontend/libs/console/legacy-ce/src/index.ts b/frontend/libs/console/legacy-ce/src/index.ts index 32c6100533c..13f87765f75 100644 --- a/frontend/libs/console/legacy-ce/src/index.ts +++ b/frontend/libs/console/legacy-ce/src/index.ts @@ -1,3 +1,7 @@ +import CommonScss from './lib/components/Common/Common.module.scss'; +import filterQueryScss from './lib/components/Common/FilterQuery/FilterQuery.module.scss'; +import tableScss from './lib/components/Common/TableCommon/Table.module.scss'; + import DragFoldTable from './lib/components/Common/TableCommon/DragFoldTable'; import Editor from './lib/components/Common/Layout/ExpandableEditor/Editor'; @@ -13,9 +17,6 @@ import * as EndpointNamedExps from './lib/Endpoints'; import * as ControlPlane from './lib/features/ControlPlane'; export * from './lib/utils/console-dev-tools'; -const CommonScss = require('./lib/components/Common/Common.module.scss'); -const filterQueryScss = require('./lib/components/Common/FilterQuery/FilterQuery.module.scss'); -const tableScss = require('./lib/components/Common/TableCommon/Table.module.scss'); export { ControlPlane }; @@ -85,6 +86,7 @@ export * from './lib/new-components/Button/'; export * from './lib/new-components/Tooltip/'; export * from './lib/new-components/Badge/'; export * from './lib/new-components/Dialog'; +export * from './lib/new-components/Toasts'; export { default as dataHeaders } from './lib/components/Services/Data/Common/Headers'; export { handleMigrationErrors } from './lib/components/Services/Data/TableModify/ModifyActions'; export { loadMigrationStatus } from './lib/components/Main/Actions'; diff --git a/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/CustomCopy.js b/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/CustomCopy.js index cfc73f0bbc4..37df8945421 100644 --- a/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/CustomCopy.js +++ b/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/CustomCopy.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { hasuraToast } from '@hasura/console-legacy-ce'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import { EditIcon } from './EditIcon'; @@ -6,7 +7,14 @@ import { EditIcon } from './EditIcon'; import styles from '../Metrics.module.scss'; import copyImg from '../images/copy.svg'; -const CustomCopy = ({ label, copy, onEdit }) => { +const CustomCopy = ({ + label, + copy, + onEdit, + displayColon = true, + displayAcknowledgement = true, + contentMaxHeight, +}) => { const [isCopied, toggle] = useState(false); const onCopy = () => { toggle(true); @@ -14,17 +22,24 @@ const CustomCopy = ({ label, copy, onEdit }) => { }; const renderCopyIcon = () => { if (isCopied) { - // To suri modify it to have some kind of tooltip saying copied - return ( -
- {'Copy -
Copied
-
- ); + if (displayAcknowledgement) { + // To suri modify it to have some kind of tooltip saying copied + return ( +
+ {'Copy +
Copied
+
+ ); + } else { + hasuraToast({ + type: 'success', + title: 'Copied!', + }); + } } return {'Copy; }; @@ -32,7 +47,10 @@ const CustomCopy = ({ label, copy, onEdit }) => {
- {label}: + + {label} + {displayColon ? ':' : ''} + {onEdit && ( {
-
+
{copy}
diff --git a/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.js b/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.js index 32ff35a5709..fb63c41e0b5 100644 --- a/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.js +++ b/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.js @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import CustomCopy from './CustomCopy'; import { Dialog } from '@hasura/console-legacy-ce'; @@ -55,6 +56,22 @@ const TraceGraph = props => { return root ? : null; }; +const LabelValue = props => { + const { label, value, className } = props; + return ( +
+
+
+ {label} +
+
+ {value} +
+
+
+ ); +}; + const Modal = props => { const { onHide, data, nullData, configData } = props; @@ -66,7 +83,7 @@ const Modal = props => { analyzeVariables = configData.analyze_query_variables; } - const renderSessonVars = () => { + const renderSessionVars = () => { const { user_vars: userVars } = data; const userVarKeys = Object.keys(userVars); const sessionVariables = {}; @@ -76,12 +93,15 @@ const Modal = props => { }); } return ( -
+
+ } copy={JSON.stringify(sessionVariables, null, 2)} + displayColon={false} + displayAcknowledgement={false} + contentMaxHeight="200px" />
); @@ -134,11 +154,19 @@ const Modal = props => { d = 'Enable response body analysis'; } return ( -
- +
+ + } + copy={d} + displayColon={false} + displayAcknowledgement={false} + contentMaxHeight="200px" + />
); } @@ -150,7 +178,7 @@ const Modal = props => { const formattedError = JSON.stringify(requestError, null, 2); return (
-
ERROR:
+
{formattedError}
@@ -166,14 +194,37 @@ const Modal = props => { if (query) { const { query: graphQLQuery, variables } = query; const queryElement = ( - +
+ + } + copy={graphQLQuery} + displayColon={false} + displayAcknowledgement={false} + contentMaxHeight="200px" + /> +
); renderItem.push(queryElement); if (variables && analyzeVariables) { try { const formattedVar = JSON.stringify(variables, null, 2); const variablesElement = [ - , +
+ + } + copy={formattedVar} + displayColon={false} + displayAcknowledgement={false} + contentMaxHeight="200px" + /> +
, ]; renderItem.push(variablesElement); } catch (e) { @@ -189,10 +240,17 @@ const Modal = props => { if (generatedSql) { try { return ( - +
+ + } + copy={JSON.stringify(generatedSql, null, 2)} + displayColon={false} + displayAcknowledgement={false} + contentMaxHeight="200px" + /> +
); } catch (e) { console.error(e); @@ -205,7 +263,19 @@ const Modal = props => { if (!requestHeaders) return null; try { const stringified = JSON.stringify(requestHeaders, null, 2); - return ; + return ( +
+ + } + copy={stringified} + displayColon={false} + displayAcknowledgement={false} + contentMaxHeight="200px" + /> +
+ ); } catch (e) { console.log(e); } @@ -217,154 +287,86 @@ const Modal = props => { onClose={onHide} hasBackdrop size="xxxl" - title="Inspect" + title={ +
+
+
+ Operation + {operationName || 'N/A'} +
+
+ Id + {operationId || 'N/A'} +
+
+
+ +
+
+ } > -
-
-
+
+ + + {transport === 'ws' ? ( + <> + + + {operationType === 'subscription' ? ( + + ) : null} + + ) : null} + + + -
-
-
- TIMESTAMP: {new Date(time).toLocaleString()} -
- {/* -
- ID: {id} -
- */} -
- OPERATION NAME: {operationName || 'N/A'} -
-
- OPERATION ID: {operationId || 'N/A'} -
-
- REQUEST ID: {requestId} -
-
- TRANSPORT: {transport} -
- {transport === 'ws' ? ( -
-
- WEBSOCKET ID: {websocketId} -
-
- WEBSOCKET OPERATION ID:{' '} - {websocketOperationId} -
- {operationType === 'subscription' ? ( -
- SUBSCRIPTION STATUS {status} -
- ) : null} -
- ) : null} -
-
- {/* -
- CLIENT ID: {clientId} -
- */} -
- CLIENT NAME: {client_name || 'N/A'} -
-
- ROLE: {role || 'N/A'} -
-
-
- {renderSessonVars()} -
-
-
- EXECUTION TIME:{' '} - - {('execution_time' in transformedVals && - transformedVals.execution_time(executionTime)) || - executionTime}{' '} - ms - -
-
- TIMING -
- {trace && trace?.length && } -
-
-
- REQUEST SIZE:{' '} - - {(transformedVals.hasOwnProperty('request_size') && - transformedVals.request_size(requestSize)) || - requestSize}{' '} - kB - -
- RESPONSE SIZE:{' '} - - {('response_size' in transformedVals && - transformedVals.response_size(responseSize)) || - responseSize}{' '} - kB - -
-
-
- {renderError()} - {renderResponseAnalysis()} -
-
- {renderOperationQuery()} - {renderGeneratedSql()} - {renderRequestHeaders()} - {/* -
-
- GENERATED SQL:{' '} - -
-
-
-
-
- */} + /> + + {renderSessionVars()} +
+ + } + />
+ {renderError()} + {renderResponseAnalysis()} +
+
+ {renderOperationQuery()} + {renderGeneratedSql()} + {renderRequestHeaders()}
diff --git a/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.stories.tsx b/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.stories.tsx new file mode 100644 index 00000000000..f561c2d3d4e --- /dev/null +++ b/frontend/libs/console/legacy-ee/src/lib/components/Services/Metrics/Common/Modal.stories.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { StoryObj, Meta } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import Modal from './Modal'; + +const DATA = { + operation: { + time: '2023-06-07T12:11:01.134+00:00', + request_id: 'f09cde5e9616177f77d61c78479ed633', + operation_id: '7116865cef017c3b09e5c9271b0e182a6dcf4c01', + operation_name: 'IntrospectionQuery', + client_name: null, + user_role: 'admin', + execution_time: 0.004819542, + request_size: 1728, + response_size: 1152, + error: null, + query: { + query: + '\n query IntrospectionQuery {\n __schema {\n queryType { name }\n mutationType { name }\n subscriptionType { name }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n }\n\n fragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n }\n\n fragment InputValue on __InputValue {\n name\n description\n type { ...TypeRef }\n defaultValue\n }\n\n fragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n }\n ', + variables: { + review: 4, + }, + }, + user_vars: { + 'x-hasura-role': 'admin', + }, + transport: 'ws', + + request_headers: { + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate', + 'Accept-Language': 'en', + 'Cache-Control': 'no-cache', + Connection: 'close', + 'Content-Length': '1728', + 'Content-Type': 'application/json', + Host: 'tenant1.nginx.hasura.me', + Origin: 'http://cloud.lux-dev.hasura.me', + Pragma: 'no-cache', + Referer: 'http://cloud.lux-dev.hasura.me/', + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + 'X-Forwarded-Host': 'tenant1.nginx.hasura.me', + 'X-Forwarded-Port': '80', + 'X-Forwarded-Proto': 'http', + 'X-Forwarded-Server': 'ccced495ab5e', + 'X-NginX-Proxy': 'true', + 'X-Request-Id': 'f09cde5e9616177f77d61c78479ed633', + }, + websocket_id: 'c0cjsdf09cde5e9616177f77d61c78479ed3', + ws_operation_id: 'c1984fgb9cde5616177f77d61c78479ed3', + kind: null, + request_mode: 'single', + generated_sql: 'SELECT * FROM public.author', + trace: [ + { + id: '429793da-9c7d-450e-9a9d-d50283b446ca', + name: '/v1/graphql', + parent_id: null, + span_id: '7903618144220886803', + time: '2023-06-07T15:14:02.774+00:00', + duration: 3398250, + start: '2023-06-07T15:14:02.878461+00:00', + meta: { + request_id: '05e83aff90e604b68cd6daa311105f78', + }, + }, + { + id: 'a1c38f1d-bd74-4660-a849-0a1e4924ebe9', + name: 'Query', + parent_id: '7903618144220886803', + span_id: '2425353977187282560', + time: '2023-06-07T15:14:02.774+00:00', + duration: 49791, + start: '2023-06-07T15:14:02.881081+00:00', + meta: {}, + }, + ], + }, +}; + +export default { + component: Modal, +} as Meta; + +export const Basic: StoryObj = { + args: { + data: DATA.operation, + configData: {}, + onHide: action('onHide'), + }, +};