diff --git a/package.json b/package.json index 8dc951b8371..b86a10880e1 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,7 @@ "jquery": "3.4.1", "lodash": "4.17.14", "marked": "0.6.2", + "memoize-one": "5.1.1", "moment": "2.24.0", "mousetrap": "1.6.3", "mousetrap-global-bind": "1.1.0", diff --git a/public/app/features/explore/ElapsedTime.tsx b/public/app/features/explore/ElapsedTime.tsx index 145e5b261a1..ab50cc12472 100644 --- a/public/app/features/explore/ElapsedTime.tsx +++ b/public/app/features/explore/ElapsedTime.tsx @@ -5,7 +5,9 @@ const INTERVAL = 150; export interface Props { time?: number; - renderCount?: number; + // Use this to reset the timer. Any value is allowed just need to be !== from the previous. + // Keep in mind things like [] !== [] or {} !== {}. + resetKey?: any; className?: string; humanize?: boolean; } @@ -14,6 +16,9 @@ export interface State { elapsed: number; } +/** + * Shows an incremental time ticker of elapsed time from some event. + */ export default class ElapsedTime extends PureComponent { offset: number; timer: number; @@ -40,7 +45,7 @@ export default class ElapsedTime extends PureComponent { this.start(); } - if (nextProps.renderCount) { + if (nextProps.resetKey !== this.props.resetKey) { clearInterval(this.timer); this.start(); } diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 137e13e4664..0838fae1215 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -5,6 +5,7 @@ import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import _ from 'lodash'; import { AutoSizer } from 'react-virtualized'; +import memoizeOne from 'memoize-one'; // Services & Utils import store from 'app/core/store'; @@ -327,6 +328,9 @@ export class Explore extends React.PureComponent { } } +const ensureQueriesMemoized = memoizeOne(ensureQueries); +const getTimeRangeFromUrlMemoized = memoizeOne(getTimeRangeFromUrl); + function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { const explore = state.explore; const { split } = explore; @@ -356,8 +360,8 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { const { datasource, queries, range: urlRange, mode: urlMode, ui } = (urlState || {}) as ExploreUrlState; const initialDatasource = datasource || store.get(lastUsedDatasourceKeyForOrgId(state.user.orgId)); - const initialQueries: DataQuery[] = ensureQueries(queries); - const initialRange = urlRange ? getTimeRangeFromUrl(urlRange, timeZone).raw : DEFAULT_RANGE; + const initialQueries: DataQuery[] = ensureQueriesMemoized(queries); + const initialRange = urlRange ? getTimeRangeFromUrlMemoized(urlRange, timeZone).raw : DEFAULT_RANGE; let newMode: ExploreMode; if (supportedModes.length) { diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 550f1d8bdc5..32fffb284c7 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -1,6 +1,7 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; +import memoizeOne from 'memoize-one'; import { ExploreId, ExploreMode } from 'app/types/explore'; import { DataSourceSelectItem, ToggleButtonGroup, ToggleButton } from '@grafana/ui'; @@ -236,6 +237,41 @@ export class UnConnectedExploreToolbar extends PureComponent { } } +const getModeOptionsMemoized = memoizeOne( + ( + supportedModes: ExploreMode[], + mode: ExploreMode + ): [Array>, SelectableValue] => { + const supportedModeOptions: Array> = []; + let selectedModeOption = null; + for (const supportedMode of supportedModes) { + switch (supportedMode) { + case ExploreMode.Metrics: + const option1 = { + value: ExploreMode.Metrics, + label: ExploreMode.Metrics, + }; + supportedModeOptions.push(option1); + if (mode === ExploreMode.Metrics) { + selectedModeOption = option1; + } + break; + case ExploreMode.Logs: + const option2 = { + value: ExploreMode.Logs, + label: ExploreMode.Logs, + }; + supportedModeOptions.push(option2); + if (mode === ExploreMode.Logs) { + selectedModeOption = option2; + } + break; + } + } + return [supportedModeOptions, selectedModeOption]; + } +); + const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => { const splitted = state.explore.split; const exploreItem = state.explore[exploreId]; @@ -257,32 +293,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps const hasLiveOption = datasourceInstance && datasourceInstance.meta && datasourceInstance.meta.streaming ? true : false; - const supportedModeOptions: Array> = []; - let selectedModeOption = null; - for (const supportedMode of supportedModes) { - switch (supportedMode) { - case ExploreMode.Metrics: - const option1 = { - value: ExploreMode.Metrics, - label: ExploreMode.Metrics, - }; - supportedModeOptions.push(option1); - if (mode === ExploreMode.Metrics) { - selectedModeOption = option1; - } - break; - case ExploreMode.Logs: - const option2 = { - value: ExploreMode.Logs, - label: ExploreMode.Logs, - }; - supportedModeOptions.push(option2); - if (mode === ExploreMode.Logs) { - selectedModeOption = option2; - } - break; - } - } + const [supportedModeOptions, selectedModeOption] = getModeOptionsMemoized(supportedModes, mode); return { datasourceMissing, diff --git a/public/app/features/explore/LiveLogs.tsx b/public/app/features/explore/LiveLogs.tsx index 190aa75716d..72acc6edecb 100644 --- a/public/app/features/explore/LiveLogs.tsx +++ b/public/app/features/explore/LiveLogs.tsx @@ -40,28 +40,10 @@ export interface Props extends Themeable { stopLive: () => void; } -export interface State { - renderCount: number; -} - -class LiveLogs extends PureComponent { +class LiveLogs extends PureComponent { private liveEndDiv: HTMLDivElement = null; - constructor(props: Props) { - super(props); - this.state = { renderCount: 0 }; - } - componentDidUpdate(prevProps: Props) { - const prevRows: LogRowModel[] = prevProps.logsResult ? prevProps.logsResult.rows : []; - const rows: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : []; - - if (prevRows !== rows) { - this.setState({ - renderCount: this.state.renderCount + 1, - }); - } - if (this.liveEndDiv) { this.liveEndDiv.scrollIntoView(false); } @@ -69,7 +51,6 @@ class LiveLogs extends PureComponent { render() { const { theme, timeZone } = this.props; - const { renderCount } = this.state; const styles = getStyles(theme); const rowsToRender: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : []; const showUtc = timeZone === 'utc'; @@ -109,7 +90,7 @@ class LiveLogs extends PureComponent {
- Last line received: ago + Last line received: ago ; onChangeTime: (range: AbsoluteTimeRange) => void; onClickLabel?: (label: string, value: string) => void; onStartScanning?: () => void; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index e0f16575530..bdf0c14bb57 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -43,7 +43,6 @@ interface LogsContainerProps { toggleLogLevelAction: typeof toggleLogLevelAction; changeDedupStrategy: typeof changeDedupStrategy; dedupStrategy: LogsDedupStrategy; - hiddenLogLevels: Set; width: number; isLive: boolean; stopLive: typeof changeRefreshIntervalAction; @@ -100,7 +99,6 @@ export class LogsContainer extends PureComponent { scanning, range, width, - hiddenLogLevels, isLive, } = this.props; @@ -131,7 +129,6 @@ export class LogsContainer extends PureComponent { scanning={scanning} scanRange={range.raw} width={width} - hiddenLogLevels={hiddenLogLevels} getRowContext={this.getLogRowContext} /> @@ -155,7 +152,6 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string } } = item; const loading = loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming; const { dedupStrategy } = exploreItemUIStateSelector(item); - const hiddenLogLevels = new Set(item.hiddenLogLevels); const dedupedResult = deduplicatedLogsSelector(item); const timeZone = getTimeZone(state.user); @@ -166,7 +162,6 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string } scanning, timeZone, dedupStrategy, - hiddenLogLevels, dedupedResult, datasourceInstance, isLive, diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index 34839620da2..12f8f013fe0 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import _ from 'lodash'; import { hot } from 'react-hot-loader'; +import memoizeOne from 'memoize-one'; // @ts-ignore import { connect } from 'react-redux'; @@ -13,7 +14,7 @@ import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/act // Types import { StoreState } from 'app/types'; -import { TimeRange, AbsoluteTimeRange, toDataFrame, guessFieldTypes } from '@grafana/data'; +import { TimeRange, AbsoluteTimeRange, toDataFrame, guessFieldTypes, GraphSeriesXY, LoadingState } from '@grafana/data'; import { DataQuery, DataSourceApi, QueryFixAction, DataSourceStatus, PanelData, DataQueryError } from '@grafana/ui'; import { HistoryItem, ExploreItemState, ExploreId, ExploreMode } from 'app/types/explore'; import { Emitter } from 'app/core/utils/emitter'; @@ -198,6 +199,17 @@ export class QueryRow extends PureComponent { } } +const makeQueryResponseMemoized = memoizeOne( + (graphResult: GraphSeriesXY[], error: DataQueryError, loadingState: LoadingState): PanelData => { + const series = graphResult ? graphResult.map(serie => guessFieldTypes(toDataFrame(serie))) : []; // TODO: use DataFrame + return { + series, + state: loadingState, + error, + }; + } +); + function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; @@ -217,12 +229,7 @@ function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) const query = queries[index]; const datasourceStatus = datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected; const error = queryErrors.filter(queryError => queryError.refId === query.refId)[0]; - const series = graphResult ? graphResult.map(serie => guessFieldTypes(toDataFrame(serie))) : []; // TODO: use DataFrame - const queryResponse: PanelData = { - series, - state: loadingState, - error, - }; + const queryResponse = makeQueryResponseMemoized(graphResult, error, loadingState); return { datasourceInstance, diff --git a/yarn.lock b/yarn.lock index 807aba53ceb..41bb24cde1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11741,6 +11741,11 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" +memoize-one@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== + "memoize-one@>=3.1.1 <6", memoize-one@^5.0.0: version "5.0.4" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.4.tgz#005928aced5c43d890a4dfab18ca908b0ec92cbc" @@ -15485,10 +15490,6 @@ redux-mock-store@1.5.3: dependencies: lodash.isplainobject "^4.0.6" -redux-observable@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/redux-observable/-/redux-observable-1.1.0.tgz#323a8fe53e89fdb519be2807b55f08e21c13e6f1" - redux-thunk@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"