From a903401ac27c42791f326c92fb439c308a1ea716 Mon Sep 17 00:00:00 2001 From: David Hartunian Date: Tue, 20 Dec 2022 18:37:21 -0500 Subject: [PATCH] ui: dynamically read ui data on js load Previous work that dynamically loaded the base ui data that we attach to `Window` did not account for JS code which referenced the `dataFromServer` variable directly in top-level consts. This led to a broken login widget in the top right corner and docs links that would pin to "stable" versions instead of the specific version link of the running cluster. This commit does not completely fix the issue but it applies several mitigations: First, the redux store is now initialized with the loaded copy of the `dataFromServer` info. This ensures that login information is initialized properly. Second, the store is now constructed in the promise we define in `index.tsx` instead of inline in the file in which it's defined, allowing us to control execution ordering. Third, one of the `dataFromServer` usages in the redux login selector is moved to be inside the selector to grab values at runtime. Last, the `docs.ts` file is updated to no longer use `const`s for all the doc link strings and instead use `let` which allows us to reload the links at runtime after we have version data from the server. Resolves #93273 Epic: None Release note (ui change): Secure clusters now show correct login information in the top right corner. Docs links correctly reference the current cluster version when necessary. Release note (bug fix): Secure clusters now show correct login information in the top right corner. Docs links correctly reference the current cluster version when necessary. --- pkg/ui/workspaces/db-console/src/index.tsx | 7 +- .../workspaces/db-console/src/redux/login.ts | 1 + .../workspaces/db-console/src/redux/state.ts | 34 +++- pkg/ui/workspaces/db-console/src/util/docs.ts | 178 ++++++++++-------- 4 files changed, 137 insertions(+), 83 deletions(-) diff --git a/pkg/ui/workspaces/db-console/src/index.tsx b/pkg/ui/workspaces/db-console/src/index.tsx index 3c4eaf4212a9..87ec91fed83e 100644 --- a/pkg/ui/workspaces/db-console/src/index.tsx +++ b/pkg/ui/workspaces/db-console/src/index.tsx @@ -15,19 +15,24 @@ import "src/protobufInit"; import { alertDataSync } from "src/redux/alerts"; import { App } from "src/app"; import { history } from "src/redux/history"; -import { store } from "src/redux/state"; +import { createAdminUIStore } from "src/redux/state"; import "src/redux/analytics"; import { DataFromServer, fetchDataFromServer, + getDataFromServer, setDataFromServer, } from "src/util/dataFromServer"; +import { recomputeDocsURLs } from "src/util/docs"; async function fetchAndRender() { setDataFromServer( (await fetchDataFromServer().catch(() => {})) as DataFromServer, ); + const store = createAdminUIStore(history, getDataFromServer()); + recomputeDocsURLs(); + ReactDOM.render( , document.getElementById("react-layout"), diff --git a/pkg/ui/workspaces/db-console/src/redux/login.ts b/pkg/ui/workspaces/db-console/src/redux/login.ts index 30e773b43225..443a8a5928bf 100644 --- a/pkg/ui/workspaces/db-console/src/redux/login.ts +++ b/pkg/ui/workspaces/db-console/src/redux/login.ts @@ -109,6 +109,7 @@ class NoLoginState { export const selectLoginState = createSelector( (state: AdminUIState) => state.login, (login: LoginAPIState) => { + const dataFromServer = getDataFromServer(); if (!dataFromServer.ExperimentalUseLogin) { return new NoLoginState(); } diff --git a/pkg/ui/workspaces/db-console/src/redux/state.ts b/pkg/ui/workspaces/db-console/src/redux/state.ts index 9e716ffa0000..40179f811133 100644 --- a/pkg/ui/workspaces/db-console/src/redux/state.ts +++ b/pkg/ui/workspaces/db-console/src/redux/state.ts @@ -25,7 +25,6 @@ import { RouterState, } from "connected-react-router"; import { History } from "history"; -import { history } from "./history"; import { apiReducersReducer, APIReducersState } from "./apiReducers"; import { hoverReducer, HoverState } from "./hover"; @@ -37,6 +36,7 @@ import { uiDataReducer, UIDataState } from "./uiData"; import { loginReducer, LoginAPIState } from "./login"; import rootSaga from "./sagas"; import { initializeAnalytics } from "./analytics"; +import { DataFromServer } from "src/util/dataFromServer"; export interface AdminUIState { cachedData: APIReducersState; @@ -51,9 +51,25 @@ export interface AdminUIState { login: LoginAPIState; } +const emptyDataFromServer: DataFromServer = { + ExperimentalUseLogin: false, + FeatureFlags: {}, + LoggedInUser: "", + LoginEnabled: false, + NodeID: "", + OIDCAutoLogin: false, + OIDCButtonText: "", + OIDCLoginEnabled: false, + Tag: "", + Version: "", +}; + // createAdminUIStore is a function that returns a new store for the admin UI. // It's in a function so it can be recreated as necessary for testing. -export function createAdminUIStore(historyInst: History) { +export function createAdminUIStore( + historyInst: History, + dataFromServer: DataFromServer = emptyDataFromServer, +) { const sagaMiddleware = createSagaMiddleware(); const routerReducer = connectRouter(historyInst); @@ -70,6 +86,16 @@ export function createAdminUIStore(historyInst: History) { uiData: uiDataReducer, login: loginReducer, }), + { + login: { + loggedInUser: dataFromServer.LoggedInUser, + error: null, + inProgress: false, + oidcAutoLogin: dataFromServer.OIDCAutoLogin, + oidcLoginEnabled: dataFromServer.OIDCLoginEnabled, + oidcButtonText: dataFromServer.OIDCButtonText, + }, + }, compose( applyMiddleware(thunk, sagaMiddleware, routerMiddleware(historyInst)), // Support for redux dev tools @@ -96,8 +122,4 @@ export function createAdminUIStore(historyInst: History) { return s; } -const store = createAdminUIStore(history); - export type AppDispatch = ThunkDispatch; - -export { store }; diff --git a/pkg/ui/workspaces/db-console/src/util/docs.ts b/pkg/ui/workspaces/db-console/src/util/docs.ts index 21f037259b14..46a9b4961900 100644 --- a/pkg/ui/workspaces/db-console/src/util/docs.ts +++ b/pkg/ui/workspaces/db-console/src/util/docs.ts @@ -11,82 +11,54 @@ import { getDataFromServer } from "src/util/dataFromServer"; const stable = "stable"; -const version = getDataFromServer().Version || stable; -const docsURLBase = "https://www.cockroachlabs.com/docs/" + version; -const docsURLBaseNoVersion = "https://www.cockroachlabs.com/docs/" + stable; function docsURL(pageName: string): string { + const version = getDataFromServer().Version || stable; + const docsURLBase = "https://www.cockroachlabs.com/docs/" + version; return `${docsURLBase}/${pageName}`; } function docsURLNoVersion(pageName: string): string { + const docsURLBaseNoVersion = "https://www.cockroachlabs.com/docs/" + stable; return `${docsURLBaseNoVersion}/${pageName}`; } -export const adminUILoginNoVersion = docsURLNoVersion( +export let adminUILoginNoVersion = docsURLNoVersion( "ui-overview.html#db-console-security", ); -export const startFlags = docsURL("start-a-node.html#flags"); -export const pauseJob = docsURL("pause-job.html"); -export const cancelJob = docsURL("cancel-job.html"); -export const enableNodeMap = docsURL("enable-node-map.html"); -export const configureReplicationZones = docsURL( - "configure-replication-zones.html", -); -export const transactionalPipelining = docsURL( - "architecture/transaction-layer.html#transaction-pipelining", -); -export const adminUIAccess = docsURL("ui-overview.html#db-console-access"); -export const howAreCapacityMetricsCalculated = docsURL( - "ui-storage-dashboard.html#capacity-metrics", -); -export const howAreCapacityMetricsCalculatedOverview = docsURL( - "ui-cluster-overview-page.html#capacity-metrics", -); -export const keyValuePairs = docsURL( - "architecture/distribution-layer.html#table-data", -); -export const writeIntents = docsURL( - "architecture/transaction-layer.html#write-intents", -); -export const metaRanges = docsURL( - "architecture/distribution-layer.html#meta-ranges", -); -export const databaseTable = docsURL("ui-databases-page.html"); -export const jobTable = docsURL("ui-jobs-page.html"); -export const jobStatus = docsURL("ui-jobs-page.html#job-status"); -export const jobsPause = docsURL("pause-job"); -export const jobsResume = docsURL("resume-job"); -export const jobsCancel = docsURL("cancel-job"); -export const statementsTable = docsURL("ui-statements-page.html"); -export const statementDiagnostics = docsURL( - "ui-statements-page.html#diagnostics", -); -export const statementsSql = docsURL( - "ui-statements-page.html#sql-statement-fingerprints", -); -export const statementsRetries = docsURL( - "transactions.html#transaction-retries", -); -export const transactionRetryErrorReference = docsURL( - "transaction-retry-error-reference.html", -); -export const capacityMetrics = docsURL( - "ui-cluster-overview-page.html#capacity-metrics", -); -export const nodeLivenessIssues = docsURL( - "cluster-setup-troubleshooting.html#node-liveness-issues", -); -export const howItWork = docsURL("cockroach-quit.html#how-it-works"); -export const clusterStore = docsURL("cockroach-start.html#store"); -export const clusterGlossary = docsURL("architecture/overview.html#glossary"); -export const clusterSettings = docsURL("cluster-settings.html"); -export const reviewOfCockroachTerminology = docsURL( - "ui-replication-dashboard.html#review-of-cockroachdb-terminology", -); -export const privileges = docsURL("authorization.html#privileges"); -export const showSessions = docsURL("show-sessions.html"); -export const sessionsTable = docsURL("ui-sessions-page.html"); +export let startFlags: string; +export let pauseJob: string; +export let cancelJob: string; +export let enableNodeMap: string; +export let configureReplicationZones: string; +export let transactionalPipelining: string; +export let adminUIAccess: string; +export let howAreCapacityMetricsCalculated: string; +export let howAreCapacityMetricsCalculatedOverview: string; +export let keyValuePairs: string; +export let writeIntents: string; +export let metaRanges: string; +export let databaseTable: string; +export let jobTable: string; +export let jobStatus: string; +export let jobsPause: string; +export let jobsResume: string; +export let jobsCancel: string; +export let statementsTable: string; +export let statementDiagnostics: string; +export let statementsSql: string; +export let statementsRetries: string; +export let transactionRetryErrorReference: string; +export let capacityMetrics: string; +export let nodeLivenessIssues: string; +export let howItWork: string; +export let clusterStore: string; +export let clusterGlossary: string; +export let clusterSettings: string; +export let reviewOfCockroachTerminology: string; +export let privileges: string; +export let showSessions: string; +export let sessionsTable: string; // Note that these explicitly don't use the current version, since we want to // link to the most up-to-date documentation available. export const upgradeCockroachVersion = @@ -97,16 +69,70 @@ export const enterpriseLicensing = // Not actually a docs URL. export const startTrial = "https://www.cockroachlabs.com/pricing/start-trial/"; -export const reduceStorageOfTimeSeriesDataOperationalFlags = docsURL( - "operational-faqs.html#can-i-reduce-or-disable-the-storage-of-time-series-data", -); - -export const performanceBestPracticesHotSpots = docsURL( - "performance-best-practices-overview.html#hot-spots", -); +export let reduceStorageOfTimeSeriesDataOperationalFlags: string; +export let performanceBestPracticesHotSpots: string; +export let uiDebugPages: string; +export let readsAndWritesOverviewPage: string; -export const uiDebugPages = docsURL("ui-debug-pages.html"); +export const recomputeDocsURLs = () => { + adminUILoginNoVersion = docsURLNoVersion( + "ui-overview.html#db-console-security", + ); + startFlags = docsURL("start-a-node.html#flags"); + pauseJob = docsURL("pause-job.html"); + cancelJob = docsURL("cancel-job.html"); + enableNodeMap = docsURL("enable-node-map.html"); + configureReplicationZones = docsURL("configure-replication-zones.html"); + transactionalPipelining = docsURL( + "architecture/transaction-layer.html#transaction-pipelining", + ); + adminUIAccess = docsURL("ui-overview.html#db-console-access"); + howAreCapacityMetricsCalculated = docsURL( + "ui-storage-dashboard.html#capacity-metrics", + ); + howAreCapacityMetricsCalculatedOverview = docsURL( + "ui-cluster-overview-page.html#capacity-metrics", + ); + keyValuePairs = docsURL("architecture/distribution-layer.html#table-data"); + writeIntents = docsURL("architecture/transaction-layer.html#write-intents"); + metaRanges = docsURL("architecture/distribution-layer.html#meta-ranges"); + databaseTable = docsURL("ui-databases-page.html"); + jobTable = docsURL("ui-jobs-page.html"); + jobStatus = docsURL("ui-jobs-page.html#job-status"); + jobsPause = docsURL("pause-job"); + jobsResume = docsURL("resume-job"); + jobsCancel = docsURL("cancel-job"); + statementsTable = docsURL("ui-statements-page.html"); + statementDiagnostics = docsURL("ui-statements-page.html#diagnostics"); + statementsSql = docsURL("ui-statements-page.html#sql-statement-fingerprints"); + statementsRetries = docsURL("transactions.html#transaction-retries"); + transactionRetryErrorReference = docsURL( + "transaction-retry-error-reference.html", + ); + capacityMetrics = docsURL("ui-cluster-overview-page.html#capacity-metrics"); + nodeLivenessIssues = docsURL( + "cluster-setup-troubleshooting.html#node-liveness-issues", + ); + howItWork = docsURL("cockroach-quit.html#how-it-works"); + clusterStore = docsURL("cockroach-start.html#store"); + clusterGlossary = docsURL("architecture/overview.html#glossary"); + clusterSettings = docsURL("cluster-settings.html"); + reviewOfCockroachTerminology = docsURL( + "ui-replication-dashboard.html#review-of-cockroachdb-terminology", + ); + privileges = docsURL("authorization.html#privileges"); + showSessions = docsURL("show-sessions.html"); + sessionsTable = docsURL("ui-sessions-page.html"); + reduceStorageOfTimeSeriesDataOperationalFlags = docsURL( + "operational-faqs.html#can-i-reduce-or-disable-the-storage-of-time-series-data", + ); + performanceBestPracticesHotSpots = docsURL( + "performance-best-practices-overview.html#hot-spots", + ); + uiDebugPages = docsURL("ui-debug-pages.html"); + readsAndWritesOverviewPage = docsURLNoVersion( + "architecture/reads-and-writes-overview.html#important-concepts", + ); +}; -export const readsAndWritesOverviewPage = docsURLNoVersion( - "architecture/reads-and-writes-overview.html#important-concepts", -); +recomputeDocsURLs();