forked from cockroachdb/cockroach
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
86409: ui: Add page to view schedules in DB console. r=benbardin a=benbardin *Summary* Adds /schedules page in DB console. Structure is very similar to /jobs page. The new /schedules page provides a bit more observability into what schedules are running, and their various states. Release note (ui change): Add page to view schedules in DB console. Release justification: Low-risk - new, read-only page. See also cockroachdb#70725 *Artifacts* Loom link: https://www.loom.com/share/0fb4fee5859d4a46a3b6442892ae9ef6 <img width="1277" alt="Screen Shot 2022-08-18 at 2 23 40 PM" src="https://user-images.githubusercontent.com/261508/185491986-3063e9b5-af0f-41c5-bd69-1b7b6a9a871b.png"> <img width="1276" alt="Screen Shot 2022-08-18 at 2 23 14 PM" src="https://user-images.githubusercontent.com/261508/185491988-dba19a72-f1d6-4d74-9529-370d5012a0ad.png"> <img width="1260" alt="Screen Shot 2022-08-18 at 2 36 39 PM" src="https://user-images.githubusercontent.com/261508/185491985-3fd8293b-e6e1-4e6c-9f90-31b31b988677.png"> <img width="1257" alt="Screen Shot 2022-08-18 at 2 23 20 PM" src="https://user-images.githubusercontent.com/261508/185491987-fa5dc38e-1f8b-48b2-9c8f-39926db70e27.png"> Co-authored-by: Ben Bardin <[email protected]>
- Loading branch information
Showing
23 changed files
with
1,528 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// 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 Long from "long"; | ||
import moment from "moment"; | ||
import { executeSql, SqlExecutionRequest } from "./sqlApi"; | ||
import { RequestError } from "../util"; | ||
|
||
type ScheduleColumns = { | ||
id: string; | ||
label: string; | ||
schedule_status: string; | ||
next_run: string; | ||
state: string; | ||
recurrence: string; | ||
jobsrunning: number; | ||
owner: string; | ||
created: string; | ||
command: string; | ||
}; | ||
|
||
export type Schedule = { | ||
id: Long; | ||
label: string; | ||
status: string; | ||
nextRun?: moment.Moment; | ||
state: string; | ||
recurrence: string; | ||
jobsRunning: number; | ||
owner: string; | ||
created: moment.Moment; | ||
command: string; | ||
}; | ||
|
||
export type Schedules = Schedule[]; | ||
|
||
export function getSchedules(req: { | ||
status: string; | ||
limit: number; | ||
}): Promise<Schedules> { | ||
// Cast int64 to string, since otherwise it gets truncated. | ||
// Likewise, prettify `command` on the server since contained int64s | ||
// may also be truncated. | ||
let stmt = ` | ||
WITH schedules AS (SHOW SCHEDULES) | ||
SELECT id::string, label, schedule_status, next_run, | ||
state, recurrence, jobsrunning, owner, | ||
created, jsonb_pretty(command) as command | ||
FROM schedules | ||
`; | ||
const args = []; | ||
if (req.status) { | ||
stmt += " WHERE schedule_status = $" + (args.length + 1); | ||
args.push(req.status); | ||
} | ||
stmt += " ORDER BY created DESC"; | ||
if (req.limit) { | ||
stmt += " LIMIT $" + (args.length + 1); | ||
args.push(req.limit.toString()); | ||
} | ||
const request: SqlExecutionRequest = { | ||
statements: [ | ||
{ | ||
sql: stmt, | ||
arguments: args, | ||
}, | ||
], | ||
execute: true, | ||
}; | ||
return executeSql<ScheduleColumns>(request).then(result => { | ||
const txn_results = result.execution.txn_results; | ||
if (txn_results.length === 0 || !txn_results[0].rows) { | ||
// No data. | ||
return []; | ||
} | ||
|
||
return txn_results[0].rows.map(row => { | ||
return { | ||
id: Long.fromString(row.id), | ||
label: row.label, | ||
status: row.schedule_status, | ||
nextRun: row.next_run ? moment.utc(row.next_run) : null, | ||
state: row.state, | ||
recurrence: row.recurrence, | ||
jobsRunning: row.jobsrunning, | ||
owner: row.owner, | ||
created: moment.utc(row.created), | ||
command: JSON.parse(row.command), | ||
}; | ||
}); | ||
}); | ||
} | ||
|
||
export function getSchedule(id: Long): Promise<Schedule> { | ||
const request: SqlExecutionRequest = { | ||
statements: [ | ||
{ | ||
// Cast int64 to string, since otherwise it gets truncated. | ||
// Likewise, prettify `command` on the server since contained int64s | ||
// may also be truncated. | ||
sql: ` | ||
WITH schedules AS (SHOW SCHEDULES) | ||
SELECT id::string, label, schedule_status, next_run, | ||
state, recurrence, jobsrunning, owner, | ||
created, jsonb_pretty(command) as command | ||
FROM schedules | ||
WHERE ID = $1::int64 | ||
`, | ||
arguments: [id.toString()], | ||
}, | ||
], | ||
execute: true, | ||
}; | ||
return executeSql<ScheduleColumns>(request).then(result => { | ||
const txn_results = result.execution.txn_results; | ||
if (txn_results.length === 0 || !txn_results[0].rows) { | ||
// No data. | ||
throw new RequestError( | ||
"Bad Request", | ||
400, | ||
"No schedule found with this ID.", | ||
); | ||
} | ||
|
||
if (txn_results[0].rows.length > 1) { | ||
throw new RequestError( | ||
"Internal Server Error", | ||
500, | ||
"Multiple schedules found for ID.", | ||
); | ||
} | ||
const row = txn_results[0].rows[0]; | ||
return { | ||
id: Long.fromString(row.id), | ||
label: row.label, | ||
status: row.schedule_status, | ||
nextRun: row.next_run ? moment.utc(row.next_run) : null, | ||
state: row.state, | ||
recurrence: row.recurrence, | ||
jobsRunning: row.jobsrunning, | ||
owner: row.owner, | ||
created: moment.utc(row.created), | ||
command: row.command, | ||
}; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// 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. | ||
|
||
export * from "./schedulesPage"; | ||
export * from "./scheduleDetailsPage"; |
11 changes: 11 additions & 0 deletions
11
pkg/ui/workspaces/cluster-ui/src/schedules/scheduleDetailsPage/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// 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. | ||
|
||
export * from "./scheduleDetails"; |
126 changes: 126 additions & 0 deletions
126
pkg/ui/workspaces/cluster-ui/src/schedules/scheduleDetailsPage/scheduleDetails.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// 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 { ArrowLeft } from "@cockroachlabs/icons"; | ||
import { Col, Row } from "antd"; | ||
import "antd/lib/col/style"; | ||
import "antd/lib/row/style"; | ||
import Long from "long"; | ||
import React, { useEffect } from "react"; | ||
import Helmet from "react-helmet"; | ||
import { RouteComponentProps } from "react-router-dom"; | ||
import { Schedule } from "src/api/schedulesApi"; | ||
import { Button } from "src/button"; | ||
import { Loading } from "src/loading"; | ||
import { SqlBox, SqlBoxSize } from "src/sql"; | ||
import { SummaryCard, SummaryCardItem } from "src/summaryCard"; | ||
import { DATE_FORMAT_24_UTC } from "src/util/format"; | ||
import { getMatchParamByName } from "src/util/query"; | ||
|
||
import { commonStyles } from "src/common"; | ||
import summaryCardStyles from "src/summaryCard/summaryCard.module.scss"; | ||
import scheduleStyles from "src/schedules/schedules.module.scss"; | ||
|
||
import classNames from "classnames/bind"; | ||
|
||
const cardCx = classNames.bind(summaryCardStyles); | ||
const scheduleCx = classNames.bind(scheduleStyles); | ||
|
||
export interface ScheduleDetailsStateProps { | ||
schedule: Schedule; | ||
scheduleError: Error | null; | ||
scheduleLoading: boolean; | ||
} | ||
|
||
export interface ScheduleDetailsDispatchProps { | ||
refreshSchedule: (id: Long) => void; | ||
} | ||
|
||
export type ScheduleDetailsProps = ScheduleDetailsStateProps & | ||
ScheduleDetailsDispatchProps & | ||
RouteComponentProps<unknown>; | ||
|
||
export const ScheduleDetails: React.FC<ScheduleDetailsProps> = props => { | ||
const idStr = getMatchParamByName(props.match, "id"); | ||
const { refreshSchedule } = props; | ||
useEffect(() => { | ||
refreshSchedule(Long.fromString(idStr)); | ||
}, [idStr, refreshSchedule]); | ||
|
||
const prevPage = (): void => props.history.goBack(); | ||
|
||
const renderContent = (): React.ReactElement => { | ||
const schedule = props.schedule; | ||
return ( | ||
<> | ||
<Row gutter={24}> | ||
<Col className="gutter-row" span={24}> | ||
<SqlBox value={schedule.command} size={SqlBoxSize.custom} /> | ||
</Col> | ||
</Row> | ||
<Row gutter={24}> | ||
<Col className="gutter-row" span={12}> | ||
<SummaryCard> | ||
<SummaryCardItem label="Label" value={schedule.label} /> | ||
<SummaryCardItem label="Status" value={schedule.status} /> | ||
<SummaryCardItem label="State" value={schedule.state} /> | ||
</SummaryCard> | ||
</Col> | ||
<Col className="gutter-row" span={12}> | ||
<SummaryCard className={cardCx("summary-card")}> | ||
<SummaryCardItem | ||
label="Creation Time" | ||
value={schedule.created?.format(DATE_FORMAT_24_UTC)} | ||
/> | ||
<SummaryCardItem | ||
label="Next Execution Time" | ||
value={schedule.nextRun?.format(DATE_FORMAT_24_UTC)} | ||
/> | ||
<SummaryCardItem label="Recurrence" value={schedule.recurrence} /> | ||
<SummaryCardItem | ||
label="Jobs Running" | ||
value={String(schedule.jobsRunning)} | ||
/> | ||
<SummaryCardItem label="Owner" value={schedule.owner} /> | ||
</SummaryCard> | ||
</Col> | ||
</Row> | ||
</> | ||
); | ||
}; | ||
|
||
return ( | ||
<div className={scheduleCx("schedule-details")}> | ||
<Helmet title={"Details | Schedule"} /> | ||
<div className={scheduleCx("section page--header")}> | ||
<Button | ||
onClick={prevPage} | ||
type="unstyled-link" | ||
size="small" | ||
icon={<ArrowLeft fontSize={"10px"} />} | ||
iconPosition="left" | ||
className={commonStyles("small-margin")} | ||
> | ||
Schedules | ||
</Button> | ||
<h3 | ||
className={scheduleCx("page--header__title")} | ||
>{`Schedule ID: ${idStr}`}</h3> | ||
</div> | ||
<section className={scheduleCx("section section--container")}> | ||
<Loading | ||
loading={!props.schedule || props.scheduleLoading} | ||
page={"schedule details"} | ||
error={props.scheduleError} | ||
render={renderContent} | ||
/> | ||
</section> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.