Skip to content

Commit edf1b33

Browse files
committed
ui: refactor schema insights to remove redux and sagas
Refactors the schema insights component to no longer require redux and sagags. It now uses `useSwr` for data fetching. In order to remove the dependency on redux and sagas, some functionality was removed from this component. This functionality is: 1. dispatching analytics.track actions 2. persisting table sorting and filtering to browser session storage. The analytics.track actions were used in cloud console to track when the table is sorted or a filter is applied. We decided this doesn't provide very much value to replace and removed it. Epic: None Release note: None
1 parent f7af5dc commit edf1b33

File tree

14 files changed

+183
-587
lines changed

14 files changed

+183
-587
lines changed

pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
InsightType,
99
recommendDropUnusedIndex,
1010
} from "../insights";
11-
import { HexStringToInt64String } from "../util";
11+
import { HexStringToInt64String, useSwrWithClusterId } from "../util";
1212

1313
import { QuoteIdentifier } from "./safesql";
1414
import {
@@ -56,7 +56,7 @@ type SchemaInsightResponse =
5656
| CreateIndexRecommendationsResponse;
5757
type SchemaInsightQuery<RowType> = {
5858
name: InsightType;
59-
query: string | ((csIndexUnusedDuration: string) => string);
59+
query: string | (() => string);
6060
toSchemaInsight: (response: SqlTxnResult<RowType>) => InsightRecommendation[];
6161
};
6262

@@ -142,7 +142,7 @@ function createIndexRecommendationsToSchemaInsight(
142142
// and want to return the most used ones as a priority.
143143
const dropUnusedIndexQuery: SchemaInsightQuery<ClusterIndexUsageStatistic> = {
144144
name: "DropIndex",
145-
query: (_: string) => {
145+
query: () => {
146146
return `SELECT * FROM (SELECT us.table_id,
147147
us.index_id,
148148
us.last_read,
@@ -209,24 +209,21 @@ const schemaInsightQueries: Array<
209209
| SchemaInsightQuery<CreateIndexRecommendationsResponse>
210210
> = [dropUnusedIndexQuery, createIndexRecommendationsQuery];
211211

212-
function getQuery(
213-
csIndexUnusedDuration: string,
214-
query: string | ((csIndexUnusedDuration: string) => string),
215-
): string {
212+
function getQuery(query: string | (() => string)): string {
216213
if (typeof query == "string") {
217214
return query;
218215
}
219-
return query(csIndexUnusedDuration);
216+
return query();
220217
}
221218

222219
// getSchemaInsights makes requests over the SQL API and transforms the corresponding
223220
// SQL responses into schema insights.
224-
export async function getSchemaInsights(
225-
params: SchemaInsightReqParams,
226-
): Promise<SqlApiResponse<InsightRecommendation[]>> {
221+
export async function getSchemaInsights(): Promise<
222+
SqlApiResponse<InsightRecommendation[]>
223+
> {
227224
const request: SqlExecutionRequest = {
228225
statements: schemaInsightQueries.map(insightQuery => ({
229-
sql: getQuery(params.csIndexUnusedDuration, insightQuery.query),
226+
sql: getQuery(insightQuery.query),
230227
})),
231228
execute: true,
232229
max_result_size: LARGE_RESULT_SIZE,
@@ -258,3 +255,16 @@ export async function getSchemaInsights(
258255
"retrieving insights information",
259256
);
260257
}
258+
259+
export function useSchemaInsights() {
260+
return useSwrWithClusterId<SqlApiResponse<InsightRecommendation[]>>(
261+
"getInsightRecommendations",
262+
() => {
263+
return getSchemaInsights();
264+
},
265+
{
266+
// Refresh every 1 minute.
267+
refreshInterval: 60 * 1_000,
268+
},
269+
);
270+
}

pkg/ui/workspaces/cluster-ui/src/api/userApi.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
77

88
import { fetchData } from "src/api";
99

10-
export type UserSQLRolesRequestMessage =
11-
cockroach.server.serverpb.UserSQLRolesRequest;
10+
import { useSwrWithClusterId } from "../util";
11+
1212
export type UserSQLRolesResponseMessage =
1313
cockroach.server.serverpb.UserSQLRolesResponse;
1414

@@ -21,3 +21,11 @@ export function getUserSQLRoles(): Promise<UserSQLRolesResponseMessage> {
2121
"30M",
2222
);
2323
}
24+
25+
export function useUserSQLRoles() {
26+
return useSwrWithClusterId("userSQLRoles", () => getUserSQLRoles(), {
27+
// Only call every 5 minutes
28+
dedupingInterval: 4_999,
29+
refreshInterval: 5_000,
30+
});
31+
}

pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsights.fixture.ts

Lines changed: 44 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,53 @@
33
// Use of this software is governed by the CockroachDB Software License
44
// included in the /LICENSE file.
55

6-
import { indexUnusedDuration } from "src/util/constants";
6+
import { InsightRecommendation } from "../types";
77

8-
import { SchemaInsightsViewProps } from "./schemaInsightsView";
9-
10-
export const SchemaInsightsPropsFixture: SchemaInsightsViewProps = {
11-
schemaInsights: [
12-
{
13-
type: "DropIndex",
14-
database: "db_name",
15-
indexDetails: {
16-
table: "table_name",
17-
indexID: 1,
18-
indexName: "index_name",
19-
schema: "public",
20-
lastUsed:
21-
"This index has not been used and can be removed for better write performance.",
22-
},
23-
},
24-
{
25-
type: "DropIndex",
26-
database: "db_name2",
27-
indexDetails: {
28-
table: "table_name2",
29-
indexID: 2,
30-
indexName: "index_name2",
31-
schema: "public",
32-
lastUsed:
33-
"This index has not been used in over 9 days, 5 hours, and 3 minutes and can be removed for better write performance.",
34-
},
8+
export const SchemaInsightsPropsFixture: InsightRecommendation[] = [
9+
{
10+
type: "DropIndex",
11+
database: "db_name",
12+
indexDetails: {
13+
table: "table_name",
14+
indexID: 1,
15+
indexName: "index_name",
16+
schema: "public",
17+
lastUsed:
18+
"This index has not been used and can be removed for better write performance.",
3519
},
36-
{
37-
type: "CreateIndex",
38-
database: "db_name",
39-
query: "CREATE INDEX ON test_table (another_num) STORING (num);",
40-
execution: {
41-
statement: "SELECT * FROM test_table WHERE another_num > _",
42-
summary: "SELECT * FROM test_table",
43-
fingerprintID: "\\xc093e4523ab0bd3e",
44-
implicit: true,
45-
},
20+
},
21+
{
22+
type: "DropIndex",
23+
database: "db_name2",
24+
indexDetails: {
25+
table: "table_name2",
26+
indexID: 2,
27+
indexName: "index_name2",
28+
schema: "public",
29+
lastUsed:
30+
"This index has not been used in over 9 days, 5 hours, and 3 minutes and can be removed for better write performance.",
4631
},
47-
{
48-
type: "CreateIndex",
49-
database: "db_name",
50-
query: "CREATE INDEX ON test_table (yet_another_num) STORING (num);",
51-
execution: {
52-
statement: "SELECT * FROM test_table WHERE yet_another_num > _",
53-
summary: "SELECT * FROM test_table",
54-
fingerprintID: "\\xc093e4523ab0db9o",
55-
implicit: false,
56-
},
32+
},
33+
{
34+
type: "CreateIndex",
35+
database: "db_name",
36+
query: "CREATE INDEX ON test_table (another_num) STORING (num);",
37+
execution: {
38+
statement: "SELECT * FROM test_table WHERE another_num > _",
39+
summary: "SELECT * FROM test_table",
40+
fingerprintID: "\\xc093e4523ab0bd3e",
41+
implicit: true,
5742
},
58-
],
59-
schemaInsightsDatabases: ["db_name", "db_name2"],
60-
schemaInsightsTypes: ["DropIndex", "CreateIndex"],
61-
schemaInsightsError: null,
62-
sortSetting: {
63-
ascending: false,
64-
columnTitle: "insights",
6543
},
66-
filters: {
67-
database: "",
68-
schemaInsightType: "",
44+
{
45+
type: "CreateIndex",
46+
database: "db_name",
47+
query: "CREATE INDEX ON test_table (yet_another_num) STORING (num);",
48+
execution: {
49+
statement: "SELECT * FROM test_table WHERE yet_another_num > _",
50+
summary: "SELECT * FROM test_table",
51+
fingerprintID: "\\xc093e4523ab0db9o",
52+
implicit: false,
53+
},
6954
},
70-
hasAdminRole: true,
71-
csIndexUnusedDuration: indexUnusedDuration,
72-
refreshSchemaInsights: () => {},
73-
onSortChange: () => {},
74-
onFiltersChange: () => {},
75-
refreshUserSQLRoles: () => {},
76-
};
55+
];

pkg/ui/workspaces/cluster-ui/src/insights/schemaInsights/schemaInsightsPageConnected.tsx

Lines changed: 3 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -3,99 +3,7 @@
33
// Use of this software is governed by the CockroachDB Software License
44
// included in the /LICENSE file.
55

6-
import { connect } from "react-redux";
7-
import { RouteComponentProps, withRouter } from "react-router-dom";
8-
import { Dispatch } from "redux";
6+
import { SchemaInsightsView } from "./schemaInsightsView";
97

10-
import { SortSetting } from "src/sortedtable";
11-
import { AppState, uiConfigActions } from "src/store";
12-
import { selectDropUnusedIndexDuration } from "src/store/clusterSettings/clusterSettings.selectors";
13-
import {
14-
actions,
15-
selectSchemaInsights,
16-
selectSchemaInsightsDatabases,
17-
selectSchemaInsightsError,
18-
selectSchemaInsightsMaxApiSizeReached,
19-
selectSchemaInsightsTypes,
20-
selectFilters,
21-
selectSortSetting,
22-
} from "src/store/schemaInsights";
23-
24-
import { actions as analyticsActions } from "../../store/analytics";
25-
import { actions as localStorageActions } from "../../store/localStorage";
26-
import { selectHasAdminRole } from "../../store/uiConfig";
27-
import { SchemaInsightEventFilters } from "../types";
28-
29-
import {
30-
SchemaInsightsView,
31-
SchemaInsightsViewDispatchProps,
32-
SchemaInsightsViewStateProps,
33-
} from "./schemaInsightsView";
34-
35-
const mapStateToProps = (
36-
state: AppState,
37-
_props: RouteComponentProps,
38-
): SchemaInsightsViewStateProps => ({
39-
schemaInsights: selectSchemaInsights(state),
40-
schemaInsightsDatabases: selectSchemaInsightsDatabases(state),
41-
schemaInsightsTypes: selectSchemaInsightsTypes(state),
42-
schemaInsightsError: selectSchemaInsightsError(state),
43-
filters: selectFilters(state),
44-
sortSetting: selectSortSetting(state),
45-
hasAdminRole: selectHasAdminRole(state),
46-
maxSizeApiReached: selectSchemaInsightsMaxApiSizeReached(state),
47-
csIndexUnusedDuration: selectDropUnusedIndexDuration(state),
48-
});
49-
50-
const mapDispatchToProps = (
51-
dispatch: Dispatch,
52-
): SchemaInsightsViewDispatchProps => ({
53-
onFiltersChange: (filters: SchemaInsightEventFilters) => {
54-
dispatch(
55-
localStorageActions.update({
56-
key: "filters/SchemaInsightsPage",
57-
value: filters,
58-
}),
59-
);
60-
dispatch(
61-
analyticsActions.track({
62-
name: "Filter Clicked",
63-
page: "Schema Insights",
64-
filterName: "filters",
65-
value: filters.toString(),
66-
}),
67-
);
68-
},
69-
onSortChange: (ss: SortSetting) => {
70-
dispatch(
71-
localStorageActions.update({
72-
key: "sortSetting/SchemaInsightsPage",
73-
value: ss,
74-
}),
75-
);
76-
dispatch(
77-
analyticsActions.track({
78-
name: "Column Sorted",
79-
page: "Schema Insights",
80-
tableName: "Schema Insights Table",
81-
columnName: ss.columnTitle,
82-
}),
83-
);
84-
},
85-
refreshSchemaInsights: (csIndexUnusedDuration: string) => {
86-
dispatch(actions.refresh({ csIndexUnusedDuration }));
87-
},
88-
refreshUserSQLRoles: () => dispatch(uiConfigActions.refreshUserSQLRoles()),
89-
});
90-
91-
export const SchemaInsightsPageConnected = withRouter(
92-
connect<
93-
SchemaInsightsViewStateProps,
94-
SchemaInsightsViewDispatchProps,
95-
RouteComponentProps,
96-
AppState
97-
>(
98-
mapStateToProps,
99-
mapDispatchToProps,
100-
)(SchemaInsightsView),
101-
);
8+
// This is here for backwards compatability sake, as this is used in Cloud Console.
9+
export const SchemaInsightsPageConnected = SchemaInsightsView;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
import { render } from "@testing-library/react";
7+
import React from "react";
8+
import { MemoryRouter } from "react-router-dom";
9+
10+
import * as schemaInsightsApi from "../../api/schemaInsightsApi";
11+
import * as userApi from "../../api/userApi";
12+
13+
import { SchemaInsightsPropsFixture } from "./schemaInsights.fixture";
14+
import { SchemaInsightsView } from "./schemaInsightsView";
15+
16+
describe("schemaInsightsView", () => {
17+
// let spy: jest.MockInstance<any, any>;
18+
// afterEach(() => {
19+
// // spy.mockClear();
20+
// });
21+
22+
it("renders expected table columns", async () => {
23+
const insightsSpy = jest
24+
.spyOn(schemaInsightsApi, "useSchemaInsights")
25+
.mockReturnValue({
26+
data: {
27+
maxSizeReached: false,
28+
results: SchemaInsightsPropsFixture,
29+
},
30+
isLoading: false,
31+
error: null,
32+
mutate: null,
33+
isValidating: false,
34+
});
35+
36+
const userRoleApiSpy = jest
37+
.spyOn(userApi, "useUserSQLRoles")
38+
.mockReturnValue({
39+
data: { roles: ["ADMIN"] },
40+
isLoading: false,
41+
error: null,
42+
mutate: null,
43+
isValidating: false,
44+
});
45+
46+
const { getAllByText } = render(
47+
<MemoryRouter>
48+
<SchemaInsightsView />
49+
</MemoryRouter>,
50+
);
51+
const dropIndexes = getAllByText("Drop Unused Index");
52+
expect(dropIndexes).toHaveLength(2);
53+
54+
const createIndexes = getAllByText("Create Index");
55+
expect(createIndexes).toHaveLength(2);
56+
57+
insightsSpy.mockClear();
58+
userRoleApiSpy.mockClear();
59+
});
60+
});

0 commit comments

Comments
 (0)