Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
103786: ui: add manual refresh option to the Active Executions in SQL Activity r=gtr a=gtr

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.

Loom (DB): https://www.loom.com/share/5bdab5a62a0c4bc694c9c5d7f936759a
Loom (CC): https://www.loom.com/share/de399300225540059bc7c4f156bc8270

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.

106216: roachtest: remove './cockroach sql' incantations from restore roachtests r=dt a=msbutler

The patch removes all './cockroach sql' incantations from the restore roachtest driver, which previously would cause the backupFixture test to fail if the user passed a binary not named 'cockroach'.

Fixes #105952

Release note: None

Co-authored-by: gtr <[email protected]>
Co-authored-by: Michael Butler <[email protected]>
  • Loading branch information
3 people committed Jul 6, 2023
3 parents 5e87ff5 + fde54d4 + adb9008 commit e38486c
Show file tree
Hide file tree
Showing 15 changed files with 507 additions and 20 deletions.
17 changes: 11 additions & 6 deletions pkg/cmd/roachtest/tests/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ func (rd *restoreDriver) getAOST(ctx context.Context) {
}

func (rd *restoreDriver) restoreCmd(target, opts string) string {
return fmt.Sprintf(`./cockroach sql --insecure -e "RESTORE %s FROM %s IN %s AS OF SYSTEM TIME '%s' %s"`,
return fmt.Sprintf(`RESTORE %s FROM %s IN %s AS OF SYSTEM TIME '%s' %s`,
target, rd.sp.backup.fullBackupDir, rd.sp.backup.backupCollection(), rd.aost, opts)
}

Expand All @@ -838,20 +838,25 @@ func (rd *restoreDriver) restoreCmd(target, opts string) string {
// - "DATABASE tpce" will execute a database restore on the tpce cluster.
// - "" will execute a cluster restore.
func (rd *restoreDriver) run(ctx context.Context, target string) error {
return rd.c.RunE(ctx, rd.c.Node(1), rd.restoreCmd(target, ""))
conn, err := rd.c.ConnE(ctx, rd.t.L(), 1)
if err != nil {
return errors.Wrapf(err, "failed to connect to node 1; running restore")
}
_, err = conn.ExecContext(ctx, rd.restoreCmd(target, ""))
return err
}

func (rd *restoreDriver) runDetached(
ctx context.Context, target string, node int,
) (jobspb.JobID, error) {
if err := rd.c.RunE(ctx, rd.c.Node(node), rd.restoreCmd(target, "WITH DETACHED")); err != nil {
return 0, err
}

db, err := rd.c.ConnE(ctx, rd.t.L(), rd.c.Node(node)[0])
if err != nil {
return 0, errors.Wrapf(err, "failed to connect to node %d; running restore detached", node)
}
if _, err = db.ExecContext(ctx, rd.restoreCmd(target,
"WITH DETACHED")); err != nil {
return 0, err
}
var jobID jobspb.JobID
if err := db.QueryRow(`SELECT job_id FROM [SHOW JOBS] WHERE job_type = 'RESTORE'`).Scan(&jobID); err != nil {
return 0, err
Expand Down
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 e38486c

Please sign in to comment.