Web console: Compaction history dialog (#13861)

* initial renames

* add comaction history diff

* final fixes

* update snapshots

* use maps

* update test
This commit is contained in:
Vadim Ogievetsky 2023-03-06 11:52:25 -08:00 committed by GitHub
parent cd4ad5123a
commit 38b6373bf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 319 additions and 84 deletions

View File

@ -5693,7 +5693,7 @@ license_category: binary
module: web-console module: web-console
license_name: Apache License version 2.0 license_name: Apache License version 2.0
copyright: Imply Data copyright: Imply Data
version: 0.17.4 version: 0.17.5
--- ---

View File

@ -99,7 +99,7 @@ export class DatasourcesOverview {
private async openCompactionConfigurationDialog(datasourceName: string): Promise<void> { private async openCompactionConfigurationDialog(datasourceName: string): Promise<void> {
await this.openEditActions(datasourceName); await this.openEditActions(datasourceName);
await this.clickMenuItem('Edit compaction configuration'); await this.clickMenuItem('Edit compaction configuration');
await this.page.waitForSelector('div.compaction-dialog'); await this.page.waitForSelector('div.compaction-config-dialog');
} }
private async clickMenuItem(text: string): Promise<void> { private async clickMenuItem(text: string): Promise<void> {

View File

@ -22,7 +22,7 @@
"d3-axis": "^2.1.0", "d3-axis": "^2.1.0",
"d3-scale": "^3.3.0", "d3-scale": "^3.3.0",
"d3-selection": "^2.0.0", "d3-selection": "^2.0.0",
"druid-query-toolkit": "^0.17.4", "druid-query-toolkit": "^0.17.5",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"follow-redirects": "^1.14.7", "follow-redirects": "^1.14.7",
"fontsource-open-sans": "^3.0.9", "fontsource-open-sans": "^3.0.9",
@ -8542,9 +8542,9 @@
} }
}, },
"node_modules/druid-query-toolkit": { "node_modules/druid-query-toolkit": {
"version": "0.17.4", "version": "0.17.5",
"resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.17.4.tgz", "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.17.5.tgz",
"integrity": "sha512-d/mNJ9ausAfxQaxgGWfP4dHpwcIqjkbdz1NNmTk6DHMbwlskk96MnXrvPsOCmeoKMb0iYVeVtq9CbB1vNaPZ5A==", "integrity": "sha512-N+kqu6xy2Gd3zQwNbBxQXG+qjU7jzbCXvE84uxsIh5gxRbiKAEOsyWtKrWF/DZZ91g/WSIMQih5klkmmcjlVfQ==",
"dependencies": { "dependencies": {
"tslib": "^2.3.1" "tslib": "^2.3.1"
}, },
@ -33236,9 +33236,9 @@
} }
}, },
"druid-query-toolkit": { "druid-query-toolkit": {
"version": "0.17.4", "version": "0.17.5",
"resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.17.4.tgz", "resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.17.5.tgz",
"integrity": "sha512-d/mNJ9ausAfxQaxgGWfP4dHpwcIqjkbdz1NNmTk6DHMbwlskk96MnXrvPsOCmeoKMb0iYVeVtq9CbB1vNaPZ5A==", "integrity": "sha512-N+kqu6xy2Gd3zQwNbBxQXG+qjU7jzbCXvE84uxsIh5gxRbiKAEOsyWtKrWF/DZZ91g/WSIMQih5klkmmcjlVfQ==",
"requires": { "requires": {
"tslib": "^2.3.1" "tslib": "^2.3.1"
} }

View File

@ -79,7 +79,7 @@
"d3-axis": "^2.1.0", "d3-axis": "^2.1.0",
"d3-scale": "^3.3.0", "d3-scale": "^3.3.0",
"d3-selection": "^2.0.0", "d3-selection": "^2.0.0",
"druid-query-toolkit": "^0.17.4", "druid-query-toolkit": "^0.17.5",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"follow-redirects": "^1.14.7", "follow-redirects": "^1.14.7",
"fontsource-open-sans": "^3.0.9", "fontsource-open-sans": "^3.0.9",

View File

@ -90,7 +90,7 @@ checker.init(
Object.assign(packages, extraPackages); Object.assign(packages, extraPackages);
const seen = {}; const seen = new Map();
const mapped = Object.keys(packages) const mapped = Object.keys(packages)
.sort() .sort()
.map(p => { .map(p => {
@ -98,8 +98,8 @@ checker.init(
if (!m) throw new Error(`Malformed name@version`); if (!m) throw new Error(`Malformed name@version`);
const name = m[1]; const name = m[1];
if (name === 'web-console') return; // This is me! if (name === 'web-console') return; // This is me!
if (seen[name]) return; // Dedupe if (seen.has(name)) return; // Dedupe
seen[name] = true; seen.set(name, true);
const version = m[2]; const version = m[2];
const meta = packages[p]; const meta = packages[p];

View File

@ -51,6 +51,7 @@ export * from './show-json/show-json';
export * from './show-log/show-log'; export * from './show-log/show-log';
export * from './show-value/show-value'; export * from './show-value/show-value';
export * from './suggestion-menu/suggestion-menu'; export * from './suggestion-menu/suggestion-menu';
export * from './supervisor-history-panel/supervisor-history-panel';
export * from './table-cell/table-cell'; export * from './table-cell/table-cell';
export * from './table-cell-unparseable/table-cell-unparseable'; export * from './table-cell-unparseable/table-cell-unparseable';
export * from './table-clickable-cell/table-clickable-cell'; export * from './table-clickable-cell/table-clickable-cell';

View File

@ -2,7 +2,7 @@
exports[`ShowValue matches snapshot 1`] = ` exports[`ShowValue matches snapshot 1`] = `
<div <div
class="show-json" class="show-value"
> >
<div <div
class="top-actions" class="top-actions"

View File

@ -32,7 +32,7 @@ export interface ShowValueProps {
export const ShowValue = React.memo(function ShowValue(props: ShowValueProps) { export const ShowValue = React.memo(function ShowValue(props: ShowValueProps) {
const { jsonValue, onDiffWithPrevious, downloadFilename } = props; const { jsonValue, onDiffWithPrevious, downloadFilename } = props;
return ( return (
<div className="show-json"> <div className="show-value">
{(onDiffWithPrevious || downloadFilename) && ( {(onDiffWithPrevious || downloadFilename) && (
<div className="top-actions"> <div className="top-actions">
<ButtonGroup className="right-buttons"> <ButtonGroup className="right-buttons">

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ShowHistory matches snapshot 1`] = ` exports[`SupervisorHistoryPanel matches snapshot 1`] = `
<div <div
class="loader" class="loader"
> >

View File

@ -18,7 +18,7 @@
@import '../../variables'; @import '../../variables';
.show-history { .supervisor-history-panel {
position: relative; position: relative;
height: 100%; height: 100%;

View File

@ -19,11 +19,11 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { ShowHistory } from './show-history'; import { SupervisorHistoryPanel } from './supervisor-history-panel';
describe('ShowHistory', () => { describe('SupervisorHistoryPanel', () => {
it('matches snapshot', () => { it('matches snapshot', () => {
const showJson = <ShowHistory endpoint="test" downloadFilenamePrefix="test" />; const showJson = <SupervisorHistoryPanel supervisorId="test" />;
const { container } = render(showJson); const { container } = render(showJson);
expect(container.firstChild).toMatchSnapshot(); expect(container.firstChild).toMatchSnapshot();
}); });

View File

@ -29,29 +29,34 @@ import { deepSet } from '../../utils';
import { Loader } from '../loader/loader'; import { Loader } from '../loader/loader';
import { ShowValue } from '../show-value/show-value'; import { ShowValue } from '../show-value/show-value';
import './show-history.scss'; import './supervisor-history-panel.scss';
export interface VersionSpec { export interface SupervisorHistoryEntry {
version: string; version: string;
spec: IngestionSpec; spec: IngestionSpec;
} }
export interface ShowHistoryProps { export interface SupervisorHistoryPanelProps {
endpoint: string; supervisorId: string;
downloadFilenamePrefix?: string;
} }
export const ShowHistory = React.memo(function ShowHistory(props: ShowHistoryProps) { export const SupervisorHistoryPanel = React.memo(function SupervisorHistoryPanel(
const { downloadFilenamePrefix, endpoint } = props; props: SupervisorHistoryPanelProps,
) {
const { supervisorId } = props;
const [historyState] = useQueryManager<string, VersionSpec[]>({
processQuery: async (endpoint: string) => {
const resp = await Api.instance.get(endpoint);
return resp.data.map((vs: VersionSpec) => deepSet(vs, 'spec', cleanSpec(vs.spec, true)));
},
initQuery: endpoint,
});
const [diffIndex, setDiffIndex] = useState(-1); const [diffIndex, setDiffIndex] = useState(-1);
const [historyState] = useQueryManager<string, SupervisorHistoryEntry[]>({
initQuery: supervisorId,
processQuery: async supervisorId => {
const resp = await Api.instance.get(
`/druid/indexer/v1/supervisor/${Api.encodePath(supervisorId)}/history`,
);
return resp.data.map((vs: SupervisorHistoryEntry) =>
deepSet(vs, 'spec', cleanSpec(vs.spec, true)),
);
},
});
if (historyState.loading) return <Loader />; if (historyState.loading) return <Loader />;
@ -59,21 +64,21 @@ export const ShowHistory = React.memo(function ShowHistory(props: ShowHistoryPro
if (!historyData) return null; if (!historyData) return null;
return ( return (
<div className="show-history"> <div className="supervisor-history-panel">
<Tabs animate renderActiveTabPanelOnly vertical defaultSelectedTabId={0}> <Tabs animate renderActiveTabPanelOnly vertical defaultSelectedTabId={0}>
{historyData.map((pastSupervisor, i) => ( {historyData.map((pastSupervisor, i) => (
<Tab <Tab
id={i} id={i}
key={i} key={i}
title={pastSupervisor.version} title={pastSupervisor.version}
panelClassName="panel"
panel={ panel={
<ShowValue <ShowValue
jsonValue={JSONBig.stringify(pastSupervisor.spec, undefined, 2)} jsonValue={JSONBig.stringify(pastSupervisor.spec, undefined, 2)}
onDiffWithPrevious={i < historyData.length - 1 ? () => setDiffIndex(i) : undefined} onDiffWithPrevious={i < historyData.length - 1 ? () => setDiffIndex(i) : undefined}
downloadFilename={`${downloadFilenamePrefix}-version-${pastSupervisor.version}.json`} downloadFilename={`supervisor-${supervisorId}-version-${pastSupervisor.version}.json`}
/> />
} }
panelClassName="panel"
/> />
))} ))}
<Tabs.Expander /> <Tabs.Expander />

View File

@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CompactionDialog matches snapshot with compactionConfig (dynamic partitionsSpec) 1`] = ` exports[`CompactionConfigDialog matches snapshot with compactionConfig (dynamic partitionsSpec) 1`] = `
<Blueprint4.Dialog <Blueprint4.Dialog
canOutsideClickClose={false} canOutsideClickClose={false}
className="compaction-dialog" className="compaction-config-dialog"
isOpen={true} isOpen={true}
onClose={[Function]} onClose={[Function]}
title="Compaction config: test1" title="Compaction config: test1"
@ -356,6 +356,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (dynamic partit
<div <div
className="bp4-dialog-footer-actions" className="bp4-dialog-footer-actions"
> >
<Blueprint4.Button
className="history-button"
minimal={true}
onClick={[Function]}
text="History"
/>
<Blueprint4.Button <Blueprint4.Button
intent="danger" intent="danger"
onClick={[Function]} onClick={[Function]}
@ -376,10 +382,10 @@ exports[`CompactionDialog matches snapshot with compactionConfig (dynamic partit
</Blueprint4.Dialog> </Blueprint4.Dialog>
`; `;
exports[`CompactionDialog matches snapshot with compactionConfig (hashed partitionsSpec) 1`] = ` exports[`CompactionConfigDialog matches snapshot with compactionConfig (hashed partitionsSpec) 1`] = `
<Blueprint4.Dialog <Blueprint4.Dialog
canOutsideClickClose={false} canOutsideClickClose={false}
className="compaction-dialog" className="compaction-config-dialog"
isOpen={true} isOpen={true}
onClose={[Function]} onClose={[Function]}
title="Compaction config: test1" title="Compaction config: test1"
@ -732,6 +738,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (hashed partiti
<div <div
className="bp4-dialog-footer-actions" className="bp4-dialog-footer-actions"
> >
<Blueprint4.Button
className="history-button"
minimal={true}
onClick={[Function]}
text="History"
/>
<Blueprint4.Button <Blueprint4.Button
intent="danger" intent="danger"
onClick={[Function]} onClick={[Function]}
@ -752,10 +764,10 @@ exports[`CompactionDialog matches snapshot with compactionConfig (hashed partiti
</Blueprint4.Dialog> </Blueprint4.Dialog>
`; `;
exports[`CompactionDialog matches snapshot with compactionConfig (range partitionsSpec) 1`] = ` exports[`CompactionConfigDialog matches snapshot with compactionConfig (range partitionsSpec) 1`] = `
<Blueprint4.Dialog <Blueprint4.Dialog
canOutsideClickClose={false} canOutsideClickClose={false}
className="compaction-dialog" className="compaction-config-dialog"
isOpen={true} isOpen={true}
onClose={[Function]} onClose={[Function]}
title="Compaction config: test1" title="Compaction config: test1"
@ -1108,6 +1120,12 @@ exports[`CompactionDialog matches snapshot with compactionConfig (range partitio
<div <div
className="bp4-dialog-footer-actions" className="bp4-dialog-footer-actions"
> >
<Blueprint4.Button
className="history-button"
minimal={true}
onClick={[Function]}
text="History"
/>
<Blueprint4.Button <Blueprint4.Button
intent="danger" intent="danger"
onClick={[Function]} onClick={[Function]}
@ -1128,10 +1146,10 @@ exports[`CompactionDialog matches snapshot with compactionConfig (range partitio
</Blueprint4.Dialog> </Blueprint4.Dialog>
`; `;
exports[`CompactionDialog matches snapshot without compactionConfig 1`] = ` exports[`CompactionConfigDialog matches snapshot without compactionConfig 1`] = `
<Blueprint4.Dialog <Blueprint4.Dialog
canOutsideClickClose={false} canOutsideClickClose={false}
className="compaction-dialog" className="compaction-config-dialog"
isOpen={true} isOpen={true}
onClose={[Function]} onClose={[Function]}
title="Compaction config: test1" title="Compaction config: test1"
@ -1484,6 +1502,12 @@ exports[`CompactionDialog matches snapshot without compactionConfig 1`] = `
<div <div
className="bp4-dialog-footer-actions" className="bp4-dialog-footer-actions"
> >
<Blueprint4.Button
className="history-button"
minimal={true}
onClick={[Function]}
text="History"
/>
<Blueprint4.Button <Blueprint4.Button
onClick={[Function]} onClick={[Function]}
text="Close" text="Close"

View File

@ -18,7 +18,7 @@
@import '../../variables'; @import '../../variables';
.compaction-dialog { .compaction-config-dialog {
&.#{$bp-ns}-dialog { &.#{$bp-ns}-dialog {
height: 80vh; height: 80vh;
} }
@ -32,6 +32,17 @@
margin: 15px; margin: 15px;
} }
.#{$bp-ns}-dialog-footer-actions {
position: relative;
.history-button {
position: absolute;
top: 0;
left: 0;
margin: 0;
}
}
.content { .content {
margin: 0 15px 10px 0; margin: 0 15px 10px 0;
padding: 0 5px 0 15px; padding: 0 5px 0 15px;

View File

@ -20,12 +20,12 @@ import React from 'react';
import { shallow } from '../../utils/shallow-renderer'; import { shallow } from '../../utils/shallow-renderer';
import { CompactionDialog } from './compaction-dialog'; import { CompactionConfigDialog } from './compaction-config-dialog';
describe('CompactionDialog', () => { describe('CompactionConfigDialog', () => {
it('matches snapshot without compactionConfig', () => { it('matches snapshot without compactionConfig', () => {
const compactionDialog = shallow( const compactionDialog = shallow(
<CompactionDialog <CompactionConfigDialog
onClose={() => {}} onClose={() => {}}
onSave={() => {}} onSave={() => {}}
onDelete={() => {}} onDelete={() => {}}
@ -38,7 +38,7 @@ describe('CompactionDialog', () => {
it('matches snapshot with compactionConfig (dynamic partitionsSpec)', () => { it('matches snapshot with compactionConfig (dynamic partitionsSpec)', () => {
const compactionDialog = shallow( const compactionDialog = shallow(
<CompactionDialog <CompactionConfigDialog
onClose={() => {}} onClose={() => {}}
onSave={() => {}} onSave={() => {}}
onDelete={() => {}} onDelete={() => {}}
@ -54,7 +54,7 @@ describe('CompactionDialog', () => {
it('matches snapshot with compactionConfig (hashed partitionsSpec)', () => { it('matches snapshot with compactionConfig (hashed partitionsSpec)', () => {
const compactionDialog = shallow( const compactionDialog = shallow(
<CompactionDialog <CompactionConfigDialog
onClose={() => {}} onClose={() => {}}
onSave={() => {}} onSave={() => {}}
onDelete={() => {}} onDelete={() => {}}
@ -70,7 +70,7 @@ describe('CompactionDialog', () => {
it('matches snapshot with compactionConfig (range partitionsSpec)', () => { it('matches snapshot with compactionConfig (range partitionsSpec)', () => {
const compactionDialog = shallow( const compactionDialog = shallow(
<CompactionDialog <CompactionConfigDialog
onClose={() => {}} onClose={() => {}}
onSave={() => {}} onSave={() => {}}
onDelete={() => {}} onDelete={() => {}}

View File

@ -27,10 +27,11 @@ import {
compactionConfigHasLegacyInputSegmentSizeBytesSet, compactionConfigHasLegacyInputSegmentSizeBytesSet,
} from '../../druid-models'; } from '../../druid-models';
import { deepDelete, formatBytesCompact } from '../../utils'; import { deepDelete, formatBytesCompact } from '../../utils';
import { CompactionHistoryDialog } from '../compaction-history-dialog/compaction-history-dialog';
import './compaction-dialog.scss'; import './compaction-config-dialog.scss';
export interface CompactionDialogProps { export interface CompactionConfigDialogProps {
onClose: () => void; onClose: () => void;
onSave: (compactionConfig: CompactionConfig) => void | Promise<void>; onSave: (compactionConfig: CompactionConfig) => void | Promise<void>;
onDelete: () => void; onDelete: () => void;
@ -38,9 +39,12 @@ export interface CompactionDialogProps {
compactionConfig: CompactionConfig | undefined; compactionConfig: CompactionConfig | undefined;
} }
export const CompactionDialog = React.memo(function CompactionDialog(props: CompactionDialogProps) { export const CompactionConfigDialog = React.memo(function CompactionConfigDialog(
props: CompactionConfigDialogProps,
) {
const { datasource, compactionConfig, onSave, onClose, onDelete } = props; const { datasource, compactionConfig, onSave, onClose, onDelete } = props;
const [showHistory, setShowHistory] = useState(false);
const [currentTab, setCurrentTab] = useState<FormJsonTabs>('form'); const [currentTab, setCurrentTab] = useState<FormJsonTabs>('form');
const [currentConfig, setCurrentConfig] = useState<CompactionConfig>( const [currentConfig, setCurrentConfig] = useState<CompactionConfig>(
compactionConfig || { compactionConfig || {
@ -53,9 +57,15 @@ export const CompactionDialog = React.memo(function CompactionDialog(props: Comp
const issueWithCurrentConfig = AutoForm.issueWithModel(currentConfig, COMPACTION_CONFIG_FIELDS); const issueWithCurrentConfig = AutoForm.issueWithModel(currentConfig, COMPACTION_CONFIG_FIELDS);
const disableSubmit = Boolean(jsonError || issueWithCurrentConfig); const disableSubmit = Boolean(jsonError || issueWithCurrentConfig);
if (showHistory) {
return (
<CompactionHistoryDialog datasource={datasource} onClose={() => setShowHistory(false)} />
);
}
return ( return (
<Dialog <Dialog
className="compaction-dialog" className="compaction-config-dialog"
isOpen isOpen
onClose={onClose} onClose={onClose}
canOutsideClickClose={false} canOutsideClickClose={false}
@ -100,6 +110,12 @@ export const CompactionDialog = React.memo(function CompactionDialog(props: Comp
</div> </div>
<div className={Classes.DIALOG_FOOTER}> <div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}> <div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button
className="history-button"
text="History"
minimal
onClick={() => setShowHistory(true)}
/>
{compactionConfig && <Button text="Delete" intent={Intent.DANGER} onClick={onDelete} />} {compactionConfig && <Button text="Delete" intent={Intent.DANGER} onClick={onDelete} />}
<Button text="Close" onClick={onClose} /> <Button text="Close" onClick={onClose} />
<Button <Button

View File

@ -0,0 +1,50 @@
/*
* 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 '../../variables';
.compaction-history-dialog {
&.#{$bp-ns}-dialog {
width: 70vw;
height: 80vh;
}
.#{$bp-ns}-tabs {
position: relative;
height: 100%;
}
.panel {
position: relative;
width: 100%;
.global-info {
position: absolute;
bottom: 10px;
left: 30px;
right: 10px;
width: auto;
white-space: pre;
background: $gray1;
}
}
.loader {
position: relative;
}
}

View File

@ -0,0 +1,145 @@
/*
* 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, Callout, Classes, Code, Dialog, Tab, Tabs } from '@blueprintjs/core';
import * as JSONBig from 'json-bigint-native';
import React, { useState } from 'react';
import { Loader, ShowValue } from '../../components';
import type { CompactionConfig } from '../../druid-models';
import { useQueryManager } from '../../hooks';
import { Api } from '../../singletons';
import { formatInteger, formatPercent } from '../../utils';
import { DiffDialog } from '../diff-dialog/diff-dialog';
import './compaction-history-dialog.scss';
interface CompactionHistoryEntry {
auditTime: string;
auditInfo: any;
globalConfig?: GlobalConfig;
compactionConfig: CompactionConfig;
}
interface GlobalConfig {
compactionTaskSlotRatio: number;
maxCompactionTaskSlots: number;
useAutoScaleSlots: boolean;
}
function formatGlobalConfig(globalConfig: GlobalConfig): string {
return [
`compactionTaskSlotRatio: ${formatPercent(globalConfig.compactionTaskSlotRatio)}`,
`maxCompactionTaskSlots: ${formatInteger(globalConfig.maxCompactionTaskSlots)}`,
`useAutoScaleSlots: ${globalConfig.useAutoScaleSlots}`,
].join('\n');
}
export interface CompactionHistoryDialogProps {
datasource: string;
onClose(): void;
}
export const CompactionHistoryDialog = React.memo(function CompactionHistoryDialog(
props: CompactionHistoryDialogProps,
) {
const { datasource, onClose } = props;
const [diffIndex, setDiffIndex] = useState(-1);
const [historyState] = useQueryManager<string, CompactionHistoryEntry[]>({
initQuery: datasource,
processQuery: async datasource => {
try {
const resp = await Api.instance.get(
`/druid/coordinator/v1/config/compaction/${Api.encodePath(datasource)}/history?count=20`,
);
return resp.data;
} catch (e) {
if (e.response?.status === 404) return [];
throw e;
}
},
});
const historyData = historyState.data;
return (
<Dialog
className="compaction-history-dialog"
isOpen
onClose={onClose}
canOutsideClickClose={false}
title={`Compaction history: ${datasource}`}
>
<div className={Classes.DIALOG_BODY}>
{historyData ? (
historyData.length ? (
<Tabs animate renderActiveTabPanelOnly vertical defaultSelectedTabId={0}>
{historyData.map((historyEntry, i) => (
<Tab
id={i}
key={i}
title={historyEntry.auditTime}
panelClassName="panel"
panel={
<>
<ShowValue
jsonValue={JSONBig.stringify(historyEntry.compactionConfig, undefined, 2)}
onDiffWithPrevious={
i < historyData.length - 1 ? () => setDiffIndex(i) : undefined
}
downloadFilename={`compaction-history-${datasource}-version-${historyEntry.auditTime}.json`}
/>
{historyEntry.globalConfig && (
<Callout className="global-info">
{formatGlobalConfig(historyEntry.globalConfig)}
</Callout>
)}
</>
}
/>
))}
<Tabs.Expander />
</Tabs>
) : (
<div>
There is no compaction history for <Code>{datasource}</Code>.
</div>
)
) : historyState.loading ? (
<Loader />
) : (
<div>{historyState.getErrorMessage()}</div>
)}
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button text="Close" onClick={onClose} />
</div>
</div>
{diffIndex !== -1 && historyData && (
<DiffDialog
title="Compaction config diff"
versions={historyData.map(s => ({ label: s.auditTime, value: s.compactionConfig }))}
initLeftIndex={diffIndex + 1}
initRightIndex={diffIndex}
onClose={() => setDiffIndex(-1)}
/>
)}
</Dialog>
);
});

View File

@ -112,7 +112,7 @@ exports[`HistoryDialog matches snapshot 1`] = `
role="tabpanel" role="tabpanel"
> >
<div <div
class="show-json" class="show-value"
> >
<div <div
class="top-actions" class="top-actions"

View File

@ -18,7 +18,7 @@
export * from './about-dialog/about-dialog'; export * from './about-dialog/about-dialog';
export * from './async-action-dialog/async-action-dialog'; export * from './async-action-dialog/async-action-dialog';
export * from './compaction-dialog/compaction-dialog'; export * from './compaction-config-dialog/compaction-config-dialog';
export * from './coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog'; export * from './coordinator-dynamic-config-dialog/coordinator-dynamic-config-dialog';
export * from './diff-dialog/diff-dialog'; export * from './diff-dialog/diff-dialog';
export * from './doctor-dialog/doctor-dialog'; export * from './doctor-dialog/doctor-dialog';

View File

@ -18,8 +18,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ShowJson } from '../../components'; import { ShowJson, SupervisorHistoryPanel } from '../../components';
import { ShowHistory } from '../../components/show-history/show-history';
import { cleanSpec } from '../../druid-models'; import { cleanSpec } from '../../druid-models';
import { Api } from '../../singletons'; import { Api } from '../../singletons';
import { deepGet } from '../../utils'; import { deepGet } from '../../utils';
@ -96,12 +95,7 @@ export const SupervisorTableActionDialog = React.memo(function SupervisorTableAc
downloadFilename={`supervisor-payload-${supervisorId}.json`} downloadFilename={`supervisor-payload-${supervisorId}.json`}
/> />
)} )}
{activeTab === 'history' && ( {activeTab === 'history' && <SupervisorHistoryPanel supervisorId={supervisorId} />}
<ShowHistory
endpoint={`${supervisorEndpointBase}/history`}
downloadFilenamePrefix={`supervisor-${supervisorId}`}
/>
)}
</TableActionDialog> </TableActionDialog>
); );
}); });

View File

@ -16,6 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { dedupe } from 'druid-query-toolkit';
import * as JSONBig from 'json-bigint-native'; import * as JSONBig from 'json-bigint-native';
import type { import type {
@ -91,18 +92,6 @@ export interface ExampleManifest {
spec: any; spec: any;
} }
function dedupe(xs: string[]): string[] {
const seen: Record<string, boolean> = {};
return xs.filter(x => {
if (seen[x]) {
return false;
} else {
seen[x] = true;
return true;
}
});
}
export function getCacheRowsFromSampleResponse(sampleResponse: SampleResponse): CacheRows { export function getCacheRowsFromSampleResponse(sampleResponse: SampleResponse): CacheRows {
return filterMap(sampleResponse.data, d => d.input).slice(0, 20); return filterMap(sampleResponse.data, d => d.input).slice(0, 20);
} }

View File

@ -39,7 +39,7 @@ import {
} from '../../components'; } from '../../components';
import { import {
AsyncActionDialog, AsyncActionDialog,
CompactionDialog, CompactionConfigDialog,
KillDatasourceDialog, KillDatasourceDialog,
RetentionDialog, RetentionDialog,
} from '../../dialogs'; } from '../../dialogs';
@ -233,7 +233,7 @@ interface RetentionDialogOpenOn {
readonly rules: Rule[]; readonly rules: Rule[];
} }
interface CompactionDialogOpenOn { interface CompactionConfigDialogOpenOn {
readonly datasource: string; readonly datasource: string;
readonly compactionConfig?: CompactionConfig; readonly compactionConfig?: CompactionConfig;
} }
@ -254,7 +254,7 @@ export interface DatasourcesViewState {
showUnused: boolean; showUnused: boolean;
retentionDialogOpenOn?: RetentionDialogOpenOn; retentionDialogOpenOn?: RetentionDialogOpenOn;
compactionDialogOpenOn?: CompactionDialogOpenOn; compactionDialogOpenOn?: CompactionConfigDialogOpenOn;
datasourceToMarkAsUnusedAllSegmentsIn?: string; datasourceToMarkAsUnusedAllSegmentsIn?: string;
datasourceToMarkAllNonOvershadowedSegmentsAsUsedIn?: string; datasourceToMarkAllNonOvershadowedSegmentsAsUsedIn?: string;
killDatasource?: string; killDatasource?: string;
@ -981,12 +981,12 @@ ORDER BY 1`;
); );
} }
private renderCompactionDialog() { private renderCompactionConfigDialog() {
const { datasourcesAndDefaultRulesState, compactionDialogOpenOn } = this.state; const { datasourcesAndDefaultRulesState, compactionDialogOpenOn } = this.state;
if (!compactionDialogOpenOn || !datasourcesAndDefaultRulesState.data) return; if (!compactionDialogOpenOn || !datasourcesAndDefaultRulesState.data) return;
return ( return (
<CompactionDialog <CompactionConfigDialog
datasource={compactionDialogOpenOn.datasource} datasource={compactionDialogOpenOn.datasource}
compactionConfig={compactionDialogOpenOn.compactionConfig} compactionConfig={compactionDialogOpenOn.compactionConfig}
onClose={() => this.setState({ compactionDialogOpenOn: undefined })} onClose={() => this.setState({ compactionDialogOpenOn: undefined })}
@ -1571,7 +1571,7 @@ ORDER BY 1`;
{this.renderUseUnuseActionByInterval()} {this.renderUseUnuseActionByInterval()}
{this.renderKillAction()} {this.renderKillAction()}
{this.renderRetentionDialog()} {this.renderRetentionDialog()}
{this.renderCompactionDialog()} {this.renderCompactionConfigDialog()}
{this.renderForceCompactAction()} {this.renderForceCompactAction()}
</div> </div>
); );