Skip to content

Commit

Permalink
ui: add connected components for jobs pages
Browse files Browse the repository at this point in the history
This commit adds "connected" components for the jobs pages.
These components can be imported from the cluster-ui package into
the managed service repo and used on the CC console.

Release note: None
  • Loading branch information
ericharmeling committed Jul 8, 2022
1 parent ab9eabf commit 046d6e6
Show file tree
Hide file tree
Showing 28 changed files with 775 additions and 58 deletions.
33 changes: 33 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/jobsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,42 @@
// licenses/APL.txt.

import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { fetchData } from "./fetchData";
import { propsToQueryString } from "../util";

const JOBS_PATH = "/_admin/v1/jobs";

export type JobsRequest = cockroach.server.serverpb.JobsRequest;
export type JobsResponse = cockroach.server.serverpb.JobsResponse;

export type JobRequest = cockroach.server.serverpb.JobRequest;
export type JobResponse = cockroach.server.serverpb.JobResponse;

export const getJobs = (
req: JobsRequest,
): Promise<cockroach.server.serverpb.JobsResponse> => {
const queryStr = propsToQueryString({
status: req.status,
type: req.type.toString(),
limit: req.limit,
});
return fetchData(
cockroach.server.serverpb.JobsResponse,
`${JOBS_PATH}?${queryStr}`,
null,
null,
"30M",
);
};

export const getJob = (
req: JobRequest,
): Promise<cockroach.server.serverpb.JobResponse> => {
return fetchData(
cockroach.server.serverpb.JobResponse,
`${JOBS_PATH}/${req.job_id}`,
null,
null,
"30M",
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
// licenses/APL.txt.

export * from "./jobDetails";
export * from "./jobDetailsConnected";
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2022 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 { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router-dom";

import { AppState } from "src/store";
import { selectJobState } from "../../store/jobDetails/job.selectors";
import {
JobDetailsStateProps,
JobDetailsDispatchProps,
JobDetails,
} from "./jobDetails";
import { JobRequest } from "src/api/jobsApi";
import { actions as jobActions } from "src/store/jobDetails";

const mapStateToProps = (state: AppState): JobDetailsStateProps => {
const jobState = selectJobState(state);
const job = jobState ? jobState.data : null;
const jobLoading = jobState ? jobState.inFlight : false;
const jobError = jobState ? jobState.lastError : null;
return {
job,
jobLoading,
jobError,
};
};

const mapDispatchToProps = {
refreshJob: (req: JobRequest) => jobActions.refresh(req),
};

export const JobDetailsPageConnected = withRouter(
connect<JobDetailsStateProps, JobDetailsDispatchProps, RouteComponentProps>(
mapStateToProps,
mapDispatchToProps,
)(JobDetails),
);
1 change: 1 addition & 0 deletions pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
export * from "./jobDescriptionCell";
export * from "./jobsPage";
export * from "./jobTable";
export * from "./jobsPageConnected";
15 changes: 10 additions & 5 deletions pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { cockroach, google } from "@cockroachlabs/crdb-protobuf-client";
import { Tooltip } from "@cockroachlabs/ui-components";
import { isEqual, map } from "lodash";
import React, { MouseEvent } from "react";
import React from "react";
import { Anchor } from "src/anchor";
import { JobsResponse } from "src/api/jobsApi";
import emptyTableResultsIcon from "src/assets/emptyState/empty-table-results.svg";
Expand All @@ -28,9 +28,8 @@ import {
} from "src/util/docs";
import { DATE_FORMAT_24_UTC } from "src/util/format";

import { HighwaterTimestamp } from "../util/highwaterTimestamp";
import { HighwaterTimestamp, JobStatusCell } from "../util";
import { JobDescriptionCell } from "./jobDescriptionCell";
import { JobStatusCell } from "../util/jobStatusCell";

import styles from "../jobs.module.scss";
import classNames from "classnames/bind";
Expand Down Expand Up @@ -125,7 +124,7 @@ const jobsTableColumns: ColumnDescriptor<Job>[] = [
style="tableTitle"
content={<p>User that created the job.</p>}
>
{"User"}
{"User Name"}
</Tooltip>
),
cell: job => job.username,
Expand Down Expand Up @@ -179,7 +178,13 @@ const jobsTableColumns: ColumnDescriptor<Job>[] = [
<Tooltip
placement="bottom"
style="tableTitle"
content={<p>Date and time the job was last executed.</p>}
content={
<p>
The high-water mark acts as a checkpoint for the changefeed’s job
progress, and guarantees that all changes before (or at) the
timestamp have been emitted.
</p>
}
>
{"High-water Timestamp"}
</Tooltip>
Expand Down
13 changes: 4 additions & 9 deletions pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsPage.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { assert, expect } from "chai";
import { assert } from "chai";
import moment from "moment";
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { JobsPage, JobsPageProps } from "./jobsPage";
import { formatDuration } from "../util/duration";
import {
allJobsFixture,
retryRunningJobFixture,
earliestRetainedTime,
} from "./jobsPage.fixture";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { allJobsFixture, earliestRetainedTime } from "./jobsPage.fixture";
import { render } from "@testing-library/react";
import React from "react";
import { MemoryRouter } from "react-router-dom";
import * as H from "history";
Expand Down Expand Up @@ -78,7 +73,7 @@ describe("Jobs", () => {
"Description",
"Status",
"Job ID",
"User",
"User Name",
"Creation Time (UTC)",
"Last Execution Time (UTC)",
"Execution Count",
Expand Down
30 changes: 16 additions & 14 deletions pkg/ui/workspaces/cluster-ui/src/jobs/jobsPage/jobsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { SortSetting } from "src/sortedtable";
import { syncHistory } from "src/util";

import { JobTable } from "./jobTable";
import { statusOptions, showOptions, typeOptions } from "../util/jobOptions";
import { statusOptions, showOptions, typeOptions } from "../util";

import { commonStyles } from "src/common";
import styles from "../jobs.module.scss";
Expand All @@ -48,11 +48,12 @@ export interface JobsPageDispatchProps {
setShow: (value: string) => void;
setType: (value: JobType) => void;
refreshJobs: (req: JobsRequest) => void;
onFilterChange?: (req: JobsRequest) => void;
}

export type JobsPageProps = JobsPageStateProps &
JobsPageDispatchProps &
RouteComponentProps<unknown>;
RouteComponentProps;

export class JobsPage extends React.Component<JobsPageProps> {
constructor(props: JobsPageProps) {
Expand All @@ -68,8 +69,8 @@ export class JobsPage extends React.Component<JobsPageProps> {
if (
this.props.setSort &&
columnTitle &&
(sortSetting.columnTitle != columnTitle ||
sortSetting.ascending != ascending)
(sortSetting.columnTitle !== columnTitle ||
sortSetting.ascending !== ascending)
) {
this.props.setSort({ columnTitle, ascending });
}
Expand All @@ -82,25 +83,26 @@ export class JobsPage extends React.Component<JobsPageProps> {

// Filter Show.
const show = searchParams.get("show") || undefined;
if (this.props.setShow && show && show != this.props.show) {
if (this.props.setShow && show && show !== this.props.show) {
this.props.setShow(show);
}

// Filter Type.
const type = parseInt(searchParams.get("type"), 10) || undefined;
if (this.props.setType && type && type != this.props.type) {
if (this.props.setType && type && type !== this.props.type) {
this.props.setType(type);
}
}

private refresh(props = this.props): void {
props.refreshJobs(
new cockroach.server.serverpb.JobsRequest({
status: props.status,
type: props.type,
limit: parseInt(props.show, 10),
}),
);
const jobsRequest = new cockroach.server.serverpb.JobsRequest({
status: props.status,
type: props.type,
limit: parseInt(props.show, 10),
});
props.onFilterChange
? props.onFilterChange(jobsRequest)
: props.refreshJobs(jobsRequest);
}

componentDidMount(): void {
Expand All @@ -113,7 +115,7 @@ export class JobsPage extends React.Component<JobsPageProps> {
prevProps.type !== this.props.type ||
prevProps.show !== this.props.show
) {
this.refresh(this.props);
this.refresh();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2022 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 { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router-dom";

import { AppState } from "src/store";
import {
selectJobsState,
selectShowSetting,
selectSortSetting,
selectTypeSetting,
selectStatusSetting,
} from "../../store/jobs/jobs.selectors";
import {
JobsPageStateProps,
JobsPageDispatchProps,
JobsPage,
} from "./jobsPage";
import { JobsRequest } from "src/api/jobsApi";
import { actions as jobsActions } from "src/store/jobs";
import { actions as localStorageActions } from "../../store/localStorage";
import { Dispatch } from "redux";
import { SortSetting } from "../../sortedtable";

const mapStateToProps = (
state: AppState,
_: RouteComponentProps,
): JobsPageStateProps => {
const sort = selectSortSetting(state);
const status = selectStatusSetting(state);
const show = selectShowSetting(state);
const type = selectTypeSetting(state);
const jobsState = selectJobsState(state);
const jobs = jobsState ? jobsState.data : null;
const jobsLoading = jobsState ? jobsState.inFlight : false;
const jobsError = jobsState ? jobsState.lastError : null;
return {
sort,
status,
show,
type,
jobs,
jobsLoading,
jobsError,
};
};

const mapDispatchToProps = (dispatch: Dispatch): JobsPageDispatchProps => ({
setShow: (showValue: string) => {
dispatch(
localStorageActions.update({
key: "showSetting/JobsPage",
value: showValue,
}),
);
},
setSort: (ss: SortSetting) => {
dispatch(
localStorageActions.update({
key: "sortSetting/JobsPage",
value: ss,
}),
);
},
setStatus: (statusValue: string) => {
dispatch(
localStorageActions.update({
key: "statusSetting/JobsPage",
value: statusValue,
}),
);
},
setType: (jobValue: number) => {
dispatch(
localStorageActions.update({
key: "typeSetting/JobsPage",
value: jobValue,
}),
);
},
refreshJobs: (req: JobsRequest) => dispatch(jobsActions.refresh(req)),
onFilterChange: (req: JobsRequest) =>
dispatch(jobsActions.updateFilteredJobs(req)),
});

export const JobsPageConnected = withRouter(
connect<JobsPageStateProps, JobsPageDispatchProps, RouteComponentProps>(
mapStateToProps,
mapDispatchToProps,
)(JobsPage),
);
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,13 @@ import {
import { Dispatch } from "redux";
import { Filters } from "../queryFilter";
import { sqlStatsSelector } from "../store/sqlStats/sqlStats.selector";
import { localStorageSelector } from "../store/utils/selectors";

export const selectSessionsData = createSelector(
sqlStatsSelector,
sessionsState => (sessionsState.valid ? sessionsState.data : null),
);

export const adminUISelector = createSelector(
(state: AppState) => state.adminUI,
adminUiState => adminUiState,
);

export const localStorageSelector = createSelector(
adminUISelector,
adminUiState => adminUiState.localStorage,
);

export const selectSessions = createSelector(
(state: AppState) => state.adminUI.sessions,
(state: SessionsState) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import { createSelector } from "reselect";
import { getActiveStatementsFromSessions } from "../activeExecutions/activeStatementUtils";
import { localStorageSelector } from "./statementsPage.selectors";
import { localStorageSelector } from "../store/utils/selectors";
import {
ActiveStatementFilters,
ActiveStatementsViewDispatchProps,
Expand Down
Loading

0 comments on commit 046d6e6

Please sign in to comment.