From a68cd89792f654dfb2ad434eaac8fe3c92ddb27d Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Wed, 9 Nov 2022 13:30:32 -0500 Subject: [PATCH] ui: move events endpoint to use sql-over-http Part of: #89429 Addresses: #90272 (blocked from resolving by #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 #91323 is completed). Release note: None --- .../cluster-ui/src/api/eventsApi.ts | 108 +++++++++++++++ pkg/ui/workspaces/cluster-ui/src/api/index.ts | 1 + pkg/ui/workspaces/db-console/src/app.spec.tsx | 20 +-- .../db-console/src/redux/apiReducers.ts | 4 +- .../workspaces/db-console/src/redux/events.ts | 2 +- .../db-console/src/util/api.spec.ts | 128 +++++++++--------- pkg/ui/workspaces/db-console/src/util/api.ts | 19 --- .../db-console/src/util/eventTypes.ts | 30 ---- .../workspaces/db-console/src/util/events.ts | 22 +-- .../workspaces/db-console/src/util/fakeApi.ts | 54 +++++++- .../cluster/containers/events/events.spec.tsx | 36 +++-- .../views/cluster/containers/events/index.tsx | 19 ++- 12 files changed, 268 insertions(+), 175 deletions(-) create mode 100644 pkg/ui/workspaces/cluster-ui/src/api/eventsApi.ts diff --git a/pkg/ui/workspaces/cluster-ui/src/api/eventsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/eventsApi.ts new file mode 100644 index 000000000000..e152efd85008 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/api/eventsApi.ts @@ -0,0 +1,108 @@ +// 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, + LARGE_RESULT_SIZE, + SqlExecutionRequest, + sqlResultsAreEmpty, + SqlStatement, +} from "./sqlApi"; +import { withTimeout } from "./util"; +import moment from "moment"; + +// defaultEventsNumLimit is the default number of events to be returned. +export const defaultEventsNumLimit = 1000; + +export type EventColumns = { + timestamp: string; + eventType: string; + reportingID: string; + info: string; + uniqueID: string; +}; + +export 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 = defaultEventsNumLimit; + } + 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; +} + +export function buildEventsSQLRequest( + req: NonRedactedEventsRequest, +): SqlExecutionRequest { + const eventsStmt: SqlStatement = buildEventStatement(req); + return { + statements: [eventsStmt], + execute: true, + max_result_size: LARGE_RESULT_SIZE, + }; +} + +// getNonRedactedEvents fetches events logs from the database. Callers of +// getNonRedactedEvents 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 { + const eventsRequest: SqlExecutionRequest = buildEventsSQLRequest(req); + return withTimeout( + executeInternalSql(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; + }); +} diff --git a/pkg/ui/workspaces/cluster-ui/src/api/index.ts b/pkg/ui/workspaces/cluster-ui/src/api/index.ts index 45d73b4d236f..fcfb8d923da5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/index.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/index.ts @@ -21,3 +21,4 @@ export * from "./schedulesApi"; export * from "./sqlApi"; export * from "./tracezApi"; export * from "./databasesApi"; +export * from "./eventsApi"; diff --git a/pkg/ui/workspaces/db-console/src/app.spec.tsx b/pkg/ui/workspaces/db-console/src/app.spec.tsx index 2017b6b0648f..0e4d7669246c 100644 --- a/pkg/ui/workspaces/db-console/src/app.spec.tsx +++ b/pkg/ui/workspaces/db-console/src/app.spec.tsx @@ -9,6 +9,8 @@ // licenses/APL.txt. import { stubComponentInModule } from "./test-utils/mockComponent"; +stubComponentInModule("src/views/cluster/containers/nodeGraphs", "default"); +stubComponentInModule("src/views/cluster/containers/events", "EventPage"); stubComponentInModule("src/views/databases/databasesPage", "DatabasesPage"); stubComponentInModule( "src/views/databases/databaseDetailsPage", @@ -57,11 +59,9 @@ import { AdminUIState, createAdminUIStore } from "src/redux/state"; const CLUSTER_OVERVIEW_CAPACITY_LABEL = "Capacity Usage"; const CLUSTER_VIZ_NODE_MAP_LABEL = "Node Map"; -const METRICS_HEADER = "Metrics"; const NODE_LIST_LABEL = /Nodes \([\d]\)/; const LOADING_CLUSTER_STATUS = /Loading cluster status.*/; const NODE_LOG_HEADER = /Logs Node.*/; -const EVENTS_HEADER = "Events"; const JOBS_HEADER = "Jobs"; const SQL_ACTIVITY_HEADER = "SQL Activity"; const TRANSACTION_DETAILS_HEADER = "Transaction Details"; @@ -142,7 +142,7 @@ describe("Routing to", () => { describe("'/metrics' path", () => { test("routes to component", () => { navigateToPath("/metrics"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); test("redirected to '/metrics/overview/cluster'", () => { @@ -154,21 +154,21 @@ describe("Routing to", () => { describe("'/metrics/overview/cluster' path", () => { test("routes to component", () => { navigateToPath("/metrics/overview/cluster"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); }); describe("'/metrics/overview/node' path", () => { test("routes to component", () => { navigateToPath("/metrics/overview/node"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); }); describe("'/metrics/:dashboardNameAttr' path", () => { test("routes to component", () => { navigateToPath("/metrics/some-dashboard"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); test("redirected to '/metrics/:${dashboardNameAttr}/cluster'", () => { @@ -180,14 +180,14 @@ describe("Routing to", () => { describe("'/metrics/:dashboardNameAttr/cluster' path", () => { test("routes to component", () => { navigateToPath("/metrics/some-dashboard/cluster"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); }); describe("'/metrics/:dashboardNameAttr/node' path", () => { test("routes to component", () => { navigateToPath("/metrics/some-dashboard/node"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); test("redirected to '/metrics/:${dashboardNameAttr}/cluster'", () => { @@ -199,7 +199,7 @@ describe("Routing to", () => { describe("'/metrics/:dashboardNameAttr/node/:nodeIDAttr' path", () => { test("routes to component", () => { navigateToPath("/metrics/some-dashboard/node/123"); - screen.getByText(METRICS_HEADER, { selector: "h3" }); + screen.getByTestId("nodeGraphs"); }); }); @@ -238,7 +238,7 @@ describe("Routing to", () => { describe("'/events' path", () => { test("routes to component", () => { navigateToPath("/events"); - screen.getByText(EVENTS_HEADER, { selector: "h1" }); + screen.getByTestId("EventPage"); }); }); diff --git a/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts b/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts index 1135c6c0eb81..402408cda08f 100644 --- a/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts +++ b/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts @@ -49,7 +49,7 @@ export const clusterReducerObj = new CachedDataReducer( export const refreshCluster = clusterReducerObj.refresh; const eventsReducerObj = new CachedDataReducer( - api.getEvents, + clusterUiApi.getNonRedactedEvents, "events", moment.duration(10, "s"), ); @@ -515,7 +515,7 @@ export const refreshSnapshot = snapshotReducerObj.refresh; export interface APIReducersState { cluster: CachedDataReducerState; - events: CachedDataReducerState; + events: CachedDataReducerState; health: HealthState; nodes: CachedDataReducerState; raft: CachedDataReducerState; diff --git a/pkg/ui/workspaces/db-console/src/redux/events.ts b/pkg/ui/workspaces/db-console/src/redux/events.ts index 67161f2291c1..073e6ef4e613 100644 --- a/pkg/ui/workspaces/db-console/src/redux/events.ts +++ b/pkg/ui/workspaces/db-console/src/redux/events.ts @@ -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; } /** diff --git a/pkg/ui/workspaces/db-console/src/util/api.spec.ts b/pkg/ui/workspaces/db-console/src/util/api.spec.ts index 1034cab5a34a..0b1fe15f8ca4 100644 --- a/pkg/ui/workspaces/db-console/src/util/api.spec.ts +++ b/pkg/ui/workspaces/db-console/src/util/api.spec.ts @@ -20,7 +20,10 @@ import * as api from "./api"; import { api as clusterUiApi } from "@cockroachlabs/cluster-ui"; import { REMOTE_DEBUGGING_ERROR_TEXT } from "src/util/constants"; import Severity = cockroach.util.log.Severity; -import { buildSQLApiDatabasesResponse } from "src/util/fakeApi"; +import { + buildSQLApiDatabasesResponse, + buildSQLApiEventsResponse, +} from "src/util/fakeApi"; describe("rest api", function () { describe("databases request", function () { @@ -297,95 +300,90 @@ describe("rest api", function () { }); describe("events request", function () { - const eventsPrefixMatcher = `begin:${api.API_PREFIX}/events?`; - afterEach(fetchMock.restore); it("correctly requests events", function () { // Mock out the fetch query fetchMock.mock({ - matcher: eventsPrefixMatcher, - method: "GET", + matcher: clusterUiApi.SQL_API_PATH, + method: "POST", response: (_url: string, requestObj: RequestInit) => { - expect(requestObj.body).toBeUndefined(); - const encodedResponse = - protos.cockroach.server.serverpb.EventsResponse.encode({ - events: [{ event_type: "test" }], - }).finish(); + expect(JSON.parse(requestObj.body.toString())).toEqual({ + ...clusterUiApi.buildEventsSQLRequest({}), + application_name: clusterUiApi.INTERNAL_SQL_API_APP, + }); return { - body: encodedResponse, + body: JSON.stringify( + buildSQLApiEventsResponse([ + { + eventType: "test", + timestamp: "2016-01-25T10:10:10.555555", + reportingID: "1", + info: `{"Timestamp":1668442242840943000,"EventType":"test","NodeID":1,"StartedAt":1668442242644228000,"LastUp":1668442242644228000}`, + uniqueID: "\\\x4ce0d9e74bd5480ab1d9e6f98cc2f483", + }, + ]), + ), }; }, }); - return api - .getEvents(new protos.cockroach.server.serverpb.EventsRequest()) - .then(result => { - expect(fetchMock.calls(eventsPrefixMatcher).length).toBe(1); - expect(result.events.length).toBe(1); - }); + return clusterUiApi.getNonRedactedEvents().then(result => { + expect(fetchMock.calls(clusterUiApi.SQL_API_PATH).length).toBe(1); + expect(result.length).toBe(1); + }); }); it("correctly requests filtered events", function () { - const req = new protos.cockroach.server.serverpb.EventsRequest({ - type: "test type", - }); + const req: clusterUiApi.NonRedactedEventsRequest = { type: "test" }; // Mock out the fetch query fetchMock.mock({ - matcher: eventsPrefixMatcher, - method: "GET", - response: (url: string, requestObj: RequestInit) => { - const params = url.split("?")[1].split("&"); - expect(params.length).toBe(2); - _.each(params, param => { - let [k, v] = param.split("="); - k = decodeURIComponent(k); - v = decodeURIComponent(v); - switch (k) { - case "type": - expect(req.type).toEqual(v); - break; - - case "unredacted_events": - break; - - default: - throw new Error(`Unknown property ${k}`); - } + matcher: clusterUiApi.SQL_API_PATH, + method: "POST", + response: (_url: string, requestObj: RequestInit) => { + expect(JSON.parse(requestObj.body.toString())).toEqual({ + ...clusterUiApi.buildEventsSQLRequest(req), + application_name: clusterUiApi.INTERNAL_SQL_API_APP, }); - expect(requestObj.body).toBeUndefined(); - const encodedResponse = - protos.cockroach.server.serverpb.EventsResponse.encode({ - events: [{ event_type: "test" }], - }).finish(); return { - body: encodedResponse, + body: JSON.stringify( + buildSQLApiEventsResponse([ + { + eventType: "test", + timestamp: "2016-01-25T10:10:10.555555", + reportingID: "1", + info: `{"Timestamp":1668442242840943000,"EventType":"test","NodeID":1,"StartedAt":1668442242644228000,"LastUp":1668442242644228000}`, + uniqueID: "\\\x4ce0d9e74bd5480ab1d9e6f98cc2f483", + }, + ]), + ), }; }, }); - return api - .getEvents(new protos.cockroach.server.serverpb.EventsRequest(req)) - .then(result => { - expect(fetchMock.calls(eventsPrefixMatcher).length).toBe(1); - expect(result.events.length).toBe(1); - }); + return clusterUiApi.getNonRedactedEvents(req).then(result => { + expect(fetchMock.calls(clusterUiApi.SQL_API_PATH).length).toBe(1); + expect(result.length).toBe(1); + }); }); it("correctly handles an error", function (done) { // Mock out the fetch query, but return a 500 status code fetchMock.mock({ - matcher: eventsPrefixMatcher, - method: "GET", + matcher: clusterUiApi.SQL_API_PATH, + method: "POST", response: (_url: string, requestObj: RequestInit) => { - expect(requestObj.body).toBeUndefined(); + expect(JSON.parse(requestObj.body.toString())).toEqual({ + ...clusterUiApi.buildEventsSQLRequest({}), + application_name: clusterUiApi.INTERNAL_SQL_API_APP, + }); return { throws: new Error() }; }, }); - api - .getEvents(new protos.cockroach.server.serverpb.EventsRequest()) + clusterUiApi + .getNonRedactedEvents() .then(_result => { done(new Error("Request unexpectedly succeeded.")); }) @@ -398,19 +396,19 @@ describe("rest api", function () { it("correctly times out", function (done) { // Mock out the fetch query, but return a promise that's never resolved to test the timeout fetchMock.mock({ - matcher: eventsPrefixMatcher, - method: "GET", + matcher: clusterUiApi.SQL_API_PATH, + method: "POST", response: (_url: string, requestObj: RequestInit) => { - expect(requestObj.body).toBeUndefined(); + expect(JSON.parse(requestObj.body.toString())).toEqual({ + ...clusterUiApi.buildEventsSQLRequest({}), + application_name: clusterUiApi.INTERNAL_SQL_API_APP, + }); return new Promise(() => {}); }, }); - api - .getEvents( - new protos.cockroach.server.serverpb.EventsRequest(), - moment.duration(0), - ) + clusterUiApi + .getNonRedactedEvents({}, moment.duration(0)) .then(_result => { done(new Error("Request unexpectedly succeeded.")); }) diff --git a/pkg/ui/workspaces/db-console/src/util/api.ts b/pkg/ui/workspaces/db-console/src/util/api.ts index 442aefbaf845..ee0c6c475a87 100644 --- a/pkg/ui/workspaces/db-console/src/util/api.ts +++ b/pkg/ui/workspaces/db-console/src/util/api.ts @@ -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 = @@ -429,20 +424,6 @@ export function setUIData( ); } -// getEvents gets event data -export function getEvents( - req: EventsRequestMessage, - timeout?: moment.Duration, -): Promise { - 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, diff --git a/pkg/ui/workspaces/db-console/src/util/eventTypes.ts b/pkg/ui/workspaces/db-console/src/util/eventTypes.ts index 906de712df0b..a708be13729a 100644 --- a/pkg/ui/workspaces/db-console/src/util/eventTypes.ts +++ b/pkg/ui/workspaces/db-console/src/util/eventTypes.ts @@ -12,10 +12,6 @@ import _ from "lodash"; -import * as protos from "src/js/protos"; - -type Event = protos.cockroach.server.serverpb.EventsResponse.Event; - // Recorded when a database is created. export const CREATE_DATABASE = "create_database"; // Recorded when a database is dropped. @@ -180,29 +176,3 @@ export const allEvents = [ ...settingsEvents, ...jobEvents, ]; - -const nodeEventSet = _.invert(nodeEvents); -const databaseEventSet = _.invert(databaseEvents); -const tableEventSet = _.invert(tableEvents); -const settingsEventSet = _.invert(settingsEvents); -const jobsEventSet = _.invert(jobEvents); - -export function isNodeEvent(e: Event): boolean { - return !_.isUndefined(nodeEventSet[e.event_type]); -} - -export function isDatabaseEvent(e: Event): boolean { - return !_.isUndefined(databaseEventSet[e.event_type]); -} - -export function isTableEvent(e: Event): boolean { - return !_.isUndefined(tableEventSet[e.event_type]); -} - -export function isSettingsEvent(e: Event): boolean { - return !_.isUndefined(settingsEventSet[e.event_type]); -} - -export function isJobsEvent(e: Event): boolean { - return !_.isUndefined(jobsEventSet[e.event_type]); -} diff --git a/pkg/ui/workspaces/db-console/src/util/events.ts b/pkg/ui/workspaces/db-console/src/util/events.ts index 30eff7b07f84..b42967745d7b 100644 --- a/pkg/ui/workspaces/db-console/src/util/events.ts +++ b/pkg/ui/workspaces/db-console/src/util/events.ts @@ -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: { @@ -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)}`; } } diff --git a/pkg/ui/workspaces/db-console/src/util/fakeApi.ts b/pkg/ui/workspaces/db-console/src/util/fakeApi.ts index 844099e18d88..1e6f421655f3 100644 --- a/pkg/ui/workspaces/db-console/src/util/fakeApi.ts +++ b/pkg/ui/workspaces/db-console/src/util/fakeApi.ts @@ -66,16 +66,15 @@ export function stubClusterSettings( } export function buildSQLApiDatabasesResponse(databases: string[]) { - const rows: clusterUiApi.DatabasesColumns[] = []; - databases.forEach(database => { - rows.push({ + const rows: clusterUiApi.DatabasesColumns[] = databases.map(database => { + return { database_name: database, owner: "root", primary_region: null, secondary_region: null, regions: [], survival_goal: null, - }); + }; }); return { num_statements: 1, @@ -126,9 +125,53 @@ export function buildSQLApiDatabasesResponse(databases: string[]) { }; } +export function buildSQLApiEventsResponse(events: clusterUiApi.EventsResponse) { + return { + num_statements: 1, + execution: { + txn_results: [ + { + statement: 1, + tag: "SELECT", + start: "2022-11-14T16:26:45.06819Z", + end: "2022-11-14T16:26:45.073657Z", + rows_affected: 0, + columns: [ + { + name: "timestamp", + type: "TIMESTAMP", + oid: 1114, + }, + { + name: "eventType", + type: "STRING", + oid: 25, + }, + { + name: "reportingID", + type: "INT8", + oid: 20, + }, + { + name: "info", + type: "STRING", + oid: 25, + }, + { + name: "uniqueID", + type: "BYTES", + oid: 17, + }, + ], + rows: events, + }, + ], + }, + }; +} + export function stubDatabases(databases: string[]) { const response = buildSQLApiDatabasesResponse(databases); - console.log("SQL API PATH", clusterUiApi.SQL_API_PATH); fetchMock.mock({ headers: { Accept: "application/json", @@ -141,7 +184,6 @@ export function stubDatabases(databases: string[]) { expect(JSON.parse(requestObj.body.toString())).toEqual( clusterUiApi.databasesRequest, ); - console.log("STRINGIFY", JSON.stringify(response)); return { body: JSON.stringify(response), }; diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx index 051b7132ec6d..6df9320ce8a0 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/events.spec.tsx @@ -12,7 +12,6 @@ import React from "react"; import { mount, shallow } from "enzyme"; import _ from "lodash"; -import * as protos from "src/js/protos"; import { EventBoxUnconnected as EventBox, EventRow, @@ -21,11 +20,10 @@ import { import { refreshEvents } from "src/redux/apiReducers"; import { allEvents } from "src/util/eventTypes"; import { ToolTipWrapper } from "src/views/shared/components/toolTip"; - -type Event = protos.cockroach.server.serverpb.EventsResponse.Event; +import { api as clusterUiApi } from "@cockroachlabs/cluster-ui"; function makeEventBox( - events: protos.cockroach.server.serverpb.EventsResponse.IEvent[], + events: clusterUiApi.EventsResponse, refreshEventsFn: typeof refreshEvents, ) { return shallow( @@ -37,10 +35,22 @@ function makeEventBox( ); } -function makeEvent(event: Event) { +function makeEvent(event: clusterUiApi.EventColumns) { return mount(); } +const createEventWithEventType = ( + eventType: string, +): clusterUiApi.EventColumns => { + return { + eventType: eventType, + timestamp: "2016-01-25T10:10:10.555555", + reportingID: "1", + info: `{"Timestamp":1668442242840943000,"EventType":"${eventType}","NodeID":1,"StartedAt":1668442242644228000,"LastUp":1668442242644228000}`, + uniqueID: "\\\x4ce0d9e74bd5480ab1d9e6f98cc2f483", + }; +}; + describe("", function () { const spy = jest.fn(); @@ -55,11 +65,10 @@ describe("", function () { describe("", function () { describe("attach", function () { it("correctly renders a known event", function () { - const e = new protos.cockroach.server.serverpb.EventsResponse.Event({ - event_type: "create_database", - }); - + const e: clusterUiApi.EventColumns = + createEventWithEventType("create_database"); const provider = makeEvent(e); + expect( provider .find("div.events__message > span") @@ -70,9 +79,7 @@ describe("", function () { }); it("correctly renders an unknown event", function () { - const e = new protos.cockroach.server.serverpb.EventsResponse.Event({ - event_type: "unknown", - }); + const e: clusterUiApi.EventColumns = createEventWithEventType("unknown"); const provider = makeEvent(e); expect( @@ -86,9 +93,8 @@ describe("", function () { describe("getEventInfo", function () { it("covers every currently known event", function () { _.each(allEvents, eventType => { - const event = new protos.cockroach.server.serverpb.EventsResponse.Event({ - event_type: eventType, - }); + const event: clusterUiApi.EventColumns = + createEventWithEventType(eventType); const eventContent = shallow( getEventInfo(event).content as React.ReactElement, ); diff --git a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx index 41ece622aead..505233051f7e 100644 --- a/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx +++ b/pkg/ui/workspaces/db-console/src/views/cluster/containers/events/index.tsx @@ -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, @@ -30,11 +29,10 @@ import { SortSetting, SortedTable, util, + 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; @@ -53,18 +51,17 @@ export interface SimplifiedEvent { class EventSortedTable extends SortedTable {} 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(util.DATE_FORMAT_24_UTC) .replace("second", "sec") .replace("minute", "min"), content: {getEventDescription(e)}, - sortableTimestamp: util.TimestampToMoment(e.timestamp), + sortableTimestamp: moment(e.timestamp), }; } @@ -86,7 +83,7 @@ export class EventRow extends React.Component { } 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; @@ -112,7 +109,7 @@ export class EventBoxUnconnected extends React.Component { {_.map( _.take(events, EVENT_BOX_NUM_EVENTS), - (e: Event$Properties, i: number) => { + (e: clusterUiApi.EventColumns, i: number) => { return ; }, )} @@ -129,7 +126,7 @@ export class EventBoxUnconnected extends React.Component { } 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;