Skip to content

Commit

Permalink
ui: move events endpoint to use sql-over-http
Browse files Browse the repository at this point in the history
Part of: cockroachdb#89429

Addresses: cockroachdb#90272 (blocked from resolving by cockroachdb#80789)

This change migrates the existing `/events` request to use the
sql-over-http endpoint on apiV2, making this request tenant-scoped once
the sql-over-http endpoint is scoped to tenants (this should be the case
when cockroachdb#91323 is completed).

Release note: None
  • Loading branch information
Thomas Hardy committed Nov 11, 2022
1 parent 15369db commit 5828597
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 51 deletions.
99 changes: 99 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/eventsApi.ts
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 {
executeInternalSql,
SqlExecutionRequest,
sqlResultsAreEmpty,
SqlStatement,
} from "./sqlApi";
import { withTimeout } from "./util";
import moment from "moment";

export const defaultAPIEventLimit = 1000;

export type EventColumns = {
timestamp: string;
eventType: string;
reportingID: string;
info: string;
uniqueID: string;
};

type NonRedactedEventsRequest = {
type?: string;
limit?: number;
offset?: number;
};

export type EventsResponse = EventColumns[];

export const baseEventsQuery = `SELECT timestamp, \"eventType\", \"reportingID\", info, \"uniqueID\" FROM system.eventlog`;

function buildEventStatement({
type,
limit,
offset,
}: NonRedactedEventsRequest): SqlStatement {
let placeholder = 1;
const eventsStmt: SqlStatement = {
sql: baseEventsQuery,
arguments: [],
};
if (type) {
eventsStmt.sql += ` WHERE "eventType" = ` + type;
eventsStmt.arguments.push(type);
}
eventsStmt.sql += ` ORDER BY timestamp DESC`;
if (!limit || limit === 0) {
limit = defaultAPIEventLimit;
}
if (limit > 0) {
eventsStmt.sql += ` LIMIT $${placeholder}`;
eventsStmt.arguments.push(limit);
placeholder++;
}
if (offset && offset > 0) {
eventsStmt.sql += ` OFFSET $${placeholder}`;
eventsStmt.arguments.push(offset);
}
eventsStmt.sql += ";";
return eventsStmt;
}

// getEvents fetches events logs from the database. Callers of
// getEvents from cluster-ui will need to pass a timeout argument for
// promise timeout handling (callers from db-console already have promise
// timeout handling as part of the cacheDataReducer).
// Note that this endpoint is not able to redact event log information.
export function getNonRedactedEvents(
req: NonRedactedEventsRequest = {},
timeout?: moment.Duration,
): Promise<EventsResponse> {
const eventsStmt: SqlStatement = buildEventStatement(req);
const eventsRequest: SqlExecutionRequest = {
statements: [eventsStmt],
execute: true,
};
return withTimeout(
executeInternalSql<EventColumns>(eventsRequest),
timeout,
).then(result => {
// If request succeeded but query failed, throw error (caught by saga/cacheDataReducer).
if (result.error) {
throw result.error;
}

if (sqlResultsAreEmpty(result)) {
return [];
}
return result.execution.txn_results[0].rows;
});
}
1 change: 1 addition & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from "./schedulesApi";
export * from "./sqlApi";
export * from "./tracezApi";
export * from "./databasesApi";
export * from "./eventsApi";
4 changes: 2 additions & 2 deletions pkg/ui/workspaces/db-console/src/redux/apiReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const clusterReducerObj = new CachedDataReducer(
export const refreshCluster = clusterReducerObj.refresh;

const eventsReducerObj = new CachedDataReducer(
api.getEvents,
clusterUiApi.getNonRedactedEvents,
"events",
moment.duration(10, "s"),
);
Expand Down Expand Up @@ -486,7 +486,7 @@ export const refreshSnapshot = snapshotReducerObj.refresh;

export interface APIReducersState {
cluster: CachedDataReducerState<api.ClusterResponseMessage>;
events: CachedDataReducerState<api.EventsResponseMessage>;
events: CachedDataReducerState<clusterUiApi.EventsResponse>;
health: HealthState;
nodes: CachedDataReducerState<INodeStatus[]>;
raft: CachedDataReducerState<api.RaftDebugResponseMessage>;
Expand Down
2 changes: 1 addition & 1 deletion pkg/ui/workspaces/db-console/src/redux/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { AdminUIState } from "src/redux/state";
* eventsSelector selects the list of events from the store.
*/
export function eventsSelector(state: AdminUIState) {
return state.cachedData.events.data && state.cachedData.events.data.events;
return state.cachedData.events.data;
}

/**
Expand Down
19 changes: 0 additions & 19 deletions pkg/ui/workspaces/db-console/src/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ export type TableDetailsRequestMessage =
export type TableDetailsResponseMessage =
protos.cockroach.server.serverpb.TableDetailsResponse;

export type EventsRequestMessage =
protos.cockroach.server.serverpb.EventsRequest;
export type EventsResponseMessage =
protos.cockroach.server.serverpb.EventsResponse;

export type LocationsRequestMessage =
protos.cockroach.server.serverpb.LocationsRequest;
export type LocationsResponseMessage =
Expand Down Expand Up @@ -449,20 +444,6 @@ export function setUIData(
);
}

// getEvents gets event data
export function getEvents(
req: EventsRequestMessage,
timeout?: moment.Duration,
): Promise<EventsResponseMessage> {
const queryString = propsToQueryString(_.pick(req, ["type"]));
return timeoutFetch(
serverpb.EventsResponse,
`${API_PREFIX}/events?unredacted_events=true&${queryString}`,
null,
timeout,
);
}

export function getLocations(
_req: LocationsRequestMessage,
timeout?: moment.Duration,
Expand Down
22 changes: 6 additions & 16 deletions pkg/ui/workspaces/db-console/src/util/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,18 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import * as protobuf from "protobufjs/minimal";

import * as protos from "src/js/protos";
import * as eventTypes from "src/util/eventTypes";

type Event$Properties = protos.cockroach.server.serverpb.EventsResponse.IEvent;
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";

/**
* getEventDescription returns a short summary of an event.
*/
export function getEventDescription(e: Event$Properties): string {
const info: EventInfo = protobuf.util.isset(e, "info")
? JSON.parse(e.info)
: {};
export function getEventDescription(e: clusterUiApi.EventColumns): string {
const info: EventInfo = e.info ? JSON.parse(e.info) : {};
let privs = "";
let comment = "";

switch (e.event_type) {
switch (e.eventType) {
case eventTypes.CREATE_DATABASE:
return `Database Created: User ${info.User} created database ${info.DatabaseName}`;
case eventTypes.DROP_DATABASE: {
Expand Down Expand Up @@ -203,14 +197,10 @@ export function getEventDescription(e: Event$Properties): string {
eventTypes.UNSAFE_UPSERT_DESCRIPTOR,
eventTypes.UNSAFE_DELETE_DESCRIPTOR):
return `Unsafe: User ${info.User} executed crdb_internal.${
e.event_type
e.eventType
}, Info: ${JSON.stringify(info, null, 2)}`;
default:
return `Event: ${e.event_type}, content: ${JSON.stringify(
info,
null,
2,
)}`;
return `Event: ${e.eventType}, content: ${JSON.stringify(info, null, 2)}`;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import React from "react";
import { Helmet } from "react-helmet";
import { Link, RouteComponentProps, withRouter } from "react-router-dom";
import { connect } from "react-redux";
import * as protos from "src/js/protos";
import { refreshEvents } from "src/redux/apiReducers";
import {
eventsLastErrorSelector,
Expand All @@ -23,15 +22,17 @@ import {
} from "src/redux/events";
import { LocalSetting } from "src/redux/localsettings";
import { AdminUIState } from "src/redux/state";
import { util } from "@cockroachlabs/cluster-ui";
import { getEventDescription } from "src/util/events";
import { DATE_FORMAT_24_UTC } from "src/util/format";
import { ToolTipWrapper } from "src/views/shared/components/toolTip";
import { Loading, SortSetting, SortedTable } from "@cockroachlabs/cluster-ui";
import {
Loading,
SortSetting,
SortedTable,
api as clusterUiApi,
} from "@cockroachlabs/cluster-ui";
import "./events.styl";

type Event$Properties = protos.cockroach.server.serverpb.EventsResponse.IEvent;

// Number of events to show in the sidebar.
const EVENT_BOX_NUM_EVENTS = 5;

Expand All @@ -50,18 +51,17 @@ export interface SimplifiedEvent {
class EventSortedTable extends SortedTable<SimplifiedEvent> {}

export interface EventRowProps {
event: Event$Properties;
event: clusterUiApi.EventColumns;
}

export function getEventInfo(e: Event$Properties): SimplifiedEvent {
export function getEventInfo(e: clusterUiApi.EventColumns): SimplifiedEvent {
return {
fromNowString: util
.TimestampToMoment(e.timestamp)
fromNowString: moment(e.timestamp)
.format(DATE_FORMAT_24_UTC)
.replace("second", "sec")
.replace("minute", "min"),
content: <span>{getEventDescription(e)}</span>,
sortableTimestamp: util.TimestampToMoment(e.timestamp),
sortableTimestamp: moment(e.timestamp),
};
}

Expand All @@ -83,7 +83,7 @@ export class EventRow extends React.Component<EventRowProps, {}> {
}

export interface EventBoxProps {
events: Event$Properties[];
events: clusterUiApi.EventsResponse;
// eventsValid is needed so that this component will re-render when the events
// data becomes invalid, and thus trigger a refresh.
eventsValid: boolean;
Expand All @@ -109,7 +109,7 @@ export class EventBoxUnconnected extends React.Component<EventBoxProps, {}> {
<tbody>
{_.map(
_.take(events, EVENT_BOX_NUM_EVENTS),
(e: Event$Properties, i: number) => {
(e: clusterUiApi.EventColumns, i: number) => {
return <EventRow event={e} key={i} />;
},
)}
Expand All @@ -126,7 +126,7 @@ export class EventBoxUnconnected extends React.Component<EventBoxProps, {}> {
}

export interface EventPageProps {
events: Event$Properties[];
events: clusterUiApi.EventsResponse;
// eventsValid is needed so that this component will re-render when the events
// data becomes invalid, and thus trigger a refresh.
eventsValid: boolean;
Expand Down

0 comments on commit 5828597

Please sign in to comment.