Web console: show stages for MSQ compaction tasks (#18545)
* show stages for MSQ compaction tasks
* more robust
diff --git a/web-console/src/components/index.ts b/web-console/src/components/index.ts
index 1342c53..849cc47 100644
--- a/web-console/src/components/index.ts
+++ b/web-console/src/components/index.ts
@@ -51,6 +51,7 @@
export * from './rule-editor/rule-editor';
export * from './segment-timeline/segment-timeline';
export * from './show-json/show-json';
+export * from './show-json-or-stages/show-json-or-stages';
export * from './show-log/show-log';
export * from './show-value/show-value';
export * from './splitter-layout/splitter-layout';
diff --git a/web-console/src/components/show-json-or-stages/__snapshots__/show-json-or-stages.spec.tsx.snap b/web-console/src/components/show-json-or-stages/__snapshots__/show-json-or-stages.spec.tsx.snap
new file mode 100644
index 0000000..052bb7e
--- /dev/null
+++ b/web-console/src/components/show-json-or-stages/__snapshots__/show-json-or-stages.spec.tsx.snap
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ShowJsonOrStages matches snapshot 1`] = `
+<div
+ class="show-json-or-stages"
+>
+ <div
+ class="top-actions"
+ >
+ <div
+ class="bp5-button-group right-buttons"
+ >
+ <button
+ class="bp5-button bp5-disabled bp5-minimal"
+ disabled=""
+ tabindex="-1"
+ type="button"
+ >
+ <span
+ class="bp5-button-text"
+ >
+ Download
+ </span>
+ </button>
+ <button
+ class="bp5-button bp5-disabled bp5-minimal"
+ disabled=""
+ tabindex="-1"
+ type="button"
+ >
+ <span
+ class="bp5-button-text"
+ >
+ Copy
+ </span>
+ </button>
+ <button
+ class="bp5-button bp5-disabled bp5-minimal"
+ disabled=""
+ tabindex="-1"
+ type="button"
+ >
+ <span
+ class="bp5-button-text"
+ >
+ View raw
+ </span>
+ </button>
+ </div>
+ </div>
+ <div
+ class="main-area"
+ >
+ <div
+ class="loader"
+ >
+ <div
+ class="loader-logo"
+ >
+ <svg
+ viewBox="0 0 100 100"
+ >
+ <path
+ class="one"
+ d="M54.2,69.8h-2.7c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h2.7c11.5,0,23.8-7.4,23.8-23.7
+ c0-9.1-6.9-15.8-16.4-15.8H38c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h23.6c5.3,0,10.1,1.9,13.6,5.3c3.5,3.4,5.4,8,5.4,13.1
+ c0,6.6-2.3,13-6.3,17.7C69.5,66.8,62.5,69.8,54.2,69.8z"
+ />
+ <path
+ class="two"
+ d="M55.7,59.5h-26c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h26c7.5,0,11.5-5.8,11.5-11.5
+ c0-4.2-3.2-7.3-7.7-7.3h-26c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h26c5.9,0,10.3,4.3,10.3,9.9c0,3.7-1.3,7.2-3.7,9.8
+ C63.5,58,59.9,59.5,55.7,59.5z"
+ />
+ <path
+ class="three"
+ d="M27.2,38h-6.3c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h6.3c0.7,0,1.3,0.6,1.3,1.3S27.9,38,27.2,38z"
+ />
+ <path
+ class="four"
+ d="M45.1,69.8h-5.8c-0.7,0-1.3-0.6-1.3-1.3c0-0.7,0.6-1.3,1.3-1.3h5.8c0.7,0,1.3,0.6,1.3,1.3
+ C46.4,69.2,45.8,69.8,45.1,69.8z"
+ />
+ </svg>
+ </div>
+ </div>
+ </div>
+</div>
+`;
diff --git a/web-console/src/components/show-json-or-stages/show-json-or-stages.scss b/web-console/src/components/show-json-or-stages/show-json-or-stages.scss
new file mode 100644
index 0000000..f4c7c60
--- /dev/null
+++ b/web-console/src/components/show-json-or-stages/show-json-or-stages.scss
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.show-json-or-stages {
+ position: relative;
+ height: 100%;
+
+ .top-actions {
+ text-align: right;
+ padding-bottom: 10px;
+
+ & > * {
+ display: inline-block;
+ }
+ }
+
+ .main-area {
+ position: absolute;
+ width: 100%;
+ top: 40px;
+ bottom: 0;
+
+ textarea {
+ height: 100%;
+ width: 100%;
+ resize: none;
+ }
+
+ .loader {
+ position: relative;
+ }
+
+ .execution-stages-pane {
+ height: 100%;
+ }
+ }
+}
diff --git a/web-console/src/components/show-json-or-stages/show-json-or-stages.spec.tsx b/web-console/src/components/show-json-or-stages/show-json-or-stages.spec.tsx
new file mode 100644
index 0000000..32660e6
--- /dev/null
+++ b/web-console/src/components/show-json-or-stages/show-json-or-stages.spec.tsx
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render } from '@testing-library/react';
+
+import { ShowJsonOrStages } from './show-json-or-stages';
+
+describe('ShowJsonOrStages', () => {
+ it('matches snapshot', () => {
+ const showJsonOrStages = <ShowJsonOrStages endpoint="test" downloadFilename="test" />;
+ const { container } = render(showJsonOrStages);
+ expect(container.firstChild).toMatchSnapshot();
+ });
+});
diff --git a/web-console/src/components/show-json-or-stages/show-json-or-stages.tsx b/web-console/src/components/show-json-or-stages/show-json-or-stages.tsx
new file mode 100644
index 0000000..8010104
--- /dev/null
+++ b/web-console/src/components/show-json-or-stages/show-json-or-stages.tsx
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Button, ButtonGroup, Intent } from '@blueprintjs/core';
+import copy from 'copy-to-clipboard';
+import * as JSONBig from 'json-bigint-native';
+import React from 'react';
+import AceEditor from 'react-ace';
+
+import { Execution } from '../../druid-models';
+import { useQueryManager } from '../../hooks';
+import { Api, AppToaster, UrlBaser } from '../../singletons';
+import { downloadFile } from '../../utils';
+import { ExecutionStagesPane } from '../../views/workbench-view/execution-stages-pane/execution-stages-pane';
+import { Loader } from '../loader/loader';
+
+import './show-json-or-stages.scss';
+
+export interface ShowJsonOrStagesProps {
+ endpoint: string;
+ transform?: (x: any) => any;
+ downloadFilename?: string;
+}
+
+export const ShowJsonOrStages = React.memo(function ShowJsonOrStages(props: ShowJsonOrStagesProps) {
+ const { endpoint, transform, downloadFilename } = props;
+
+ const [jsonState] = useQueryManager<null, [string, Execution | undefined]>({
+ processQuery: async (_, signal) => {
+ const resp = await Api.instance.get(endpoint, { signal });
+ let data = resp.data;
+ if (transform) data = transform(data);
+
+ let execution: Execution | undefined;
+ if (data.multiStageQuery) {
+ try {
+ execution = Execution.fromTaskReport(data);
+ } catch (e) {
+ console.error(`Could not parse task report as MSQ execution: ${e.message}`);
+ }
+ }
+
+ return [
+ typeof data === 'string' ? data : JSONBig.stringify(data, undefined, 2),
+ execution,
+ ] as [string, Execution | undefined];
+ },
+ initQuery: null,
+ });
+
+ const [jsonValue, execution] = jsonState.data || [''];
+ return (
+ <div className="show-json-or-stages">
+ <div className="top-actions">
+ <ButtonGroup className="right-buttons">
+ {downloadFilename && (
+ <Button
+ disabled={jsonState.loading}
+ text="Download"
+ minimal
+ onClick={() => downloadFile(jsonValue, 'json', downloadFilename)}
+ />
+ )}
+ <Button
+ text="Copy"
+ minimal
+ disabled={jsonState.loading}
+ onClick={() => {
+ copy(jsonValue, { format: 'text/plain' });
+ AppToaster.show({
+ message: 'JSON value copied to clipboard',
+ intent: Intent.SUCCESS,
+ });
+ }}
+ />
+ <Button
+ text="View raw"
+ disabled={!jsonValue}
+ minimal
+ onClick={() => window.open(UrlBaser.base(endpoint), '_blank')}
+ />
+ </ButtonGroup>
+ </div>
+
+ <div className="main-area">
+ {jsonState.loading ? (
+ <Loader />
+ ) : execution ? (
+ <ExecutionStagesPane execution={execution} />
+ ) : (
+ <AceEditor
+ mode="hjson"
+ theme="solarized_dark"
+ readOnly
+ fontSize={12}
+ width="100%"
+ height="100%"
+ showPrintMargin={false}
+ showGutter={false}
+ value={!jsonState.error ? jsonValue : jsonState.getErrorMessage()}
+ style={{}}
+ />
+ )}
+ </div>
+ </div>
+ );
+});
diff --git a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
index 9edc5d9..5638971 100644
--- a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
+++ b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
@@ -18,7 +18,7 @@
import React, { useState } from 'react';
-import { ShowJson, ShowLog } from '../../components';
+import { ShowJson, ShowJsonOrStages, ShowLog } from '../../components';
import { Api } from '../../singletons';
import { deepGet } from '../../utils';
import type { BasicAction } from '../../utils/basic-action';
@@ -83,7 +83,7 @@
/>
)}
{activeTab === 'report' && (
- <ShowJson
+ <ShowJsonOrStages
endpoint={`${taskEndpointBase}/reports`}
transform={x => deepGet(x, 'ingestionStatsAndErrors.payload') || x}
downloadFilename={`task-reports-${taskId}.json`}
diff --git a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
index d7c447d..e4717c6 100644
--- a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
+++ b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
@@ -157,7 +157,7 @@
execution: Execution;
onErrorClick?(): void;
onWarningClick?(): void;
- goToTask(taskId: string): void;
+ goToTask?(taskId: string): void;
}
export const ExecutionStagesPane = React.memo(function ExecutionStagesPane(
@@ -245,8 +245,10 @@
Header: 'Worker',
id: 'worker',
accessor: d => d.index,
+ className: goToTask ? undefined : 'padded',
width: 95,
Cell({ value }) {
+ if (!goToTask) return `Worker${value}`;
const taskId = `${execution.id}-worker${value}_0`;
return (
<TableClickableCell