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
Part of: #100663.

Previously, the Active Executions views in the SQL Activity pages only
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.

A summary of changes:

- A new component `RefreshControl` which contains an auto-refresh
  toggle, a manual refresh button, and a timestamp indicating when the
  last refresh was performed..
- `ActiveStatementsView` and `ActiveTransactionsView` make use of this
  component to control when their data gets refreshed. The toggle
  defaults to ON for both pages.
- Analytics for the auto refresh toggle and the manual refresh button.
- A inline alert if the data hasn't been refreshed in over 10 minutes.

Release note (ui change): the active executions views in the SQL
Activity 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 Jul 5, 2023
1 parent 59be26a commit fde54d4
Show file tree
Hide file tree
Showing 14 changed files with 496 additions and 14 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,49 @@
// 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: $colors--primary-blue-3;
vertical-align: middle;
margin-right: 12px;
}

.ant-switch-checked {
background-color: $colors--primary-blue-3;
}

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

.refresh-divider {
border-left: 1px solid $colors--neutral-4;
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,81 @@
// 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_WITH_SECONDS_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 {
onManualRefresh: () => void;
}

// RefreshButton consists of the RefreshIcon and the text "Refresh".
const RefreshButton: React.FC<RefreshButtonProps> = ({ onManualRefresh }) => (
<span className={cx("refresh-button")} onClick={onManualRefresh}>
<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_WITH_SECONDS_FORMAT_24_TZ}
/>
) : (
"N/A"
)}
</span>
<RefreshButton onManualRefresh={onManualRefresh} />
<span className={cx("refresh-divider")}>
<span className={cx("auto-refresh-label")}>Auto Refresh: </span>
<Switch
className={cx(`ant-switch-${isAutoRefreshEnabled ? "checked" : ""}`)}
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 @@ -16,13 +16,17 @@ import {
ActiveStatementsViewStateProps,
AppState,
SortSetting,
analyticsActions,
} from "src";
import {
selectActiveStatements,
selectAppName,
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 @@ -33,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 @@ -58,6 +68,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 @@ -86,4 +98,28 @@ export const mapDispatchToActiveStatementsPageProps = (
value: ss,
}),
),
onAutoRefreshToggle: (isEnabled: boolean) => {
dispatch(
localStorageActions.update({
key: LocalStorageKeys.ACTIVE_EXECUTIONS_IS_AUTOREFRESH_ENABLED,
value: isEnabled,
}),
);
dispatch(
analyticsActions.track({
name: "Auto Refresh Toggle",
page: "Statements",
value: isEnabled,
}),
);
},
onManualRefresh: () => {
dispatch(sessionsActions.refresh());
dispatch(
analyticsActions.track({
name: "Manual Refresh",
page: "Statements",
}),
);
},
});
Loading

0 comments on commit fde54d4

Please sign in to comment.