Skip to content

Commit

Permalink
ui: use cluster setting from redux on schema insights
Browse files Browse the repository at this point in the history
Part Of cockroachdb#108373

Use the value of the cluster setting
`sql.index_recommendation.drop_unused_duration` from redux,
instead of adding as part of the select.
With this change, now users with VIEWACTIVITY or
VIEWACTIVITYREDACTED can see index recommendations on the console,
without the need the view cluster settings permission.

This commit changes the Schema Insights Api.

Release note (ui change): Users without `VIEWCLUSTERSETTINGS` permission
but with `VIEWACTIVITY` or `VIEWACTIVITYREDACTED` can now see
index recommendations.
  • Loading branch information
maryliag committed Aug 18, 2023
1 parent 6716e92 commit 4ff9e91
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 32 deletions.
1 change: 1 addition & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/databaseDetailsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ type DatabaseIndexUsageStatsResponse = {

const getDatabaseIndexUsageStats: DatabaseDetailsQuery<IndexUsageStatistic> = {
createStmt: (dbName: string, csIndexUnusedDuration: string) => {
csIndexUnusedDuration = csIndexUnusedDuration ?? "168h";
return {
sql: Format(
`SELECT * FROM (SELECT
Expand Down
41 changes: 26 additions & 15 deletions pkg/ui/workspaces/cluster-ui/src/api/schemaInsightsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ type CreateIndexRecommendationsResponse = {
index_recommendations: string[];
};

export type SchemaInsightReqParams = {
csIndexUnusedDuration: string;
};

type SchemaInsightResponse =
| ClusterIndexUsageStatistic
| CreateIndexRecommendationsResponse;
type SchemaInsightQuery<RowType> = {
name: InsightType;
query: string;
query: string | ((csIndexUnusedDuration: string) => string);
toSchemaInsight: (response: SqlTxnResult<RowType>) => InsightRecommendation[];
};

Expand Down Expand Up @@ -142,12 +146,9 @@ function createIndexRecommendationsToSchemaInsight(
// and want to return the most used ones as a priority.
const dropUnusedIndexQuery: SchemaInsightQuery<ClusterIndexUsageStatistic> = {
name: "DropIndex",
query: `WITH cs AS (
SELECT value
FROM crdb_internal.cluster_settings
WHERE variable = 'sql.index_recommendation.drop_unused_duration'
)
SELECT * FROM (SELECT us.table_id,
query: (csIndexUnusedDuration: string) => {
csIndexUnusedDuration = csIndexUnusedDuration ?? "168h";
return `SELECT * FROM (SELECT us.table_id,
us.index_id,
us.last_read,
us.total_reads,
Expand All @@ -157,18 +158,18 @@ const dropUnusedIndexQuery: SchemaInsightQuery<ClusterIndexUsageStatistic> = {
t.parent_id as database_id,
t.database_name,
t.schema_name,
cs.value as unused_threshold,
cs.value::interval as interval_threshold,
'${csIndexUnusedDuration}' as unused_threshold,
'${csIndexUnusedDuration}'::interval as interval_threshold,
now() - COALESCE(us.last_read AT TIME ZONE 'UTC', COALESCE(ti.created_at, '0001-01-01')) as unused_interval
FROM "".crdb_internal.index_usage_statistics AS us
JOIN "".crdb_internal.table_indexes as ti
ON us.index_id = ti.index_id AND us.table_id = ti.descriptor_id
JOIN "".crdb_internal.tables as t
ON t.table_id = ti.descriptor_id and t.name = ti.descriptor_name
CROSS JOIN cs
WHERE t.database_name != 'system' AND ti.is_unique IS false)
WHERE unused_interval > interval_threshold
ORDER BY total_reads DESC;`,
ORDER BY total_reads DESC;`;
},
toSchemaInsight: clusterIndexUsageStatsToSchemaInsight,
};

Expand Down Expand Up @@ -211,14 +212,24 @@ const schemaInsightQueries: SchemaInsightQuery<SchemaInsightResponse>[] = [
createIndexRecommendationsQuery,
];

function getQuery(
csIndexUnusedDuration: string,
query: string | ((csIndexUnusedDuration: string) => string),
): string {
if (typeof query == "string") {
return query;
}
return query(csIndexUnusedDuration);
}

// getSchemaInsights makes requests over the SQL API and transforms the corresponding
// SQL responses into schema insights.
export async function getSchemaInsights(): Promise<
SqlApiResponse<InsightRecommendation[]>
> {
export async function getSchemaInsights(
params: SchemaInsightReqParams,
): Promise<SqlApiResponse<InsightRecommendation[]>> {
const request: SqlExecutionRequest = {
statements: schemaInsightQueries.map(insightQuery => ({
sql: insightQuery.query,
sql: getQuery(params.csIndexUnusedDuration, insightQuery.query),
})),
execute: true,
max_result_size: LARGE_RESULT_SIZE,
Expand Down
1 change: 1 addition & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ const getTableIndexUsageStats: TableDetailsQuery<IndexUsageStatistic> = {
[new Identifier(dbName), new SQL(tableName)],
new SQL("."),
);
csIndexUnusedDuration = csIndexUnusedDuration ?? "168h";
return {
sql: Format(
`WITH tableId AS (SELECT $1::regclass::int as table_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const SchemaInsightsPropsFixture: SchemaInsightsViewProps = {
schemaInsightType: "",
},
hasAdminRole: true,
csIndexUnusedDuration: "168h",
refreshSchemaInsights: () => {},
onSortChange: () => {},
onFiltersChange: () => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { actions as localStorageActions } from "../../store/localStorage";
import { Dispatch } from "redux";
import { selectHasAdminRole } from "../../store/uiConfig";
import { actions as analyticsActions } from "../../store/analytics";
import { selectDropUnusedIndexDuration } from "src/store/clusterSettings/clusterSettings.selectors";

const mapStateToProps = (
state: AppState,
Expand All @@ -45,6 +46,7 @@ const mapStateToProps = (
sortSetting: selectSortSetting(state),
hasAdminRole: selectHasAdminRole(state),
maxSizeApiReached: selectSchemaInsightsMaxApiSizeReached(state),
csIndexUnusedDuration: selectDropUnusedIndexDuration(state),
});

const mapDispatchToProps = (
Expand Down Expand Up @@ -82,8 +84,8 @@ const mapDispatchToProps = (
}),
);
},
refreshSchemaInsights: () => {
dispatch(actions.refresh());
refreshSchemaInsights: (csIndexUnusedDuration: string) => {
dispatch(actions.refresh({ csIndexUnusedDuration }));
},
refreshUserSQLRoles: () => dispatch(uiConfigActions.refreshUserSQLRoles()),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ export type SchemaInsightsViewStateProps = {
filters: SchemaInsightEventFilters;
sortSetting: SortSetting;
hasAdminRole: boolean;
csIndexUnusedDuration: string;
maxSizeApiReached?: boolean;
};

export type SchemaInsightsViewDispatchProps = {
onFiltersChange: (filters: SchemaInsightEventFilters) => void;
onSortChange: (ss: SortSetting) => void;
refreshSchemaInsights: () => void;
refreshSchemaInsights: (csIndexUnusedDuration: string) => void;
refreshUserSQLRoles: () => void;
};

Expand All @@ -83,6 +84,7 @@ export const SchemaInsightsView: React.FC<SchemaInsightsViewProps> = ({
onFiltersChange,
onSortChange,
maxSizeApiReached,
csIndexUnusedDuration,
}: SchemaInsightsViewProps) => {
const isCockroachCloud = useContext(CockroachCloudContext);
const [pagination, setPagination] = useState<ISortedTablePagination>({
Expand All @@ -95,13 +97,17 @@ export const SchemaInsightsView: React.FC<SchemaInsightsViewProps> = ({
);

useEffect(() => {
const refreshSchema = (): void => {
refreshSchemaInsights(csIndexUnusedDuration);
};

// Refresh every 1 minute.
refreshSchemaInsights();
const interval = setInterval(refreshSchemaInsights, 60 * 1000);
refreshSchema();
const interval = setInterval(refreshSchema, 60 * 1000);
return () => {
clearInterval(interval);
};
}, [refreshSchemaInsights]);
}, [refreshSchemaInsights, csIndexUnusedDuration]);

useEffect(() => {
// Refresh every 5 minutes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
// licenses/APL.txt.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { DOMAIN_NAME, noopReducer } from "../utils";
import { DOMAIN_NAME } from "../utils";
import moment, { Moment } from "moment-timezone";
import { InsightRecommendation } from "../../insights";
import { SqlApiResponse } from "src/api";
import { SchemaInsightReqParams, SqlApiResponse } from "src/api";

export type SchemaInsightsState = {
data: SqlApiResponse<InsightRecommendation[]>;
Expand Down Expand Up @@ -50,9 +50,8 @@ const schemaInsightsSlice = createSlice({
state.valid = false;
state.lastUpdated = moment.utc();
},
// Define actions that don't change state.
refresh: noopReducer,
request: noopReducer,
refresh: (_, _action: PayloadAction<SchemaInsightReqParams>) => {},
request: (_, _action: PayloadAction<SchemaInsightReqParams>) => {},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@
import { all, call, put, takeLatest } from "redux-saga/effects";

import { actions } from "./schemaInsights.reducer";
import { getSchemaInsights } from "../../api";
import { SchemaInsightReqParams, getSchemaInsights } from "../../api";
import { PayloadAction } from "@reduxjs/toolkit";

export function* refreshSchemaInsightsSaga() {
yield put(actions.request());
export function* refreshSchemaInsightsSaga(
action: PayloadAction<SchemaInsightReqParams>,
) {
yield put(actions.request(action.payload));
}

export function* requestSchemaInsightsSaga(): any {
export function* requestSchemaInsightsSaga(
action: PayloadAction<SchemaInsightReqParams>,
): any {
try {
const result = yield call(getSchemaInsights);
const result = yield call(getSchemaInsights, action.payload);
yield put(actions.received(result));
} catch (e) {
yield put(actions.failed(e));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
selectSchemaInsightsTypes,
} from "src/views/insights/insightsSelectors";
import { selectHasAdminRole } from "src/redux/user";
import { selectDropUnusedIndexDuration } from "src/redux/clusterSettings";

const mapStateToProps = (
state: AdminUIState,
Expand All @@ -44,13 +45,16 @@ const mapStateToProps = (
sortSetting: schemaInsightsSortLocalSetting.selector(state),
hasAdminRole: selectHasAdminRole(state),
maxSizeApiReached: selectSchemaInsightsMaxApiReached(state),
csIndexUnusedDuration: selectDropUnusedIndexDuration(state),
});

const mapDispatchToProps = {
onFiltersChange: (filters: SchemaInsightEventFilters) =>
schemaInsightsFiltersLocalSetting.set(filters),
onSortChange: (ss: SortSetting) => schemaInsightsSortLocalSetting.set(ss),
refreshSchemaInsights: refreshSchemaInsights,
refreshSchemaInsights: (csIndexUnusedDuration: string) => {
return refreshSchemaInsights({ csIndexUnusedDuration });
},
refreshUserSQLRoles: refreshUserSQLRoles,
};

Expand Down

0 comments on commit 4ff9e91

Please sign in to comment.