Skip to content

Commit

Permalink
ui: dynamically read ui data on js load
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dhartunian committed Dec 21, 2022
1 parent 2bdff7b commit a903401
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 83 deletions.
7 changes: 6 additions & 1 deletion pkg/ui/workspaces/db-console/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<App history={history} store={store} />,
document.getElementById("react-layout"),
Expand Down
1 change: 1 addition & 0 deletions pkg/ui/workspaces/db-console/src/redux/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
34 changes: 28 additions & 6 deletions pkg/ui/workspaces/db-console/src/redux/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -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<any>) {
export function createAdminUIStore(
historyInst: History<any>,
dataFromServer: DataFromServer = emptyDataFromServer,
) {
const sagaMiddleware = createSagaMiddleware();
const routerReducer = connectRouter(historyInst);

Expand All @@ -70,6 +86,16 @@ export function createAdminUIStore(historyInst: History<any>) {
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
Expand All @@ -96,8 +122,4 @@ export function createAdminUIStore(historyInst: History<any>) {
return s;
}

const store = createAdminUIStore(history);

export type AppDispatch = ThunkDispatch<AdminUIState, unknown, Action>;

export { store };
178 changes: 102 additions & 76 deletions pkg/ui/workspaces/db-console/src/util/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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();

0 comments on commit a903401

Please sign in to comment.