Skip to content

Commit

Permalink
ui: add manual refresh option to the Active Executions in SQL Activity
Browse files Browse the repository at this point in the history
Previously, the Active Executions pages only supported automatic refresh
at a rate of every 10 seconds. This is a papercut which may lead to some
queries disappearing before a user is able to investigate it. This
commit adds adds support for manual refresh in the Active Executions
page as well as a toggle to switch between  automatic and manual
refresh.

Release note (ui change): the active executions pages now support
toggling between automatic and manual refresh. A manual refresh button
is also added along with a timestamp indicating when the last refresh
was performed.
  • Loading branch information
gtr committed Jun 2, 2023
1 parent ad6c84a commit 583952a
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

export * from "./refreshControl";
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

@import "src/core/index.module";

.refresh-timestamp {
vertical-align: middle;
}

.refresh-icon {
margin-left: 12px;
margin-right: 6px;
vertical-align: middle;
}

.refresh-text {
color: #0055FF;
vertical-align: middle;
margin-right: 12px;
}

.refresh-button {
vertical-align: middle;
align-items: center;
}

.refresh-divider {
border-left: 1px solid #c0c6d9;
padding-left: 12px;
height: $line-height--large;
display: inline-flex;
align-items: center;
vertical-align: middle;
}

.auto-refresh-label {
padding-right: 8px;
vertical-align: middle;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { Switch } from "antd";
import "antd/lib/switch/style";
import React from "react";
import classNames from "classnames/bind";
import styles from "./refreshControl.module.scss";
import RefreshIcon from "src/icon/refreshIcon";
import { Timestamp } from "src/timestamp";
import { Moment } from "moment-timezone";
import { DATE_FORMAT_24_TZ, capitalize } from "src/util";
import { ExecutionType } from "../types";

const cx = classNames.bind(styles);

interface RefreshControlProps {
isAutoRefreshEnabled: boolean;
onToggleAutoRefresh: () => void;
onManualRefresh: () => void;
lastRefreshTimestamp: Moment;
execType: ExecutionType;
}

const REFRESH_BUTTON_COLOR = "#0055FF";

interface RefreshButtonProps {
onClick: () => void;
}

// RefreshButton consists of the RefreshIcon and the text "Refresh"
const RefreshButton: React.FC<RefreshButtonProps> = ({ onClick }) => (
<span className={cx("refresh-button")} onClick={onClick}>
<RefreshIcon color={REFRESH_BUTTON_COLOR} className={cx("refresh-icon")} />
<span className={cx("refresh-text")}>Refresh</span>
</span>
);

export const RefreshControl: React.FC<RefreshControlProps> = ({
isAutoRefreshEnabled,
onToggleAutoRefresh,
onManualRefresh,
lastRefreshTimestamp,
execType,
}) => {
return (
<div>
<span className={cx("refresh-timestamp")}>
<span>Active {capitalize(execType)} Executions As Of: </span>
{lastRefreshTimestamp && lastRefreshTimestamp.isValid() ? (
<Timestamp time={lastRefreshTimestamp} format={DATE_FORMAT_24_TZ} />
) : (
"N/A"
)}
</span>
<RefreshButton onClick={onManualRefresh} />
<span className={cx("refresh-divider")}>
<span className={cx("auto-refresh-label")}>Auto Refresh: </span>
<Switch
checkedChildren={"On"}
unCheckedChildren={"Off"}
checked={isAutoRefreshEnabled}
onClick={onToggleAutoRefresh}
/>
</span>
</div>
);
};

export default RefreshControl;
45 changes: 45 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/icon/refreshIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import * as React from "react";

interface IconProps {
className?: string;
color?: string;
}

const RefreshIcon = ({ className, color }: IconProps) => (
<svg
className={className}
width="18"
height="18"
viewBox="0 0 24 24"
fill={color || "#394455"}
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_11029_22359)">
<path
d="M1.77601 11.9912C1.77601 8.58278 3.43606 5.47461 6.17336 3.58499V7.04636H7.93937V0H0.893012V1.766H5.78484C2.19985 3.92053 0.0100098 7.7351 0.0100098 11.9912C0.0100098 17.5364 3.93054 22.4636 9.33451 23.6821L9.72303 21.9514C5.11376 20.9095 1.77601 16.7064 1.77601 11.9735V11.9912Z"
fill={color || "#394455"}
/>
<path
d="M23.9924 11.9912C23.9924 6.35762 20.1601 1.53642 14.6678 0.300221L14.2793 2.03091C18.8886 3.07285 22.2263 7.27594 22.2263 12.0088C22.2263 15.4172 20.5663 18.5254 17.829 20.415V16.9536H16.063V24H23.1094V22.234H18.2175C21.8025 20.0795 23.9924 16.2649 23.9924 12.0088V11.9912Z"
fill={color || "#394455"}
/>
</g>
<defs>
<clipPath id="clip0_11029_22359">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
);

export default RefreshIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
selectExecutionStatus,
selectClusterLocksMaxApiSizeReached,
} from "src/selectors/activeExecutions.selectors";
import { actions as localStorageActions } from "src/store/localStorage";
import {
LocalStorageKeys,
actions as localStorageActions,
} from "src/store/localStorage";
import { actions as sessionsActions } from "src/store/sessions";
import { selectIsTenant } from "src/store/uiConfig";
import { localStorageSelector } from "../store/utils/selectors";
Expand All @@ -34,6 +37,12 @@ export const selectSortSetting = (state: AppState): SortSetting =>
export const selectFilters = (state: AppState): ActiveStatementFilters =>
localStorageSelector(state)["filters/ActiveStatementsPage"];

export const selectIsAutoRefreshEnabled = (state: AppState): boolean => {
return localStorageSelector(state)[
LocalStorageKeys.ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED
];
};

const selectLocalStorageColumns = (state: AppState) => {
const localStorage = localStorageSelector(state);
return localStorage["showColumns/ActiveStatementsPage"];
Expand All @@ -60,6 +69,8 @@ export const mapStateToActiveStatementsPageProps = (
internalAppNamePrefix: selectAppName(state),
isTenant: selectIsTenant(state),
maxSizeApiReached: selectClusterLocksMaxApiSizeReached(state),
isAutoRefreshEnabled: selectIsAutoRefreshEnabled(state),
lastUpdated: state.adminUI?.sessions.lastUpdated,
});

export const mapDispatchToActiveStatementsPageProps = (
Expand Down Expand Up @@ -88,4 +99,12 @@ export const mapDispatchToActiveStatementsPageProps = (
value: ss,
}),
),
onAutoRefreshToggle: (isEnabled: boolean) => {
dispatch(
localStorageActions.update({
key: LocalStorageKeys.ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED,
value: isEnabled,
}),
);
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { Pagination } from "src/pagination";
import { InlineAlert } from "@cockroachlabs/ui-components";

import styles from "./statementsPage.module.scss";
import RefreshControl from "src/activeExecutions/refreshControl/refreshControl";

const cx = classNames.bind(styles);
const PAGE_SIZE = 20;
Expand All @@ -48,6 +49,7 @@ export type ActiveStatementsViewDispatchProps = {
onFiltersChange: (filters: ActiveStatementFilters) => void;
onSortChange: (ss: SortSetting) => void;
refreshLiveWorkload: () => void;
onAutoRefreshToggle: (isEnabled: boolean) => void;
};

export type ActiveStatementsViewStateProps = {
Expand All @@ -60,6 +62,8 @@ export type ActiveStatementsViewStateProps = {
internalAppNamePrefix: string;
isTenant?: boolean;
maxSizeApiReached?: boolean;
isAutoRefreshEnabled?: boolean;
lastUpdated: Moment | null;
};

export type ActiveStatementsViewProps = ActiveStatementsViewStateProps &
Expand All @@ -79,6 +83,9 @@ export const ActiveStatementsView: React.FC<ActiveStatementsViewProps> = ({
internalAppNamePrefix,
isTenant,
maxSizeApiReached,
isAutoRefreshEnabled,
onAutoRefreshToggle,
lastUpdated,
}: ActiveStatementsViewProps) => {
const [pagination, setPagination] = useState<ISortedTablePagination>({
current: 1,
Expand All @@ -90,13 +97,25 @@ export const ActiveStatementsView: React.FC<ActiveStatementsViewProps> = ({
);

useEffect(() => {
// Refresh every 10 seconds.
refreshLiveWorkload();
// useEffect hook which triggers an immediate data refresh if auto-refresh
// is enabled. It fetches the latest workload details by dispatching a
// refresh action when the component mounts, ensuring that users see fresh
// data as soon as they land on the page if auto-refresh is on.
if (isAutoRefreshEnabled) {
refreshLiveWorkload();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
// Refresh every 10 seconds if auto refresh is on.
if (isAutoRefreshEnabled) {
const interval = setInterval(refreshLiveWorkload, 10 * 1000);
return () => {
clearInterval(interval);
};
}, [refreshLiveWorkload]);
}
}, [isAutoRefreshEnabled, refreshLiveWorkload]);

useEffect(() => {
// We use this effect to sync settings defined on the URL (sort, filters),
Expand Down Expand Up @@ -160,6 +179,18 @@ export const ActiveStatementsView: React.FC<ActiveStatementsViewProps> = ({
resetPagination();
};

const onSubmitToggleAutoRefresh = () => {
// Refresh immediately when toggling auto-refresh on.
if (!isAutoRefreshEnabled) {
refreshLiveWorkload();
}
onAutoRefreshToggle(!isAutoRefreshEnabled);
};

const handleRefresh = () => {
refreshLiveWorkload();
};

const clearSearch = () => onSubmitSearch("");
const clearFilters = () =>
onSubmitFilters({
Expand Down Expand Up @@ -204,6 +235,15 @@ export const ActiveStatementsView: React.FC<ActiveStatementsViewProps> = ({
filters={filters}
/>
</PageConfigItem>
<PageConfigItem>
<RefreshControl
isAutoRefreshEnabled={isAutoRefreshEnabled}
onToggleAutoRefresh={onSubmitToggleAutoRefresh}
onManualRefresh={handleRefresh}
lastRefreshTimestamp={lastUpdated}
execType={"statement"}
/>
</PageConfigItem>
</PageConfig>
<div className={cx("table-area")}>
<Loading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum LocalStorageKeys {
STMT_FINGERPRINTS_SORT = "sort/StatementsPage",
TXN_FINGERPRINTS_LIMIT = "limit/TransactionsPage",
TXN_FINGERPRINTS_SORT = "sort/TransactionsPage",
ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED = "isAutoRefreshEnabled/ActiveExecutions",
}

export type LocalStorageState = {
Expand Down Expand Up @@ -67,6 +68,7 @@ export type LocalStorageState = {
"typeSetting/JobsPage": number;
"statusSetting/JobsPage": string;
"showSetting/JobsPage": string;
[LocalStorageKeys.ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED]: boolean;
};

type Payload = {
Expand Down Expand Up @@ -129,6 +131,10 @@ const defaultJobShowSetting = "0";

const defaultJobTypeSetting = 0;

const defaultIsAutoRefreshEnabledSetting = true;

const defaultLastTimestampRefreshSetting = "";

// TODO (koorosh): initial state should be restored from preserved keys in LocalStorage
const initialState: LocalStorageState = {
"adminUi/showDiagnosticsModal":
Expand Down Expand Up @@ -229,6 +235,12 @@ const initialState: LocalStorageState = {
"statusSetting/JobsPage":
JSON.parse(localStorage.getItem("statusSetting/JobsPage")) ||
defaultJobStatusSetting,
[LocalStorageKeys.ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED]:
JSON.parse(
localStorage.getItem(
LocalStorageKeys.ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED,
),
) || defaultIsAutoRefreshEnabledSetting,
};

const localStorageSlice = createSlice({
Expand Down
Loading

0 comments on commit 583952a

Please sign in to comment.