Skip to content

Commit

Permalink
jobs: add button to request execution details
Browse files Browse the repository at this point in the history
This is the last of the three PRs to add support
for requesting, viewing and downloading execution
details from the job details page.

This change wires up the logic needed to request
the execution details for a given job. The request
is powered by the crdb_internal.request_job_execution_details
builtin that triggers the collection of execution details.

Fixes: cockroachdb#105076
Release note: None
  • Loading branch information
adityamaru committed Jul 27, 2023
1 parent 566f3cb commit f23c468
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 2 deletions.
41 changes: 41 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/jobProfilerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { fetchData } from "./fetchData";
import { SqlExecutionRequest, executeInternalSql } from "./sqlApi";

export type ListJobProfilerExecutionDetailsRequest =
cockroach.server.serverpb.ListJobProfilerExecutionDetailsRequest;
Expand Down Expand Up @@ -44,3 +45,43 @@ export const getExecutionDetailFile = (
"30M",
);
};

export type CollectExecutionDetailsRequest = {
job_id: Long;
};

export type CollectExecutionDetailsResponse = {
req_resp: boolean;
};

export function collectExecutionDetails({
job_id,
}: CollectExecutionDetailsRequest): Promise<CollectExecutionDetailsResponse> {
const args: any = [job_id.toString()];

const collectExecutionDetails = {
sql: `SELECT crdb_internal.request_job_execution_details($1::INT) as req_resp`,
arguments: args,
};

const req: SqlExecutionRequest = {
execute: true,
statements: [collectExecutionDetails],
};

return executeInternalSql<CollectExecutionDetailsResponse>(req).then(res => {
// If request succeeded but query failed, throw error (caught by saga/cacheDataReducer).
if (res.error) {
throw res.error;
}

if (
res.execution?.txn_results[0]?.rows?.length === 0 ||
res.execution?.txn_results[0]?.rows[0]["req_resp"] === false
) {
throw new Error("Failed to collect execution details");
}

return res.execution.txn_results[0].rows[0];
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import jobStyles from "src/jobs/jobs.module.scss";
import classNames from "classnames/bind";
import { Timestamp } from "../../timestamp";
import {
CollectExecutionDetailsResponse,
GetJobProfilerExecutionDetailRequest,
GetJobProfilerExecutionDetailResponse,
ListJobProfilerExecutionDetailsRequest,
Expand All @@ -49,6 +50,7 @@ import {
import moment from "moment-timezone";
import { CockroachCloudContext } from "src/contexts";
import { JobProfilerView } from "./jobProfilerView";
import long from "long";

const { TabPane } = Tabs;

Expand All @@ -75,6 +77,7 @@ export interface JobDetailsDispatchProps {
refreshExecutionDetailFiles: (
req: ListJobProfilerExecutionDetailsRequest,
) => void;
onRequestExecutionDetails: (jobID: long) => void;
}

export interface JobDetailsState {
Expand Down Expand Up @@ -144,6 +147,7 @@ export class JobDetails extends React.Component<
onDownloadExecutionFileClicked={
this.props.onDownloadExecutionFileClicked
}
onRequestExecutionDetails={this.props.onRequestExecutionDetails}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
actions as jobProfilerActions,
} from "src/store/jobs/jobProfiler.reducer";
import { Dispatch } from "redux";
import long from "long";

const emptyState = createInitialState<JobResponse>();

Expand All @@ -52,6 +53,9 @@ const mapDispatchToProps = (dispatch: Dispatch): JobDetailsDispatchProps => ({
refreshJob: (req: JobRequest) => jobActions.refresh(req),
refreshExecutionDetailFiles: (req: ListJobProfilerExecutionDetailsRequest) =>
dispatch(jobProfilerActions.refresh(req)),
onRequestExecutionDetails: (jobID: long) => {
dispatch(jobProfilerActions.collectExecutionDetails({ job_id: jobID }));
},
});

export const JobDetailsPageConnected = withRouter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type JobProfilerDispatchProps = {
refreshExecutionDetailFiles: (
req: ListJobProfilerExecutionDetailsRequest,
) => void;
onRequestExecutionDetails: (jobID: long) => void;
};

export type JobProfilerViewProps = JobProfilerStateProps &
Expand Down Expand Up @@ -143,6 +144,7 @@ export const JobProfilerView: React.FC<JobProfilerViewProps> = ({
isDataValid,
onDownloadExecutionFileClicked,
refreshExecutionDetailFiles,
onRequestExecutionDetails,
}: JobProfilerViewProps) => {
const columns = makeJobProfilerViewColumns(
jobID,
Expand Down Expand Up @@ -198,6 +200,20 @@ export const JobProfilerView: React.FC<JobProfilerViewProps> = ({
<p className={summaryCardStylesCx("summary--card__divider--large")} />
<Row gutter={24}>
<Col className="gutter-row" span={24}>
<Button
as="a"
size="small"
intent="secondary"
className={cx("request-execution-detail-button")}
onClick={() => {
onRequestExecutionDetails(jobID);
}}
>
Request Execution Details
</Button>
<p
className={summaryCardStylesCx("summary--card__divider--large")}
/>
<SortedTable
data={executionDetailFilesResponse.data?.files}
columns={columns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
// licenses/APL.txt.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { DOMAIN_NAME } from "../utils";
import { DOMAIN_NAME, noopReducer } from "../utils";
import moment from "moment-timezone";
import { createInitialState, RequestState } from "src/api/types";
import {
CollectExecutionDetailsRequest,
ListJobProfilerExecutionDetailsRequest,
ListJobProfilerExecutionDetailsResponse,
} from "src/api";
Expand Down Expand Up @@ -58,6 +59,15 @@ const JobProfilerExecutionDetailsSlice = createSlice({
) => {
state.inFlight = true;
},
collectExecutionDetails: (
_state,
_action: PayloadAction<CollectExecutionDetailsRequest>,
) => {},
collectExecutionDetailsCompleted: noopReducer,
collectExecutionDetailsFailed: (
_state,
_action: PayloadAction<Error>,
) => {},
},
});

Expand Down
16 changes: 16 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/store/jobs/jobProfiler.sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { actions } from "./jobProfiler.reducer";
import { call, put, all, takeEvery } from "redux-saga/effects";
import {
ListJobProfilerExecutionDetailsRequest,
collectExecutionDetails,
listExecutionDetailFiles,
} from "src/api";

Expand All @@ -33,9 +34,24 @@ export function* requestJobProfilerSaga(
}
}

export function* collectExecutionDetailsSaga(
action: ReturnType<typeof actions.collectExecutionDetails>,
) {
try {
yield call(collectExecutionDetails, action.payload);
yield put(actions.collectExecutionDetailsCompleted());
// request execution details to reflect changed state for newly
// requested statement.
yield put(actions.request());
} catch (e) {
yield put(actions.collectExecutionDetailsFailed(e));
}
}

export function* jobProfilerSaga() {
yield all([
takeEvery(actions.refresh, refreshJobProfilerSaga),
takeEvery(actions.request, requestJobProfilerSaga),
takeEvery(actions.collectExecutionDetails, collectExecutionDetailsSaga),
]);
}
41 changes: 41 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/jobs/jobsActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 { Action } from "redux";
import { PayloadAction } from "src/interfaces/action";
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";

export const COLLECT_EXECUTION_DETAILS =
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS";
export const COLLECT_EXECUTION_DETAILS_COMPLETE =
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS_COMPLETE";
export const COLLECT_EXECUTION_DETAILS_FAILED =
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS_FAILED";

export function collectExecutionDetailsAction(
collectExecutionDetailsRequest: clusterUiApi.CollectExecutionDetailsRequest,
): PayloadAction<clusterUiApi.CollectExecutionDetailsRequest> {
return {
type: COLLECT_EXECUTION_DETAILS,
payload: collectExecutionDetailsRequest,
};
}

export function collectExecutionDetailsCompleteAction(): Action {
return {
type: COLLECT_EXECUTION_DETAILS_COMPLETE,
};
}

export function collectExecutionDetailsFailedAction(): Action {
return {
type: COLLECT_EXECUTION_DETAILS_FAILED,
};
}
37 changes: 37 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/jobs/jobsSagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 { PayloadAction } from "@reduxjs/toolkit";
import { refreshListExecutionDetailFiles } from "oss/src/redux/apiReducers";
import { all, call, put, takeEvery } from "redux-saga/effects";
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";
import {
COLLECT_EXECUTION_DETAILS,
collectExecutionDetailsCompleteAction,
collectExecutionDetailsFailedAction,
} from "./jobsActions";

export function* collectExecutionDetailsSaga(
action: PayloadAction<clusterUiApi.CollectExecutionDetailsRequest>,
) {
try {
yield call(clusterUiApi.collectExecutionDetails, action.payload);
yield put(collectExecutionDetailsCompleteAction());
yield put(refreshListExecutionDetailFiles() as any);
} catch (e) {
yield put(collectExecutionDetailsFailedAction());
}
}

export function* jobsSaga() {
yield all([
takeEvery(COLLECT_EXECUTION_DETAILS, collectExecutionDetailsSaga),
]);
}
2 changes: 2 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import { sessionsSaga } from "./sessions";
import { sqlStatsSaga } from "./sqlStats";
import { indexUsageStatsSaga } from "./indexUsageStats";
import { timeScaleSaga } from "src/redux/timeScale";
import { jobsSaga } from "./jobs/jobsSagas";

export default function* rootSaga() {
yield all([
fork(queryMetricsSaga),
fork(localSettingsSaga),
fork(customAnalyticsSaga),
fork(statementsSaga),
fork(jobsSaga),
fork(analyticsSaga),
fork(sessionsSaga),
fork(sqlStatsSaga),
Expand Down
9 changes: 8 additions & 1 deletion pkg/ui/workspaces/db-console/src/views/jobs/jobDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import {
refreshListExecutionDetailFiles,
refreshJob,
} from "src/redux/apiReducers";
import { AdminUIState } from "src/redux/state";
import { AdminUIState, AppDispatch } from "src/redux/state";
import { ListJobProfilerExecutionDetailsResponseMessage } from "src/util/api";
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";
import { collectExecutionDetailsAction } from "oss/src/redux/jobs/jobsActions";
import long from "long";

const selectJob = createSelectorForKeyedCachedDataField("job", selectID);
const selectExecutionDetailFiles =
Expand Down Expand Up @@ -49,6 +51,11 @@ const mapStateToProps = (
const mapDispatchToProps = {
refreshJob,
refreshExecutionDetailFiles: refreshListExecutionDetailFiles,
onRequestExecutionDetails: (jobID: long) => {
return (dispatch: AppDispatch) => {
dispatch(collectExecutionDetailsAction({ job_id: jobID }));
};
},
};

export default withRouter(
Expand Down

0 comments on commit f23c468

Please sign in to comment.