This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 7e147ee905 Web console: Reset to specific offsets dialog (#14863)
7e147ee905 is described below
commit 7e147ee905443ce1f57f456f72b962740461fa15
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Thu Aug 17 15:38:56 2023 -0700
Web console: Reset to specific offsets dialog (#14863)
* add dialog
* copy changes
---
web-console/src/dialogs/index.ts | 1 +
.../supervisor-reset-offsets-dialog.tsx | 122 +++++++++++++++++++++
.../views/supervisors-view/supervisors-view.tsx | 25 ++++-
3 files changed, 147 insertions(+), 1 deletion(-)
diff --git a/web-console/src/dialogs/index.ts b/web-console/src/dialogs/index.ts
index 468ccf8d62..f7e239b53f 100644
--- a/web-console/src/dialogs/index.ts
+++ b/web-console/src/dialogs/index.ts
@@ -34,6 +34,7 @@ export * from './retention-dialog/retention-dialog';
export * from './snitch-dialog/snitch-dialog';
export * from './spec-dialog/spec-dialog';
export * from './string-input-dialog/string-input-dialog';
+export * from
'./supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog';
export * from
'./supervisor-table-action-dialog/supervisor-table-action-dialog';
export * from './table-action-dialog/table-action-dialog';
export * from './task-table-action-dialog/task-table-action-dialog';
diff --git
a/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.tsx
b/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.tsx
new file mode 100644
index 0000000000..8fd8fdba56
--- /dev/null
+++
b/web-console/src/dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog.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, Classes, Code, ControlGroup, Dialog, FormGroup, Intent } from
'@blueprintjs/core';
+import React, { useState } from 'react';
+
+import { FancyNumericInput } from
'../../components/fancy-numeric-input/fancy-numeric-input';
+import { useQueryManager } from '../../hooks';
+import { Api, AppToaster } from '../../singletons';
+import { deepGet, getDruidErrorMessage } from '../../utils';
+
+type OffsetMap = Record<string, number>;
+
+interface SupervisorResetOffsetsDialogProps {
+ supervisorId: string;
+ supervisorType: string;
+ onClose: () => void;
+}
+
+export const SupervisorResetOffsetsDialog = React.memo(function
SupervisorResetOffsetsDialog(
+ props: SupervisorResetOffsetsDialogProps,
+) {
+ const { supervisorId, supervisorType, onClose } = props;
+ const [offsetsToResetTo, setOffsetsToResetTo] = useState<OffsetMap>({});
+
+ const [statusResp] = useQueryManager<string, OffsetMap>({
+ initQuery: supervisorId,
+ processQuery: async supervisorId => {
+ const statusResp = await Api.instance.get(
+ `/druid/indexer/v1/supervisor/${Api.encodePath(supervisorId)}/status`,
+ );
+ return statusResp.data;
+ },
+ });
+
+ const stream = deepGet(statusResp.data || {}, 'payload.stream');
+ const latestOffsets = deepGet(statusResp.data || {},
'payload.latestOffsets');
+
+ async function onSave() {
+ if (!stream) return;
+ if (!Object.keys(offsetsToResetTo).length) return;
+
+ try {
+ await Api.instance.post(
+
`/druid/indexer/v1/supervisor/${Api.encodePath(supervisorId)}/resetOffsets`,
+ {
+ type: supervisorType,
+ partitions: {
+ type: 'end',
+ stream,
+ partitionOffsetMap: offsetsToResetTo,
+ },
+ },
+ );
+ } catch (e) {
+ AppToaster.show({
+ message: `Failed to set offsets: ${getDruidErrorMessage(e)}`,
+ intent: Intent.DANGER,
+ });
+ return;
+ }
+
+ AppToaster.show({
+ message: `${supervisorId} offsets have been set`,
+ intent: Intent.SUCCESS,
+ });
+ onClose();
+ }
+
+ return (
+ <Dialog
+ className="supervisor-reset-offsets-dialog"
+ isOpen
+ onClose={onClose}
+ title={`Set supervisor offsets: ${supervisorId}`}
+ >
+ <div className={Classes.DIALOG_FOOTER}>
+ <div className={Classes.DIALOG_BODY}>
+ <p>
+ Set <Code>{supervisorId}</Code> to specific offsets
+ </p>
+ {latestOffsets &&
+ Object.entries(latestOffsets).map(([key, latestOffset]) => (
+ <FormGroup key={key}>
+ <ControlGroup>
+ <Button text={`${key} (currently: ${latestOffset})`}
disabled />
+ <FancyNumericInput
+ value={offsetsToResetTo[key]}
+ onValueChange={valueAsNumber => {
+ setOffsetsToResetTo({ ...offsetsToResetTo, [key]:
valueAsNumber });
+ }}
+ min={0}
+ fill
+ placeholder={"Don't change"}
+ />
+ </ControlGroup>
+ </FormGroup>
+ ))}
+ </div>
+ <div className={Classes.DIALOG_FOOTER_ACTIONS}>
+ <Button text="Close" onClick={onClose} />
+ <Button text="Save" intent={Intent.PRIMARY} onClick={() => void
onSave()} />
+ </div>
+ </div>
+ </Dialog>
+ );
+});
diff --git a/web-console/src/views/supervisors-view/supervisors-view.tsx
b/web-console/src/views/supervisors-view/supervisors-view.tsx
index c4bef912c3..2959e06f62 100644
--- a/web-console/src/views/supervisors-view/supervisors-view.tsx
+++ b/web-console/src/views/supervisors-view/supervisors-view.tsx
@@ -40,6 +40,7 @@ import {
SpecDialog,
SupervisorTableActionDialog,
} from '../../dialogs';
+import { SupervisorResetOffsetsDialog } from
'../../dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog';
import type { QueryWithContext } from '../../druid-models';
import type { Capabilities } from '../../helpers';
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from
'../../react-table';
@@ -108,6 +109,7 @@ export interface SupervisorsViewState {
resumeSupervisorId?: string;
suspendSupervisorId?: string;
+ resetOffsetsSupervisorInfo?: { id: string; type: string };
resetSupervisorId?: string;
terminateSupervisorId?: string;
@@ -339,6 +341,11 @@ GROUP BY 1, 2`;
? this.setState({ resumeSupervisorId: id })
: this.setState({ suspendSupervisorId: id }),
},
+ {
+ icon: IconNames.STEP_BACKWARD,
+ title: 'Set offsets',
+ onAction: () => this.setState({ resetOffsetsSupervisorInfo: { id, type
} }),
+ },
{
icon: IconNames.STEP_BACKWARD,
title: 'Hard reset',
@@ -417,6 +424,21 @@ GROUP BY 1, 2`;
);
}
+ renderResetOffsetsSupervisorAction() {
+ const { resetOffsetsSupervisorInfo } = this.state;
+ if (!resetOffsetsSupervisorInfo) return;
+
+ return (
+ <SupervisorResetOffsetsDialog
+ supervisorId={resetOffsetsSupervisorInfo.id}
+ supervisorType={resetOffsetsSupervisorInfo.type}
+ onClose={() => {
+ this.setState({ resetOffsetsSupervisorInfo: undefined });
+ }}
+ />
+ );
+ }
+
renderResetSupervisorAction() {
const { resetSupervisorId } = this.state;
if (!resetSupervisorId) return;
@@ -426,7 +448,7 @@ GROUP BY 1, 2`;
action={async () => {
const resp = await Api.instance.post(
`/druid/indexer/v1/supervisor/${Api.encodePath(resetSupervisorId)}/reset`,
- {},
+ '',
);
return resp.data;
}}
@@ -784,6 +806,7 @@ GROUP BY 1, 2`;
{this.renderSupervisorTable()}
{this.renderResumeSupervisorAction()}
{this.renderSuspendSupervisorAction()}
+ {this.renderResetOffsetsSupervisorAction()}
{this.renderResetSupervisorAction()}
{this.renderTerminateSupervisorAction()}
{supervisorSpecDialogOpen && (
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]