diff --git a/public/env.js b/public/env.js index e0fbdece9c..39ba547d09 100644 --- a/public/env.js +++ b/public/env.js @@ -1,2 +1,14 @@ -// This file is intentionally left bank. -// Kiali server may re-generate this file with configuration variables. +// This file is setup for development mode (such as using "yarn start") +// Make sure it always matches definition in kiali/config/public_config.go +// !! WARNING !! KIALI SERVER WILL RE-GENERATE THIS FILE ON STARTUP +window.serverConfig = { + istioNamespace: 'istio-system', + istioLabels: { + appLabelName: 'app', + versionLabelName: 'version' + }, + prometheus: { + globalScrapeInterval: 15, + storageTsdbRetention: 21600 + } +}; diff --git a/src/actions/GraphDataThunkActions.ts b/src/actions/GraphDataThunkActions.ts index 2dd4734dc5..f4dee5799d 100644 --- a/src/actions/GraphDataThunkActions.ts +++ b/src/actions/GraphDataThunkActions.ts @@ -43,7 +43,7 @@ const GraphDataThunkActions = { graphType: graphType, injectServiceNodes: injectServiceNodes }; - if (namespaces.find(namespace => namespace.name === serverConfig().istioNamespace)) { + if (namespaces.find(namespace => namespace.name === serverConfig.istioNamespace)) { restParams['includeIstio'] = true; } diff --git a/src/actions/KialiAppAction.ts b/src/actions/KialiAppAction.ts index 91058cde83..9415978c46 100644 --- a/src/actions/KialiAppAction.ts +++ b/src/actions/KialiAppAction.ts @@ -8,7 +8,6 @@ import { LoginAction } from './LoginActions'; import { MessageCenterAction } from './MessageCenterActions'; import { NamespaceAction } from './NamespaceAction'; import { UserSettingsAction } from './UserSettingsActions'; -import { ServerConfigAction } from './ServerConfigActions'; import { JaegerAction } from './JaegerActions'; export type KialiAppAction = @@ -21,6 +20,5 @@ export type KialiAppAction = | LoginAction | MessageCenterAction | NamespaceAction - | ServerConfigAction | UserSettingsAction | JaegerAction; diff --git a/src/actions/LoginThunkActions.ts b/src/actions/LoginThunkActions.ts index 9f241dce15..882e92e884 100644 --- a/src/actions/LoginThunkActions.ts +++ b/src/actions/LoginThunkActions.ts @@ -1,12 +1,10 @@ import { ThunkDispatch } from 'redux-thunk'; -import { HTTP_CODES } from '../types/Common'; -import { KialiAppState, Token, ServerConfig } from '../store/Store'; +import { KialiAppState, Token } from '../store/Store'; import { KialiAppAction } from './KialiAppAction'; import HelpDropdownThunkActions from './HelpDropdownThunkActions'; import GrafanaThunkActions from './GrafanaThunkActions'; import { LoginActions } from './LoginActions'; import * as API from '../services/Api'; -import { ServerConfigActions } from './ServerConfigActions'; const ANONYMOUS: string = 'anonymous'; @@ -19,31 +17,12 @@ const completeLogin = ( timeout?: number ) => { const auth = `Bearer ${token['token']}`; - API.getServerConfig(auth).then( - response => { - // set the serverConfig before completing login so that it is available for - // anything needing it to render properly. - const serverConfig: ServerConfig = { - istioNamespace: response.data.istioNamespace, - istioLabels: response.data.istioLabels, - prometheus: response.data.prometheus - }; - dispatch(ServerConfigActions.setServerConfig(serverConfig)); + // complete the login process + dispatch(LoginActions.loginSuccess(token, username, timeout)); - // complete the login process - dispatch(LoginActions.loginSuccess(token, username, timeout)); - - // dispatch requests to be done now but not necessarily requiring immediate completion - dispatch(HelpDropdownThunkActions.refresh()); - dispatch(GrafanaThunkActions.getInfo(auth)); - }, - error => { - /** Logout user */ - if (error.response && error.response.status === HTTP_CODES.UNAUTHORIZED) { - dispatch(LoginActions.logoutSuccess()); - } - } - ); + // dispatch requests to be done now but not necessarily requiring immediate completion + dispatch(HelpDropdownThunkActions.refresh()); + dispatch(GrafanaThunkActions.getInfo(auth)); }; // performLogin performs only the authentication part of login. If successful diff --git a/src/actions/ServerConfigActions.ts b/src/actions/ServerConfigActions.ts deleted file mode 100644 index d48d4ca507..0000000000 --- a/src/actions/ServerConfigActions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ActionType, createStandardAction } from 'typesafe-actions'; -import { ServerConfig } from '../store/Store'; - -enum ServerConfigActionKeys { - SET_SERVER_CONFIG = 'SET_SERVER_CONFIG' -} - -// synchronous action creators -export const ServerConfigActions = { - setServerConfig: createStandardAction(ServerConfigActionKeys.SET_SERVER_CONFIG)() -}; - -export type ServerConfigAction = ActionType; diff --git a/src/actions/__tests__/ServerConfigAction.test.ts b/src/actions/__tests__/ServerConfigAction.test.ts deleted file mode 100644 index f1d3be1290..0000000000 --- a/src/actions/__tests__/ServerConfigAction.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ServerConfigActions } from '../ServerConfigActions'; -import { ServerConfig } from '../../store/Store'; -import { serverConfig } from '../../config/serverConfig'; -import { store } from '../../store/ConfigStore'; - -const config: ServerConfig = { - istioNamespace: 'istio-system', - istioLabels: { appLabelName: 'app', versionLabelName: 'version' }, - prometheus: { globalScrapeInterval: 15, storageTsdbRetention: 21600 } -}; - -describe('ServerConfigActions', () => { - it('Set ServerConfig action success', () => { - store.dispatch(ServerConfigActions.setServerConfig(config)); - expect(serverConfig().istioNamespace).toEqual(config.istioNamespace); - expect(serverConfig().istioLabels).toEqual(config.istioLabels); - expect(serverConfig().prometheus.globalScrapeInterval).toEqual(config.prometheus.globalScrapeInterval); - expect(serverConfig().prometheus.storageTsdbRetention).toEqual(config.prometheus.storageTsdbRetention); - }); -}); diff --git a/src/app/History.ts b/src/app/History.ts index d145c64422..0d900b43a3 100644 --- a/src/app/History.ts +++ b/src/app/History.ts @@ -1,13 +1,13 @@ import { createBrowserHistory } from 'history'; import createMemoryHistory from 'history/createMemoryHistory'; +import { serverConfig, toValidDuration } from '../config/serverConfig'; -const webRoot = (window as any).WEB_ROOT ? (window as any).WEB_ROOT : undefined; -const baseName = webRoot && webRoot !== '/' ? webRoot + '/console' : '/console'; +const baseName = serverConfig.webRoot && serverConfig.webRoot !== '/' ? serverConfig.webRoot + '/console' : '/console'; const history = process.env.TEST_RUNNER ? createMemoryHistory() : createBrowserHistory({ basename: baseName }); export default history; -export enum URLParams { +export enum URLParam { AGGREGATOR = 'aggregator', BY_LABELS = 'bylbl', DIRECTION = 'direction', @@ -28,7 +28,7 @@ export enum URLParams { } export interface URLParamValue { - name: URLParams; + name: URLParam; value: any; } @@ -38,18 +38,31 @@ export enum ParamAction { } export namespace HistoryManager { - export const setParam = (name: URLParams, value: string) => { + export const setParam = (name: URLParam, value: string) => { const urlParams = new URLSearchParams(history.location.search); urlParams.set(name, value); history.replace(history.location.pathname + '?' + urlParams.toString()); }; - export const getParam = (name: URLParams): string | null => { - const urlParams = new URLSearchParams(history.location.search); - return urlParams.get(name); + export const getParam = (name: URLParam, urlParams?: URLSearchParams): string | undefined => { + if (!urlParams) { + urlParams = new URLSearchParams(history.location.search); + } + const p = urlParams.get(name); + return p !== null ? p : undefined; }; - export const deleteParam = (name: URLParams, historyReplace?: boolean) => { + export const getNumericParam = (name: URLParam, urlParams?: URLSearchParams): number | undefined => { + const p = getParam(name, urlParams); + return p !== undefined ? Number(p) : undefined; + }; + + export const getBooleanParam = (name: URLParam, urlParams?: URLSearchParams): boolean | undefined => { + const p = getParam(name, urlParams); + return p !== undefined ? p === 'true' : undefined; + }; + + export const deleteParam = (name: URLParam, historyReplace?: boolean) => { const urlParams = new URLSearchParams(history.location.search); urlParams.delete(name); if (historyReplace) { @@ -82,4 +95,12 @@ export namespace HistoryManager { history.push(history.location.pathname + '?' + urlParams.toString()); } }; + + export const getDuration = (urlParams?: URLSearchParams): number | undefined => { + const duration = getParam(URLParam.DURATION, urlParams); + if (duration) { + return toValidDuration(Number(duration)); + } + return undefined; + }; } diff --git a/src/components/CytoscapeGraph/CytoscapeToolbar.tsx b/src/components/CytoscapeGraph/CytoscapeToolbar.tsx index 5e6ab95765..4e3589b97b 100644 --- a/src/components/CytoscapeGraph/CytoscapeToolbar.tsx +++ b/src/components/CytoscapeGraph/CytoscapeToolbar.tsx @@ -14,8 +14,7 @@ import { CoseGraph } from './graphs/CoseGraph'; import { DagreGraph } from './graphs/DagreGraph'; import { KialiAppAction } from '../../actions/KialiAppAction'; import { GraphActions } from '../../actions/GraphActions'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../ListPage/ListPagesHelper'; +import { HistoryManager, URLParam } from '../../app/History'; import * as LayoutDictionary from './graphs/LayoutDictionary'; import { GraphFilterActions } from '../../actions/GraphFilterActions'; @@ -46,19 +45,19 @@ export class CytoscapeToolbar extends React.PureComponent constructor(props: CytoscapeToolbarProps) { super(props); // Let URL override current redux state at construction time. Update URL with unset params. - const urlLayout = ListPagesHelper.getSingleQueryParam(URLParams.GRAPH_LAYOUT); + const urlLayout = HistoryManager.getParam(URLParam.GRAPH_LAYOUT); if (urlLayout) { if (urlLayout !== props.layout.name) { props.setLayout(LayoutDictionary.getLayoutByName(urlLayout)); } } else { - HistoryManager.setParam(URLParams.GRAPH_LAYOUT, props.layout.name); + HistoryManager.setParam(URLParam.GRAPH_LAYOUT, props.layout.name); } } componentDidUpdate() { // ensure redux state and URL are aligned - HistoryManager.setParam(URLParams.GRAPH_LAYOUT, this.props.layout.name); + HistoryManager.setParam(URLParam.GRAPH_LAYOUT, this.props.layout.name); } render() { diff --git a/src/components/GraphFilter/GraphFilter.tsx b/src/components/GraphFilter/GraphFilter.tsx index 08709d0f60..1f4e883c74 100644 --- a/src/components/GraphFilter/GraphFilter.tsx +++ b/src/components/GraphFilter/GraphFilter.tsx @@ -16,8 +16,7 @@ import { EdgeLabelMode } from '../../types/GraphFilter'; import GraphFindContainer from './GraphFind'; import GraphRefreshContainer from './GraphRefresh'; import GraphSettingsContainer from './GraphSettings'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; +import history, { HistoryManager, URLParam } from '../../app/History'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; import Namespace, { namespacesToString, namespacesFromString } from '../../types/Namespace'; import { NamespaceActions } from '../../actions/NamespaceAction'; @@ -75,32 +74,33 @@ export class GraphFilter extends React.PureComponent { constructor(props: GraphFilterProps) { super(props); // Let URL override current redux state at construction time. Update URL with unset params. - const urlEdgeLabelMode = ListPagesHelper.getSingleQueryParam(URLParams.GRAPH_EDGES) as EdgeLabelMode; + const urlParams = new URLSearchParams(history.location.search); + const urlEdgeLabelMode = HistoryManager.getParam(URLParam.GRAPH_EDGES, urlParams) as EdgeLabelMode; if (urlEdgeLabelMode) { if (urlEdgeLabelMode !== props.edgeLabelMode) { props.setEdgeLabelMode(urlEdgeLabelMode); } } else { - HistoryManager.setParam(URLParams.GRAPH_EDGES, String(this.props.edgeLabelMode)); + HistoryManager.setParam(URLParam.GRAPH_EDGES, String(this.props.edgeLabelMode)); } - const urlGraphType = ListPagesHelper.getSingleQueryParam(URLParams.GRAPH_TYPE) as GraphType; + const urlGraphType = HistoryManager.getParam(URLParam.GRAPH_TYPE, urlParams) as GraphType; if (urlGraphType) { if (urlGraphType !== props.graphType) { props.setGraphType(urlGraphType); } } else { - HistoryManager.setParam(URLParams.GRAPH_TYPE, String(this.props.graphType)); + HistoryManager.setParam(URLParam.GRAPH_TYPE, String(this.props.graphType)); } - const urlNamespaces = ListPagesHelper.getSingleQueryParam(URLParams.NAMESPACES); + const urlNamespaces = HistoryManager.getParam(URLParam.NAMESPACES, urlParams); if (urlNamespaces) { if (urlNamespaces !== namespacesToString(props.activeNamespaces)) { props.setActiveNamespaces(namespacesFromString(urlNamespaces)); } } else { const activeNamespacesString = namespacesToString(props.activeNamespaces); - HistoryManager.setParam(URLParams.NAMESPACES, activeNamespacesString); + HistoryManager.setParam(URLParam.NAMESPACES, activeNamespacesString); } } @@ -108,12 +108,12 @@ export class GraphFilter extends React.PureComponent { // ensure redux state and URL are aligned const activeNamespacesString = namespacesToString(this.props.activeNamespaces); if (this.props.activeNamespaces.length === 0) { - HistoryManager.deleteParam(URLParams.NAMESPACES, true); + HistoryManager.deleteParam(URLParam.NAMESPACES, true); } else { - HistoryManager.setParam(URLParams.NAMESPACES, activeNamespacesString); + HistoryManager.setParam(URLParam.NAMESPACES, activeNamespacesString); } - HistoryManager.setParam(URLParams.GRAPH_EDGES, String(this.props.edgeLabelMode)); - HistoryManager.setParam(URLParams.GRAPH_TYPE, String(this.props.graphType)); + HistoryManager.setParam(URLParam.GRAPH_EDGES, String(this.props.edgeLabelMode)); + HistoryManager.setParam(URLParam.GRAPH_TYPE, String(this.props.graphType)); } handleRefresh = () => { diff --git a/src/components/GraphFilter/GraphRefresh.tsx b/src/components/GraphFilter/GraphRefresh.tsx index 3493d3b3af..27d62cef37 100644 --- a/src/components/GraphFilter/GraphRefresh.tsx +++ b/src/components/GraphFilter/GraphRefresh.tsx @@ -5,18 +5,17 @@ import { connect } from 'react-redux'; import { ThunkDispatch } from 'redux-thunk'; import { bindActionCreators } from 'redux'; -import { KialiAppState, ServerConfig } from '../../store/Store'; -import { durationSelector, refreshIntervalSelector, serverConfigSelector } from '../../store/Selectors'; +import { KialiAppState } from '../../store/Store'; +import { durationSelector, refreshIntervalSelector } from '../../store/Selectors'; import { KialiAppAction } from '../../actions/KialiAppAction'; import { UserSettingsActions } from '../../actions/UserSettingsActions'; import { DurationInSeconds, PollIntervalInMs } from '../../types/Common'; import { config } from '../../config/config'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; +import { HistoryManager, URLParam } from '../../app/History'; import ToolbarDropdown from '../ToolbarDropdown/ToolbarDropdown'; -import { getValidDurations, getValidDuration } from '../../config/serverConfig'; +import { serverConfig } from '../../config/serverConfig'; // // GraphRefresh actually handles the Duration dropdown, the RefreshInterval dropdown and the Refresh button. @@ -25,7 +24,6 @@ import { getValidDurations, getValidDuration } from '../../config/serverConfig'; type ReduxProps = { duration: DurationInSeconds; refreshInterval: PollIntervalInMs; - serverConfig: ServerConfig; setDuration: (duration: DurationInSeconds) => void; setRefreshInterval: (refreshInterval: PollIntervalInMs) => void; @@ -38,7 +36,6 @@ type GraphRefreshProps = ReduxProps & { }; export class GraphRefresh extends React.PureComponent { - static readonly DURATION_LIST = config.toolbar.intervalDuration; static readonly POLL_INTERVAL_LIST = config.toolbar.pollInterval; static readonly durationLabelStyle = style({ @@ -54,39 +51,35 @@ export class GraphRefresh extends React.PureComponent { super(props); // Let URL override current redux state at construction time - const urlDuration = ListPagesHelper.getSingleIntQueryParam(URLParams.DURATION); - const urlPollInterval = ListPagesHelper.getSingleIntQueryParam(URLParams.POLL_INTERVAL); + const urlDuration = HistoryManager.getDuration(); + const urlPollInterval = HistoryManager.getNumericParam(URLParam.POLL_INTERVAL); if (urlDuration !== undefined && urlDuration !== props.duration) { props.setDuration(urlDuration); } if (urlPollInterval !== undefined && urlPollInterval !== props.refreshInterval) { props.setRefreshInterval(urlPollInterval); } - HistoryManager.setParam(URLParams.DURATION, String(this.props.duration)); - HistoryManager.setParam(URLParams.POLL_INTERVAL, String(this.props.refreshInterval)); + HistoryManager.setParam(URLParam.DURATION, String(this.props.duration)); + HistoryManager.setParam(URLParam.POLL_INTERVAL, String(this.props.refreshInterval)); } componentDidUpdate() { // ensure redux state and URL are aligned - HistoryManager.setParam(URLParams.DURATION, String(this.props.duration)); - HistoryManager.setParam(URLParams.POLL_INTERVAL, String(this.props.refreshInterval)); + HistoryManager.setParam(URLParam.DURATION, String(this.props.duration)); + HistoryManager.setParam(URLParam.POLL_INTERVAL, String(this.props.refreshInterval)); } render() { - const retention = this.props.serverConfig.prometheus.storageTsdbRetention; - const validDurations = getValidDurations(GraphRefresh.DURATION_LIST, retention); - const validDuration = getValidDuration(validDurations, this.props.duration); - return ( <> this.props.setDuration(Number(key))} + value={this.props.duration} + label={String(serverConfig.durations[this.props.duration])} + options={serverConfig.durations} /> { ); } - - private handleDurationChange = (duration: string) => { - this.props.setDuration(Number(duration)); - }; } const mapStateToProps = (state: KialiAppState) => ({ duration: durationSelector(state), - refreshInterval: refreshIntervalSelector(state), - serverConfig: serverConfigSelector(state) + refreshInterval: refreshIntervalSelector(state) }); const mapDispatchToProps = (dispatch: ThunkDispatch) => { diff --git a/src/components/GraphFilter/GraphSettings.tsx b/src/components/GraphFilter/GraphSettings.tsx index 30fead91a1..8a1d3ffe97 100644 --- a/src/components/GraphFilter/GraphSettings.tsx +++ b/src/components/GraphFilter/GraphSettings.tsx @@ -4,8 +4,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { ThunkDispatch } from 'redux-thunk'; import { bindActionCreators } from 'redux'; -import { HistoryManager, URLParams } from '../../app/History'; -import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; +import { HistoryManager, URLParam } from '../../app/History'; import { GraphFilterState, KialiAppState } from '../../store/Store'; import { KialiAppAction } from '../../actions/KialiAppAction'; import { GraphFilterActions } from '../../actions/GraphFilterActions'; @@ -44,19 +43,19 @@ class GraphSettings extends React.PureComponent { super(props); // Let URL override current redux state at construction time. Update URL with unset params. - const urlInjectServiceNodes = ListPagesHelper.getSingleBooleanQueryParam(URLParams.GRAPH_SERVICE_NODES); + const urlInjectServiceNodes = HistoryManager.getBooleanParam(URLParam.GRAPH_SERVICE_NODES); if (urlInjectServiceNodes !== undefined) { if (urlInjectServiceNodes !== props.showServiceNodes) { props.toggleServiceNodes(); } } else { - HistoryManager.setParam(URLParams.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); + HistoryManager.setParam(URLParam.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); } } componentDidUpdate(prevProps: GraphSettingsProps) { // ensure redux state and URL are aligned - HistoryManager.setParam(URLParams.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); + HistoryManager.setParam(URLParam.GRAPH_SERVICE_NODES, String(this.props.showServiceNodes)); } render() { diff --git a/src/components/IstioWizards/IstioWizard.tsx b/src/components/IstioWizards/IstioWizard.tsx index 094c43d260..b5c212dc96 100644 --- a/src/components/IstioWizards/IstioWizard.tsx +++ b/src/components/IstioWizards/IstioWizard.tsx @@ -137,7 +137,7 @@ class IstioWizard extends React.Component { host: this.props.serviceName, subsets: this.props.workloads.map(workload => { // Using version - const versionLabelName = serverConfig().istioLabels.versionLabelName; + const versionLabelName = serverConfig.istioLabels.versionLabelName; const versionValue = workload.labels![versionLabelName]; const labels: { [key: string]: string } = {}; labels[versionLabelName] = versionValue; diff --git a/src/components/IstioWizards/IstioWizardDropdown.tsx b/src/components/IstioWizards/IstioWizardDropdown.tsx index 14bf81abf1..0d88b4a0ff 100644 --- a/src/components/IstioWizards/IstioWizardDropdown.tsx +++ b/src/components/IstioWizards/IstioWizardDropdown.tsx @@ -132,8 +132,8 @@ class IstioWizardDropdown extends React.Component { namespace={this.props.namespace} serviceName={this.props.serviceName} workloads={this.props.workloads.filter(workload => { - const appLabelName = serverConfig().istioLabels.versionLabelName; - const versionLabelName = serverConfig().istioLabels.versionLabelName; + const appLabelName = serverConfig.istioLabels.versionLabelName; + const versionLabelName = serverConfig.istioLabels.versionLabelName; return workload.labels && workload.labels[appLabelName] && workload.labels[versionLabelName]; })} onClose={this.onClose} diff --git a/src/components/ListPage/ListComponent.tsx b/src/components/ListPage/ListComponent.tsx index 8336615e66..3b045dab22 100644 --- a/src/components/ListPage/ListComponent.tsx +++ b/src/components/ListPage/ListComponent.tsx @@ -4,7 +4,7 @@ import { ListPagesHelper } from './ListPagesHelper'; import { SortField } from '../../types/SortFilters'; import { Pagination } from '../../types/Pagination'; import * as API from '../../services/Api'; -import { HistoryManager, URLParams } from '../../app/History'; +import { HistoryManager, URLParam } from '../../app/History'; export namespace ListComponent { export interface Props { @@ -33,7 +33,7 @@ export namespace ListComponent { onFilterChange = () => { // Resetting pagination when filters change - HistoryManager.deleteParam(URLParams.PAGE); + HistoryManager.deleteParam(URLParam.PAGE); this.updateListItems(true); }; @@ -58,7 +58,7 @@ export namespace ListComponent { } }; }); - HistoryManager.setParam(URLParams.PAGE, String(page)); + HistoryManager.setParam(URLParam.PAGE, String(page)); }; perPageSelect = (perPage: number) => { @@ -73,8 +73,8 @@ export namespace ListComponent { }; }); HistoryManager.setParams([ - { name: URLParams.PAGE, value: '1' }, - { name: URLParams.PER_PAGE, value: String(perPage) } + { name: URLParam.PAGE, value: '1' }, + { name: URLParam.PER_PAGE, value: String(perPage) } ]); }; @@ -84,7 +84,7 @@ export namespace ListComponent { currentSortField: sortField, listItems: sorted }); - HistoryManager.setParam(URLParams.SORT, sortField.param); + HistoryManager.setParam(URLParam.SORT, sortField.param); }); }; @@ -94,7 +94,7 @@ export namespace ListComponent { isSortAscending: !this.state.isSortAscending, listItems: sorted }); - HistoryManager.setParam(URLParams.DIRECTION, this.state.isSortAscending ? 'asc' : 'desc'); + HistoryManager.setParam(URLParam.DIRECTION, this.state.isSortAscending ? 'asc' : 'desc'); }); }; } diff --git a/src/components/ListPage/ListPagesHelper.ts b/src/components/ListPage/ListPagesHelper.ts index d5fc30c956..57cd653cd1 100644 --- a/src/components/ListPage/ListPagesHelper.ts +++ b/src/components/ListPage/ListPagesHelper.ts @@ -1,4 +1,4 @@ -import history, { URLParams } from '../../app/History'; +import history, { URLParam, HistoryManager } from '../../app/History'; import { config } from '../../config'; import { ActiveFilter, FilterType } from '../../types/Filters'; import { Pagination } from '../../types/Pagination'; @@ -14,32 +14,6 @@ export namespace ListPagesHelper { MessageCenter.add(error); }; - export const getQueryParam = (queryName: string): string[] | undefined => { - const urlParams = new URLSearchParams(history.location.search); - const values = urlParams.getAll(queryName); - - if (values.length === 0) { - return undefined; - } - - return values; - }; - - export const getSingleQueryParam = (queryName: string): string | undefined => { - const p = getQueryParam(queryName); - return p === undefined ? undefined : p[0]; - }; - - export const getSingleBooleanQueryParam = (queryName: string): boolean | undefined => { - const p = getQueryParam(queryName); - return p === undefined ? undefined : p[0] === 'true'; - }; - - export const getSingleIntQueryParam = (queryName: string): number | undefined => { - const p = getQueryParam(queryName); - return p === undefined ? undefined : Number(p[0]); - }; - export const getFiltersFromURL = (filterTypes: FilterType[]): ActiveFilter[] => { const urlParams = new URLSearchParams(history.location.search); const activeFilters: ActiveFilter[] = []; @@ -69,7 +43,7 @@ export namespace ListPagesHelper { urlParams.append(filterType.id, activeFilter.value); }); // Resetting pagination when filters change - urlParams.delete(URLParams.PAGE); + urlParams.delete(URLParam.PAGE); history.push(history.location.pathname + '?' + urlParams.toString()); return cleanFilters; }; @@ -106,27 +80,24 @@ export namespace ListPagesHelper { }; export const currentPagination = (): Pagination => { + const urlParams = new URLSearchParams(history.location.search); return { - page: getSingleIntQueryParam(URLParams.PAGE) || 1, - perPage: getSingleIntQueryParam(URLParams.PER_PAGE) || perPageOptions[1], + page: HistoryManager.getNumericParam(URLParam.PAGE, urlParams) || 1, + perPage: HistoryManager.getNumericParam(URLParam.PER_PAGE, urlParams) || perPageOptions[1], perPageOptions: perPageOptions }; }; export const isCurrentSortAscending = (): boolean => { - return (getSingleQueryParam(URLParams.DIRECTION) || 'asc') === 'asc'; - }; - - export const currentSortFieldId = (): string | undefined => { - return getSingleQueryParam(URLParams.SORT); + return (HistoryManager.getParam(URLParam.DIRECTION) || 'asc') === 'asc'; }; export const currentDuration = (): number => { - return getSingleIntQueryParam(URLParams.DURATION) || defaultDuration; + return HistoryManager.getDuration() || defaultDuration; }; export const currentPollInterval = (): number => { - const pi = getSingleIntQueryParam(URLParams.POLL_INTERVAL); + const pi = HistoryManager.getNumericParam(URLParam.POLL_INTERVAL); if (pi === undefined) { return defaultPollInterval; } @@ -134,7 +105,7 @@ export namespace ListPagesHelper { }; export const currentSortField = (sortFields: SortField[]): SortField => { - const queriedSortedField = getQueryParam(URLParams.SORT) || [sortFields[0].param]; + const queriedSortedField = HistoryManager.getParam(URLParam.SORT) || [sortFields[0].param]; return ( sortFields.find(sortField => { return sortField.param === queriedSortedField[0]; diff --git a/src/components/Metrics/CustomMetrics.tsx b/src/components/Metrics/CustomMetrics.tsx index 2a60352d6c..2eb0ff5149 100644 --- a/src/components/Metrics/CustomMetrics.tsx +++ b/src/components/Metrics/CustomMetrics.tsx @@ -16,7 +16,7 @@ import { Dashboard } from './Dashboard'; import MetricsHelper from './Helper'; import { MetricsSettingsDropdown, MetricsSettings } from '../MetricsOptions/MetricsSettings'; import MetricsRawAggregation from '../MetricsOptions/MetricsRawAggregation'; -import MetricsDurationContainer from '../MetricsOptions/MetricsDuration'; +import MetricsDuration from '../MetricsOptions/MetricsDuration'; type MetricsState = { dashboard?: M.MonitoringDashboard; @@ -129,7 +129,7 @@ class CustomMetrics extends React.Component { - + diff --git a/src/components/Metrics/Helper.ts b/src/components/Metrics/Helper.ts index bff20d8c84..de6b8d286b 100644 --- a/src/components/Metrics/Helper.ts +++ b/src/components/Metrics/Helper.ts @@ -11,7 +11,7 @@ import { } from '../../types/Metrics'; import { BaseMetricsOptions } from '../../types/MetricsOptions'; import { MetricsSettingsDropdown, MetricsSettings } from '../MetricsOptions/MetricsSettings'; -import { MetricsDuration } from '../MetricsOptions/MetricsDuration'; +import MetricsDuration from '../MetricsOptions/MetricsDuration'; import { DurationInSeconds } from '../../types/Common'; import { computePrometheusRateParams } from '../../services/Prometheus'; diff --git a/src/components/Metrics/IstioMetrics.tsx b/src/components/Metrics/IstioMetrics.tsx index e912150b90..076388ec5c 100644 --- a/src/components/Metrics/IstioMetrics.tsx +++ b/src/components/Metrics/IstioMetrics.tsx @@ -15,7 +15,7 @@ import { Dashboard } from './Dashboard'; import MetricsHelper from './Helper'; import { MetricsSettings, MetricsSettingsDropdown } from '../MetricsOptions/MetricsSettings'; import MetricsReporter from '../MetricsOptions/MetricsReporter'; -import MetricsDurationContainer from '../MetricsOptions/MetricsDuration'; +import MetricsDuration from '../MetricsOptions/MetricsDuration'; type MetricsState = { dashboard?: M.MonitoringDashboard; @@ -176,7 +176,7 @@ class IstioMetrics extends React.Component { )} - + diff --git a/src/components/Metrics/__tests__/CustomMetrics.test.tsx b/src/components/Metrics/__tests__/CustomMetrics.test.tsx index 7301f2a5ec..75ab3317b4 100644 --- a/src/components/Metrics/__tests__/CustomMetrics.test.tsx +++ b/src/components/Metrics/__tests__/CustomMetrics.test.tsx @@ -7,8 +7,6 @@ import CustomMetrics from '../CustomMetrics'; import * as API from '../../../services/Api'; import { MonitoringDashboard } from '../../../types/Metrics'; import { store } from '../../../store/ConfigStore'; -import { ServerConfig } from '../../../store/Store'; -import { ServerConfigActions } from '../../../actions/ServerConfigActions'; window['SVGPathElement'] = a => a; let mounted: ReactWrapper | null; @@ -34,15 +32,6 @@ const mockCustomDashboard = (dashboard: MonitoringDashboard): Promise => { return mockAPIToPromise('getCustomDashboard', dashboard); }; -const mockServerConfig = () => { - const config: ServerConfig = { - istioNamespace: 'istio-system', - istioLabels: { appLabelName: 'app', versionLabelName: 'version' }, - prometheus: { storageTsdbRetention: 31 * 24 * 60 * 60 } - }; - store.dispatch(ServerConfigActions.setServerConfig(config)); -}; - describe('Custom metrics', () => { beforeEach(() => { mounted = null; @@ -54,7 +43,6 @@ describe('Custom metrics', () => { }); it('mounts and loads empty metrics', done => { - mockServerConfig(); mockCustomDashboard({ title: 'foo', aggregations: [], charts: [] }) .then(() => { mounted!.update(); diff --git a/src/components/Metrics/__tests__/IstioMetrics.test.tsx b/src/components/Metrics/__tests__/IstioMetrics.test.tsx index 7259c93a44..a19e16fc2a 100644 --- a/src/components/Metrics/__tests__/IstioMetrics.test.tsx +++ b/src/components/Metrics/__tests__/IstioMetrics.test.tsx @@ -7,8 +7,6 @@ import IstioMetrics from '../IstioMetrics'; import * as API from '../../../services/Api'; import { MetricsObjectTypes, MonitoringDashboard, Chart } from '../../../types/Metrics'; import { store } from '../../../store/ConfigStore'; -import { ServerConfig } from '../../../store/Store'; -import { ServerConfigActions } from '../../../actions/ServerConfigActions'; window['SVGPathElement'] = a => a; let mounted: ReactWrapper | null; @@ -42,15 +40,6 @@ const mockGrafanaInfo = (info: any): Promise => { return mockAPIToPromise('getGrafanaInfo', info); }; -const mockServerConfig = () => { - const config: ServerConfig = { - istioNamespace: 'istio-system', - istioLabels: { appLabelName: 'app', versionLabelName: 'version' }, - prometheus: { storageTsdbRetention: 31 * 24 * 60 * 60 } - }; - store.dispatch(ServerConfigActions.setServerConfig(config)); -}; - const createMetricChart = (name: string): Chart => { return { name: name, @@ -125,7 +114,6 @@ describe('Metrics for a service', () => { }); it('renders initial layout', () => { - mockServerConfig(); mockGrafanaInfo({}); const wrapper = shallow( @@ -157,7 +145,6 @@ describe('Metrics for a service', () => { it('mounts and loads empty metrics', done => { const allMocksDone = [ - mockServerConfig(), mockServiceDashboard({ title: 'foo', aggregations: [], charts: [] }) .then(() => { mounted!.update(); @@ -196,7 +183,6 @@ describe('Metrics for a service', () => { it('mounts and loads full metrics', done => { const allMocksDone = [ - mockServerConfig(), mockServiceDashboard({ title: 'foo', aggregations: [], @@ -281,7 +267,6 @@ describe('Inbound Metrics for a workload', () => { it('mounts and loads empty metrics', done => { const allMocksDone = [ - mockServerConfig(), mockWorkloadDashboard({ title: 'foo', aggregations: [], charts: [] }) .then(() => { mounted!.update(); @@ -320,7 +305,6 @@ describe('Inbound Metrics for a workload', () => { it('mounts and loads full metrics', done => { const allMocksDone = [ - mockServerConfig(), mockWorkloadDashboard({ title: 'foo', aggregations: [], diff --git a/src/components/MetricsOptions/MetricsDuration.tsx b/src/components/MetricsOptions/MetricsDuration.tsx index b7620a7133..1f7bbc1dfc 100644 --- a/src/components/MetricsOptions/MetricsDuration.tsx +++ b/src/components/MetricsOptions/MetricsDuration.tsx @@ -1,24 +1,15 @@ import * as React from 'react'; -import history, { URLParams, HistoryManager } from '../../app/History'; -import { config } from '../../config'; +import { URLParam, HistoryManager } from '../../app/History'; import { DurationInSeconds } from '../../types/Common'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; -import { KialiAppState, ServerConfig } from '../../store/Store'; -import { serverConfigSelector } from '../../store/Selectors'; -import { connect } from 'react-redux'; -import { getValidDurations, getValidDuration } from '../../config/serverConfig'; +import { serverConfig } from '../../config/serverConfig'; -type ReduxProps = { - serverConfig: ServerConfig; -}; - -type Props = ReduxProps & { +type Props = { onChanged: (duration: DurationInSeconds) => void; }; -export class MetricsDuration extends React.Component { - static Durations = config.toolbar.intervalDuration; +export default class MetricsDuration extends React.Component { // Default to 10 minutes. Showing timeseries to only 1 minute doesn't make so much sense. static DefaultDuration = 600; @@ -26,14 +17,13 @@ export class MetricsDuration extends React.Component { private duration: DurationInSeconds; static initialDuration = (): DurationInSeconds => { - const urlParams = new URLSearchParams(history.location.search); - let d = urlParams.get(URLParams.DURATION); - if (d !== null) { - sessionStorage.setItem(URLParams.DURATION, d); - return Number(d); + const urlDuration = HistoryManager.getDuration(); + if (urlDuration !== undefined) { + sessionStorage.setItem(URLParam.DURATION, String(urlDuration)); + return urlDuration; } - d = sessionStorage.getItem(URLParams.DURATION); - return d !== null ? Number(d) : MetricsDuration.DefaultDuration; + const storageDuration = sessionStorage.getItem(URLParam.DURATION); + return storageDuration !== null ? Number(storageDuration) : MetricsDuration.DefaultDuration; }; constructor(props: Props) { @@ -48,25 +38,21 @@ export class MetricsDuration extends React.Component { } onDurationChanged = (key: string) => { - sessionStorage.setItem(URLParams.DURATION, key); - HistoryManager.setParam(URLParams.DURATION, key); + sessionStorage.setItem(URLParam.DURATION, key); + HistoryManager.setParam(URLParam.DURATION, key); }; render() { this.processUrlParams(); - const retention = this.props.serverConfig.prometheus.storageTsdbRetention; - const validDurations = getValidDurations(MetricsDuration.Durations, retention); - const validDuration = getValidDuration(validDurations, this.duration); - return ( ); } @@ -77,14 +63,3 @@ export class MetricsDuration extends React.Component { this.duration = duration; } } - -const mapStateToProps = (state: KialiAppState) => ({ - serverConfig: serverConfigSelector(state) -}); - -const MetricsDurationContainer = connect( - mapStateToProps, - null -)(MetricsDuration); - -export default MetricsDurationContainer; diff --git a/src/components/MetricsOptions/MetricsRawAggregation.tsx b/src/components/MetricsOptions/MetricsRawAggregation.tsx index d80a794be8..98bb38ddea 100644 --- a/src/components/MetricsOptions/MetricsRawAggregation.tsx +++ b/src/components/MetricsOptions/MetricsRawAggregation.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import history, { URLParams, HistoryManager } from '../../app/History'; +import { URLParam, HistoryManager } from '../../app/History'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; import { Aggregator } from '../../types/MetricsOptions'; @@ -22,9 +22,8 @@ export default class MetricsRawAggregation extends React.Component { private aggregator: Aggregator; static initialAggregator = (): Aggregator => { - const urlParams = new URLSearchParams(history.location.search); - const opParam = urlParams.get(URLParams.AGGREGATOR); - if (opParam != null) { + const opParam = HistoryManager.getParam(URLParam.AGGREGATOR); + if (opParam !== undefined) { return opParam as Aggregator; } return 'sum'; @@ -42,7 +41,7 @@ export default class MetricsRawAggregation extends React.Component { } onAggregatorChanged = (aggregator: string) => { - HistoryManager.setParam(URLParams.AGGREGATOR, aggregator); + HistoryManager.setParam(URLParam.AGGREGATOR, aggregator); }; render() { diff --git a/src/components/MetricsOptions/MetricsReporter.tsx b/src/components/MetricsOptions/MetricsReporter.tsx index e039d2f859..12bc1ae11d 100644 --- a/src/components/MetricsOptions/MetricsReporter.tsx +++ b/src/components/MetricsOptions/MetricsReporter.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import history, { URLParams, HistoryManager } from '../../app/History'; +import { URLParam, HistoryManager } from '../../app/History'; import { ToolbarDropdown } from '../ToolbarDropdown/ToolbarDropdown'; import { Reporter, Direction } from '../../types/MetricsOptions'; @@ -19,9 +19,8 @@ export default class MetricsReporter extends React.Component { private reporter: Reporter; static initialReporter = (direction: Direction): Reporter => { - const urlParams = new URLSearchParams(history.location.search); - const reporterParam = urlParams.get(URLParams.REPORTER); - if (reporterParam != null) { + const reporterParam = HistoryManager.getParam(URLParam.REPORTER); + if (reporterParam !== undefined) { return reporterParam as Reporter; } return direction === 'inbound' ? 'destination' : 'source'; @@ -39,7 +38,7 @@ export default class MetricsReporter extends React.Component { } onReporterChanged = (reporter: string) => { - HistoryManager.setParam(URLParams.REPORTER, reporter); + HistoryManager.setParam(URLParam.REPORTER, reporter); }; render() { diff --git a/src/components/MetricsOptions/MetricsSettings.tsx b/src/components/MetricsOptions/MetricsSettings.tsx index cc113f5771..9083b6a397 100644 --- a/src/components/MetricsOptions/MetricsSettings.tsx +++ b/src/components/MetricsOptions/MetricsSettings.tsx @@ -3,7 +3,7 @@ import { Button, Icon, OverlayTrigger, Popover } from 'patternfly-react'; import { style } from 'typestyle'; import isEqual from 'lodash/fp/isEqual'; -import history, { URLParams } from '../../app/History'; +import history, { URLParam } from '../../app/History'; import { LabelDisplayName, AllLabelsValues } from '../../types/Metrics'; export type Quantiles = '0.5' | '0.95' | '0.99' | '0.999'; @@ -32,11 +32,11 @@ export class MetricsSettingsDropdown extends React.Component { showQuantiles: ['0.5', '0.95', '0.99'], activeLabels: [] }; - const avg = urlParams.get(URLParams.SHOW_AVERAGE); + const avg = urlParams.get(URLParam.SHOW_AVERAGE); if (avg !== null) { settings.showAverage = avg === 'true'; } - const quantiles = urlParams.get(URLParams.QUANTILES); + const quantiles = urlParams.get(URLParam.QUANTILES); if (quantiles !== null) { if (quantiles.trim().length !== 0) { settings.showQuantiles = quantiles.split(' ').map(val => val.trim() as Quantiles); @@ -44,7 +44,7 @@ export class MetricsSettingsDropdown extends React.Component { settings.showQuantiles = []; } } - const byLabels = urlParams.getAll(URLParams.BY_LABELS); + const byLabels = urlParams.getAll(URLParam.BY_LABELS); if (byLabels.length !== 0) { settings.activeLabels = byLabels as LabelDisplayName[]; } @@ -68,14 +68,14 @@ export class MetricsSettingsDropdown extends React.Component { : this.settings.activeLabels.filter(g => label !== g); const urlParams = new URLSearchParams(history.location.search); - urlParams.delete(URLParams.BY_LABELS); - newLabels.forEach(lbl => urlParams.append(URLParams.BY_LABELS, lbl)); + urlParams.delete(URLParam.BY_LABELS); + newLabels.forEach(lbl => urlParams.append(URLParam.BY_LABELS, lbl)); history.replace(history.location.pathname + '?' + urlParams.toString()); }; onHistogramAverageChanged = (checked: boolean) => { const urlParams = new URLSearchParams(history.location.search); - urlParams.set(URLParams.SHOW_AVERAGE, String(checked)); + urlParams.set(URLParam.SHOW_AVERAGE, String(checked)); history.replace(history.location.pathname + '?' + urlParams.toString()); }; @@ -85,7 +85,7 @@ export class MetricsSettingsDropdown extends React.Component { : this.settings.showQuantiles.filter(q => quantile !== q); const urlParams = new URLSearchParams(history.location.search); - urlParams.set(URLParams.QUANTILES, newQuantiles.join(' ')); + urlParams.set(URLParam.QUANTILES, newQuantiles.join(' ')); history.replace(history.location.pathname + '?' + urlParams.toString()); }; diff --git a/src/components/NamespaceDropdown.tsx b/src/components/NamespaceDropdown.tsx index 4129727807..87d312557f 100644 --- a/src/components/NamespaceDropdown.tsx +++ b/src/components/NamespaceDropdown.tsx @@ -11,7 +11,7 @@ import { NamespaceActions } from '../actions/NamespaceAction'; import NamespaceThunkActions from '../actions/NamespaceThunkActions'; import Namespace from '../types/Namespace'; import { PfColors } from './Pf/PfColors'; -import { HistoryManager, URLParams } from '../app/History'; +import { HistoryManager, URLParam } from '../app/History'; const namespaceButtonColors = { backgroundColor: PfColors.White, @@ -66,21 +66,21 @@ export class NamespaceDropdown extends React.PureComponent item.name).join(',')); + HistoryManager.setParam(URLParam.NAMESPACES, this.props.activeNamespaces.map(item => item.name).join(',')); } } } syncNamespacesURLParam = () => { - const namespaces = (HistoryManager.getParam(URLParams.NAMESPACES) || '').split(',').filter(Boolean); + const namespaces = (HistoryManager.getParam(URLParam.NAMESPACES) || '').split(',').filter(Boolean); if (namespaces.length > 0 && _.difference(namespaces, this.props.activeNamespaces.map(item => item.name))) { // We must change the props of namespaces const items = namespaces.map(ns => ({ name: ns } as Namespace)); this.props.setNamespaces(items); } else if (namespaces.length === 0 && this.props.activeNamespaces.length !== 0) { - HistoryManager.setParam(URLParams.NAMESPACES, this.props.activeNamespaces.map(item => item.name).join(',')); + HistoryManager.setParam(URLParam.NAMESPACES, this.props.activeNamespaces.map(item => item.name).join(',')); } }; diff --git a/src/components/Nav/NavUtils.tsx b/src/components/Nav/NavUtils.tsx index c1a4891343..b3d9185f59 100644 --- a/src/components/Nav/NavUtils.tsx +++ b/src/components/Nav/NavUtils.tsx @@ -2,7 +2,7 @@ import { NodeType, NodeParamsType, GraphType } from '../../types/Graph'; import { Layout, EdgeLabelMode } from '../../types/GraphFilter'; import { DurationInSeconds, PollIntervalInMs } from '../../types/Common'; import Namespace from '../../types/Namespace'; -import { URLParams } from '../../app/History'; +import { URLParam } from '../../app/History'; import { isKioskMode } from '../../utils/SearchParamUtils'; export type GraphUrlParams = { @@ -17,12 +17,12 @@ export type GraphUrlParams = { }; const buildCommonQueryParams = (params: GraphUrlParams): string => { - let q = `&${URLParams.GRAPH_EDGES}=${params.edgeLabelMode}`; - q += `&${URLParams.GRAPH_LAYOUT}=${params.graphLayout.name}`; - q += `&${URLParams.GRAPH_SERVICE_NODES}=${params.showServiceNodes}`; - q += `&${URLParams.GRAPH_TYPE}=${params.graphType}`; - q += `&${URLParams.DURATION}=${params.duration}`; - q += `&${URLParams.POLL_INTERVAL}=${params.refreshInterval}`; + let q = `&${URLParam.GRAPH_EDGES}=${params.edgeLabelMode}`; + q += `&${URLParam.GRAPH_LAYOUT}=${params.graphLayout.name}`; + q += `&${URLParam.GRAPH_SERVICE_NODES}=${params.showServiceNodes}`; + q += `&${URLParam.GRAPH_TYPE}=${params.graphType}`; + q += `&${URLParam.DURATION}=${params.duration}`; + q += `&${URLParam.POLL_INTERVAL}=${params.refreshInterval}`; return q; }; @@ -30,7 +30,7 @@ export const makeNamespacesGraphUrlFromParams = (params: GraphUrlParams): string let queryParams = buildCommonQueryParams(params); if (params.activeNamespaces.length > 0) { const namespaces = params.activeNamespaces.map(namespace => namespace.name).join(','); - queryParams += `&${URLParams.NAMESPACES}=${namespaces}`; + queryParams += `&${URLParam.NAMESPACES}=${namespaces}`; } if (isKioskMode()) { queryParams += '&kiosk=true'; diff --git a/src/components/ToolbarDropdown/__tests__/ToolbarDropdown.test.tsx b/src/components/ToolbarDropdown/__tests__/ToolbarDropdown.test.tsx index f6080186d2..d5831ebd1f 100644 --- a/src/components/ToolbarDropdown/__tests__/ToolbarDropdown.test.tsx +++ b/src/components/ToolbarDropdown/__tests__/ToolbarDropdown.test.tsx @@ -3,6 +3,7 @@ import { mount, shallow } from 'enzyme'; import ToolbarDropdown from '../ToolbarDropdown'; import { config } from '../../../config'; +import { serverConfig } from '../../../config/serverConfig'; const optionsChanged = jest.fn(); @@ -10,7 +11,7 @@ const data = [ { id: 'graph_filter_interval_duration', default: config.toolbar.defaultDuration, - options: config.toolbar.intervalDuration + options: serverConfig.durations }, { id: 'metrics_filter_poll_interval', @@ -63,8 +64,8 @@ describe('ToolbarDropdown', () => { handleSelect={optionsChanged} nameDropdown={'Duration'} initialValue={config.toolbar.defaultDuration} - initialLabel={config.toolbar.intervalDuration[config.toolbar.defaultDuration]} - options={config.toolbar.intervalDuration} + initialLabel={serverConfig.durations[config.toolbar.defaultDuration]} + options={serverConfig.durations} /> ); const elt = wrapper diff --git a/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap b/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap index 19c24b52e4..295894a54f 100644 --- a/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap +++ b/src/components/ToolbarDropdown/__tests__/__snapshots__/ToolbarDropdown.test.tsx.snap @@ -15,14 +15,10 @@ ShallowWrapper { "10800": "Last 3 hours", "1800": "Last 30 min", "21600": "Last 6 hours", - "2592000": "Last 30 days", "300": "Last 5 min", "3600": "Last hour", - "43200": "Last 12 hours", "60": "Last min", "600": "Last 10 min", - "604800": "Last 7 days", - "86400": "Last day", } } />, @@ -127,46 +123,6 @@ ShallowWrapper { > Last 6 hours - - Last 12 hours - - - Last day - - - Last 7 days - - - Last 30 days - , ], }, @@ -263,46 +219,6 @@ ShallowWrapper { > Last 6 hours , - - Last 12 hours - , - - Last day - , - - Last 7 days - , - - Last 30 days - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -431,74 +347,6 @@ ShallowWrapper { "rendered": "Last 6 hours", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12 hours", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12 hours", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last day", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last day", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7 days", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7 days", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30 days", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30 days", - "type": [Function], - }, ], "type": [Function], }, @@ -599,46 +447,6 @@ ShallowWrapper { > Last 6 hours - - Last 12 hours - - - Last day - - - Last 7 days - - - Last 30 days - , ], }, @@ -735,46 +543,6 @@ ShallowWrapper { > Last 6 hours , - - Last 12 hours - , - - Last day - , - - Last 7 days - , - - Last 30 days - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -903,74 +671,6 @@ ShallowWrapper { "rendered": "Last 6 hours", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12 hours", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12 hours", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last day", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last day", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7 days", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7 days", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30 days", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30 days", - "type": [Function], - }, ], "type": [Function], }, @@ -2183,14 +1883,10 @@ ShallowWrapper { "10800": "Last 3 hours", "1800": "Last 30 min", "21600": "Last 6 hours", - "2592000": "Last 30 days", "300": "Last 5 min", "3600": "Last hour", - "43200": "Last 12 hours", "60": "Last min", "600": "Last 10 min", - "604800": "Last 7 days", - "86400": "Last day", } } value={60} @@ -2296,46 +1992,6 @@ ShallowWrapper { > Last 6 hours - - Last 12 hours - - - Last day - - - Last 7 days - - - Last 30 days - , ], }, @@ -2432,46 +2088,6 @@ ShallowWrapper { > Last 6 hours , - - Last 12 hours - , - - Last day - , - - Last 7 days - , - - Last 30 days - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -2600,74 +2216,6 @@ ShallowWrapper { "rendered": "Last 6 hours", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12 hours", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12 hours", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last day", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last day", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7 days", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7 days", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30 days", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30 days", - "type": [Function], - }, ], "type": [Function], }, @@ -2768,46 +2316,6 @@ ShallowWrapper { > Last 6 hours - - Last 12 hours - - - Last day - - - Last 7 days - - - Last 30 days - , ], }, @@ -2904,46 +2412,6 @@ ShallowWrapper { > Last 6 hours , - - Last 12 hours - , - - Last day - , - - Last 7 days - , - - Last 30 days - , ], "disabled": false, "id": "graph_filter_interval_duration", @@ -3072,74 +2540,6 @@ ShallowWrapper { "rendered": "Last 6 hours", "type": [Function], }, - Object { - "instance": null, - "key": "43200", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 12 hours", - "disabled": false, - "divider": false, - "eventKey": "43200", - "header": false, - }, - "ref": null, - "rendered": "Last 12 hours", - "type": [Function], - }, - Object { - "instance": null, - "key": "86400", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last day", - "disabled": false, - "divider": false, - "eventKey": "86400", - "header": false, - }, - "ref": null, - "rendered": "Last day", - "type": [Function], - }, - Object { - "instance": null, - "key": "604800", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 7 days", - "disabled": false, - "divider": false, - "eventKey": "604800", - "header": false, - }, - "ref": null, - "rendered": "Last 7 days", - "type": [Function], - }, - Object { - "instance": null, - "key": "2592000", - "nodeType": "class", - "props": Object { - "active": false, - "bsClass": "dropdown", - "children": "Last 30 days", - "disabled": false, - "divider": false, - "eventKey": "2592000", - "header": false, - }, - "ref": null, - "rendered": "Last 30 days", - "type": [Function], - }, ], "type": [Function], }, diff --git a/src/config/config.ts b/src/config/config.ts index 38ae7d6b08..345c6eeb33 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -16,20 +16,6 @@ const conf = { toolbar: { /** Duration default in 1 minute */ defaultDuration: 1 * UNIT_TIME.MINUTE, - /** Options in interval duration */ - intervalDuration: { - 60: 'Last min', - 300: 'Last 5 min', - 600: 'Last 10 min', - 1800: 'Last 30 min', - 3600: 'Last hour', - 10800: 'Last 3 hours', - 21600: 'Last 6 hours', - 43200: 'Last 12 hours', - 86400: 'Last day', - 604800: 'Last 7 days', - 2592000: 'Last 30 days' - }, /** By default refresh is 15 seconds */ defaultPollInterval: 15 * MILLISECONDS, /** Options in refresh */ diff --git a/src/config/serverConfig.ts b/src/config/serverConfig.ts index cb6e88ae0f..94166331a7 100644 --- a/src/config/serverConfig.ts +++ b/src/config/serverConfig.ts @@ -1,42 +1,85 @@ import deepFreeze from 'deep-freeze'; -import { store } from '../store/ConfigStore'; -import { KialiAppState, ServerConfig } from '../store/Store'; -import { PersistPartial } from 'redux-persist'; import { DurationInSeconds } from '../types/Common'; -import { forOwn, pickBy } from 'lodash'; -// It's not great to access the store directly for convenience but the alternative is -// a huge code ripple just to access some server config. better to just have this one utility. -export const serverConfig = (): ServerConfig => { - const actualState = store.getState() || ({} as KialiAppState & PersistPartial); - return deepFreeze(actualState.serverConfig); -}; +export type IstioLabelKey = 'appLabelName' | 'versionLabelName'; +export type Durations = { [key: number]: string }; + +export interface ServerConfig { + istioNamespace: string; + istioLabels: { [key in IstioLabelKey]: string }; + prometheus: { + globalScrapeInterval?: DurationInSeconds; + storageTsdbRetention?: DurationInSeconds; + }; + webRoot?: string; + durations: Durations; +} -// getValidDurations returns a new object with only the durations <= retention -export const getValidDurations = ( - durations: { [key: number]: string }, - retention?: DurationInSeconds -): { [key: number]: string } => { - const validDurations = pickBy(durations, (_, key: number) => { - return !retention || key <= retention; +const toDurations = (tupleArray: [number, string][]): Durations => { + const obj = {}; + tupleArray.forEach(tuple => { + obj[tuple[0]] = tuple[1]; }); - return validDurations as { [key: number]: string }; + return obj; }; -// getValidDuration returns duration if it is a valid property key in durations, otherwise it return the first property -// key in durations. It is assumed that durations has at least one property. -export const getValidDuration = ( - durations: { [key: number]: string }, - duration: DurationInSeconds -): DurationInSeconds => { - let validDuration = 0; - forOwn(durations, (_, key) => { - const d = Number(key); - if (d === duration) { - validDuration = d; - } else if (!validDuration) { - validDuration = d; +let durationsTuples: [number, string][] = [ + [60, 'Last min'], + [300, 'Last 5 min'], + [600, 'Last 10 min'], + [1800, 'Last 30 min'], + [3600, 'Last hour'], + [10800, 'Last 3 hours'], + [21600, 'Last 6 hours'], + [43200, 'Last 12 hours'], + [86400, 'Last day'], + [604800, 'Last 7 days'], + [2592000, 'Last 30 days'] +]; + +const computeValidDurations = (cfg: ServerConfig) => { + if (cfg.prometheus.storageTsdbRetention) { + // Make sure we'll keep at least one item + if (cfg.prometheus.storageTsdbRetention <= durationsTuples[0][0]) { + durationsTuples = [durationsTuples[0]]; + } else { + durationsTuples = durationsTuples.filter(d => d[0] <= cfg.prometheus.storageTsdbRetention!); } - }); - return validDuration; + } + cfg.durations = toDurations(durationsTuples); +}; + +const tmpConfig: ServerConfig = process.env.TEST_RUNNER + ? { + istioNamespace: 'istio-system', + istioLabels: { + appLabelName: 'app', + versionLabelName: 'version' + }, + prometheus: { + globalScrapeInterval: 15, + storageTsdbRetention: 21600 + }, + durations: {} + } + : (window as any).serverConfig; +if (tmpConfig) { + computeValidDurations(tmpConfig); +} else { + console.error('serverConfig object is missing'); +} +export const serverConfig: ServerConfig = deepFreeze(tmpConfig); + +export const toValidDuration = (duration: number): number => { + // Check if valid + if (serverConfig.durations[duration]) { + return duration; + } + // Get closest duration + for (let i = durationsTuples.length - 1; i >= 0; i--) { + if (duration > durationsTuples[i][0]) { + return durationsTuples[i][0]; + } + } + return durationsTuples[0][0]; }; diff --git a/src/pages/AppList/AppListComponent.tsx b/src/pages/AppList/AppListComponent.tsx index 8050fbccad..f40f9ebbfd 100644 --- a/src/pages/AppList/AppListComponent.tsx +++ b/src/pages/AppList/AppListComponent.tsx @@ -12,7 +12,6 @@ import { PromisesRegistry } from '../../utils/CancelablePromises'; import { ListPagesHelper } from '../../components/ListPage/ListPagesHelper'; import { SortField } from '../../types/SortFilters'; import { ListComponent } from '../../components/ListPage/ListComponent'; -import { HistoryManager, URLParams } from '../../app/History'; interface AppListComponentState extends ListComponent.State { rateInterval: number; @@ -68,11 +67,6 @@ class AppListComponent extends ListComponent.Component { - HistoryManager.setParam(URLParams.DURATION, String(key)); - this.setState({ rateInterval: key }); - }; - sortItemList(apps: AppListItem[], sortField: SortField, isAscending: boolean): Promise { // Chain promises, as there may be an ongoing fetch/refresh and sort can be called after UI interaction // This ensures that the list will display the new data with the right sorting diff --git a/src/pages/Graph/SummaryPanelCommon.tsx b/src/pages/Graph/SummaryPanelCommon.tsx index 5935acfdb2..89e4009702 100644 --- a/src/pages/Graph/SummaryPanelCommon.tsx +++ b/src/pages/Graph/SummaryPanelCommon.tsx @@ -178,7 +178,7 @@ export const renderLabels = (data: NodeData) => { <>
{hasNamespace &&
); diff --git a/src/pages/Graph/SummaryPanelEdge.tsx b/src/pages/Graph/SummaryPanelEdge.tsx index 65d0e0b7cf..8b6cf82f5e 100644 --- a/src/pages/Graph/SummaryPanelEdge.tsx +++ b/src/pages/Graph/SummaryPanelEdge.tsx @@ -231,8 +231,8 @@ export default class SummaryPanelEdge extends React.Component (