Skip to content

Commit 8344148

Browse files
authored
fix(wasm): check if wasm enabled and no errors (#1762)
1 parent d4e312f commit 8344148

File tree

12 files changed

+102
-40
lines changed

12 files changed

+102
-40
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"node": ">=24"
106106
},
107107
"dependencies": {
108-
"@bsull/augurs": "^0.6.0",
108+
"@bsull/augurs": "^0.10.2",
109109
"@emotion/css": "11.10.6",
110110
"@grafana/assistant": "0.1.13",
111111
"@grafana/data": "^12.4.0",

src/Components/ServiceScene/Breakdowns/FieldValuesBreakdownScene.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import { logger } from '../../../services/logger';
2929
import { LokiQuery } from '../../../services/lokiQuery';
3030
import { getQueryRunner } from '../../../services/panel';
3131
import { buildDataQuery, renderLogQLFieldFilters, renderLogQLMetadataFilters } from '../../../services/query';
32-
import { DEFAULT_SORT_BY } from '../../../services/sorting';
3332
import { getSortByPreference } from '../../../services/store';
3433
import {
3534
getFieldGroupByVariable,
@@ -50,6 +49,7 @@ import { LayoutSwitcher } from './LayoutSwitcher';
5049
import { ValueSummaryPanelScene } from './Panels/ValueSummary';
5150
import { QueryErrorAlert } from './QueryErrorAlert';
5251
import { getLabelValue } from './SortByScene';
52+
import { DEFAULT_SORT_DIRECTION, getDefaultSortBy } from 'services/sorting';
5353

5454
export interface FieldValuesBreakdownSceneState extends SceneObjectState {
5555
$data?: SceneDataProvider;
@@ -377,7 +377,7 @@ export class FieldValuesBreakdownScene extends SceneObjectBase<FieldValuesBreakd
377377
*/
378378
private buildBody(query: LokiQuery) {
379379
const { optionValue, parser } = this.getParserForThisField();
380-
const { direction, sortBy } = getSortByPreference('fields', DEFAULT_SORT_BY, 'desc');
380+
const { direction, sortBy } = getSortByPreference('fields', getDefaultSortBy(), DEFAULT_SORT_DIRECTION);
381381
const fieldsBreakdownScene = sceneGraph.getAncestor(this, FieldsBreakdownScene);
382382
const getFilter = () => fieldsBreakdownScene.state.search.state.filter ?? '';
383383

src/Components/ServiceScene/Breakdowns/FieldsBreakdownScene.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { CustomConstantVariable, CustomConstantVariableState } from '../../../se
2121
import { ValueSlugs } from '../../../services/enums';
2222
import { navigateToValueBreakdown } from '../../../services/navigate';
2323
import { getRouteParams, getUILabelName } from '../../../services/routing';
24-
import { DEFAULT_SORT_BY } from '../../../services/sorting';
2524
import { getFieldGroupByVariable, getLabelsVariable } from '../../../services/variableGetters';
2625
import { clearVariables, getVariablesThatCanBeCleared } from '../../../services/variableHelpers';
2726
import { IndexScene } from '../../IndexScene/IndexScene';
@@ -39,6 +38,7 @@ import { SortByScene, SortCriteriaChanged } from './SortByScene';
3938
import { StatusWrapper } from './StatusWrapper';
4039
import { reportAppInteraction, USER_EVENTS_ACTIONS, USER_EVENTS_PAGES } from 'services/analytics';
4140
import { getFieldOptions } from 'services/filters';
41+
import { DEFAULT_SORT_DIRECTION, getDefaultSortBy } from 'services/sorting';
4242
import { getSortByPreference } from 'services/store';
4343
import { ALL_VARIABLE_VALUE, VAR_FIELD_GROUP_BY, VAR_FIELDS, VAR_LABELS } from 'services/variables';
4444

@@ -269,7 +269,7 @@ export class FieldsBreakdownScene extends SceneObjectBase<FieldsBreakdownSceneSt
269269
}
270270

271271
const variable = getFieldGroupByVariable(this);
272-
const { direction, sortBy } = getSortByPreference('fields', DEFAULT_SORT_BY, 'desc');
272+
const { direction, sortBy } = getSortByPreference('fields', getDefaultSortBy(), DEFAULT_SORT_DIRECTION);
273273

274274
reportAppInteraction(
275275
USER_EVENTS_PAGES.service_details,

src/Components/ServiceScene/Breakdowns/LabelBreakdownScene.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { areArraysEqual } from '../../../services/comparison';
2121
import { CustomConstantVariable, CustomConstantVariableState } from '../../../services/CustomConstantVariable';
2222
import { ValueSlugs } from '../../../services/enums';
2323
import { navigateToValueBreakdown } from '../../../services/navigate';
24-
import { DEFAULT_SORT_BY } from '../../../services/sorting';
2524
import { getLabelGroupByVariable, getLabelsVariable } from '../../../services/variableGetters';
2625
import { clearVariables, getVariablesThatCanBeCleared } from '../../../services/variableHelpers';
2726
import { IndexScene } from '../../IndexScene/IndexScene';
@@ -38,6 +37,7 @@ import { StatusWrapper } from './StatusWrapper';
3837
import { reportAppInteraction, USER_EVENTS_ACTIONS, USER_EVENTS_PAGES } from 'services/analytics';
3938
import { getLabelOptions } from 'services/filters';
4039
import { getRouteParams } from 'services/routing';
40+
import { DEFAULT_SORT_DIRECTION, getDefaultSortBy } from 'services/sorting';
4141
import { getSortByPreference } from 'services/store';
4242
import { ALL_VARIABLE_VALUE, SERVICE_NAME, SERVICE_UI_LABEL, VAR_LABEL_GROUP_BY, VAR_LABELS } from 'services/variables';
4343

@@ -273,7 +273,7 @@ export class LabelBreakdownScene extends SceneObjectBase<LabelBreakdownSceneStat
273273
const variable = getLabelGroupByVariable(this);
274274
variable.changeValueTo(value);
275275

276-
const { direction, sortBy } = getSortByPreference('labels', DEFAULT_SORT_BY, 'desc');
276+
const { direction, sortBy } = getSortByPreference('labels', getDefaultSortBy(), DEFAULT_SORT_DIRECTION);
277277
reportAppInteraction(
278278
USER_EVENTS_PAGES.service_details,
279279
USER_EVENTS_ACTIONS.service_details.select_field_in_breakdown_clicked,

src/Components/ServiceScene/Breakdowns/LabelValuesBreakdownScene.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { buildLabelsQuery, LABEL_BREAKDOWN_GRID_TEMPLATE_COLUMNS } from '../../.
2525
import { logger } from '../../../services/logger';
2626
import { getQueryRunner, setLevelColorOverrides } from '../../../services/panel';
2727
import { renderLevelsFilter, renderLogQLLabelFilters } from '../../../services/query';
28-
import { DEFAULT_SORT_BY } from '../../../services/sorting';
2928
import { getSortByPreference } from '../../../services/store';
3029
import {
3130
getFieldsVariable,
@@ -53,6 +52,7 @@ import { LayoutSwitcher } from './LayoutSwitcher';
5352
import { NoMatchingLabelsScene } from './NoMatchingLabelsScene';
5453
import { ValueSummaryPanelScene } from './Panels/ValueSummary';
5554
import { getLabelValue } from './SortByScene';
55+
import { DEFAULT_SORT_DIRECTION, getDefaultSortBy } from 'services/sorting';
5656

5757
type DisplayError = DataQueryError & { displayed: boolean };
5858
type DisplayErrors = Record<string, DisplayError>;
@@ -373,7 +373,7 @@ export class LabelValuesBreakdownScene extends SceneObjectBase<LabelValueBreakdo
373373
.setMenu(new PanelMenu({}))
374374
.setTitle(tagKey);
375375

376-
const { direction, sortBy } = getSortByPreference('labels', DEFAULT_SORT_BY, 'desc');
376+
const { direction, sortBy } = getSortByPreference('labels', getDefaultSortBy(), DEFAULT_SORT_DIRECTION);
377377

378378
const getFilter = () => labelBreakdownScene.state.search.state.filter ?? '';
379379

src/Components/ServiceScene/Breakdowns/SortByScene.test.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,35 @@ import { render, screen, waitFor } from '@testing-library/react';
44
import { select } from 'react-select-event';
55

66
import { SortByScene, SortCriteriaChanged } from './SortByScene';
7+
import { DEFAULT_SORT_BY, setWasmSortInit } from 'services/sorting';
78
import { setSortByPreference } from 'services/store';
89

910
describe('SortByScene', () => {
1011
let scene: SortByScene;
1112
beforeEach(() => {
1213
localStorage.clear();
14+
setWasmSortInit(true);
1315
scene = new SortByScene({ target: 'fields' });
1416
});
1517

16-
test('Sorts by standard deviation by default', () => {
18+
test('Shows changepoint as default when WASM init succeeded', () => {
1719
render(<scene.Component model={scene} />);
1820

1921
expect(screen.getByText('Most relevant')).toBeInTheDocument();
2022
expect(screen.getByText('Desc')).toBeInTheDocument();
2123
});
2224

25+
test('Hides changepoint and outliers when WASM init failed', () => {
26+
setWasmSortInit(false);
27+
scene = new SortByScene({ target: 'fields' });
28+
render(<scene.Component model={scene} />);
29+
30+
expect(screen.queryByText('Most relevant')).not.toBeInTheDocument();
31+
expect(screen.queryByText('Outlying values')).not.toBeInTheDocument();
32+
expect(screen.getByText('Widest spread')).toBeInTheDocument();
33+
expect(screen.getByText('Desc')).toBeInTheDocument();
34+
});
35+
2336
test('Retrieves stored sorting preferences', () => {
2437
setSortByPreference('fields', 'alphabetical', 'asc');
2538

@@ -30,7 +43,7 @@ describe('SortByScene', () => {
3043
expect(screen.getByText('Asc')).toBeInTheDocument();
3144
});
3245

33-
test('Reports criteria changes', async () => {
46+
test('Reports sort-by criteria changes', async () => {
3447
const eventSpy = jest.spyOn(scene, 'publishEvent');
3548

3649
render(<scene.Component model={scene} />);
@@ -40,13 +53,23 @@ describe('SortByScene', () => {
4053
expect(eventSpy).toHaveBeenCalledWith(new SortCriteriaChanged('fields', 'max', 'desc'), true);
4154
});
4255

43-
test('Reports criteria changes', async () => {
56+
test('Reports sort-direction criteria changes', async () => {
4457
const eventSpy = jest.spyOn(scene, 'publishEvent');
4558

4659
render(<scene.Component model={scene} />);
4760

4861
await waitFor(() => select(screen.getByLabelText('Sort direction'), 'Asc', { container: document.body }));
4962

50-
expect(eventSpy).toHaveBeenCalledWith(new SortCriteriaChanged('fields', 'changepoint', 'asc'), true);
63+
expect(eventSpy).toHaveBeenCalledWith(new SortCriteriaChanged('fields', DEFAULT_SORT_BY, 'asc'), true);
64+
});
65+
66+
test('Overrides stored changepoint/outliers preference when WASM init failed', () => {
67+
setSortByPreference('fields', DEFAULT_SORT_BY, 'desc');
68+
setWasmSortInit(false);
69+
scene = new SortByScene({ target: 'fields' });
70+
render(<scene.Component model={scene} />);
71+
72+
expect(screen.getByText('Widest spread')).toBeInTheDocument();
73+
expect(screen.queryByText('Most relevant')).not.toBeInTheDocument();
5174
});
5275
});

src/Components/ServiceScene/Breakdowns/SortByScene.tsx

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import { BusEventBase, DataFrame, FieldReducerInfo, fieldReducers, ReducerID, Se
44
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
55
import { InlineField, Select } from '@grafana/ui';
66

7-
import { DEFAULT_SORT_BY } from '../../../services/sorting';
8-
import { testIds } from '../../../services/testIds';
97
import { getLabelValueFromDataFrame } from 'services/levels';
8+
import {
9+
DEFAULT_SORT_BY,
10+
DEFAULT_SORT_DIRECTION,
11+
getDefaultSortBy,
12+
isWasmInit,
13+
SORT_BY_OUTLIERS,
14+
SortBy,
15+
} from 'services/sorting';
1016
import { getSortByPreference, setSortByPreference } from 'services/store';
17+
import { testIds } from 'services/testIds';
1118

12-
export type SortBy = 'changepoint' | 'outliers' | ReducerID | '';
1319
export type SortDirection = 'asc' | 'desc';
1420
export interface SortBySceneState extends SceneObjectState {
1521
direction: SortDirection;
@@ -32,12 +38,12 @@ export class SortByScene extends SceneObjectBase<SortBySceneState> {
3238
{
3339
description: 'Smart ordering of graphs based on the most significant spikes in the data',
3440
label: 'Most relevant',
35-
value: 'changepoint',
41+
value: DEFAULT_SORT_BY,
3642
},
3743
{
3844
description: 'Order by the amount of outlying values in the data',
3945
label: 'Outlying values',
40-
value: 'outliers',
46+
value: SORT_BY_OUTLIERS,
4147
},
4248
{
4349
description: 'Sort graphs by deviation from the average value',
@@ -73,10 +79,13 @@ export class SortByScene extends SceneObjectBase<SortBySceneState> {
7379
];
7480

7581
constructor(state: Pick<SortBySceneState, 'target'>) {
76-
const { direction, sortBy } = getSortByPreference(state.target, DEFAULT_SORT_BY, 'desc');
82+
const defaultSortBy = getDefaultSortBy();
83+
const { direction, sortBy } = getSortByPreference(state.target, defaultSortBy, DEFAULT_SORT_DIRECTION);
84+
const finalSortBy: SortBy =
85+
(sortBy === DEFAULT_SORT_BY || sortBy === SORT_BY_OUTLIERS) && !isWasmInit() ? defaultSortBy : sortBy;
7786
super({
7887
direction,
79-
sortBy,
88+
sortBy: finalSortBy,
8089
target: state.target,
8190
});
8291
}
@@ -101,8 +110,17 @@ export class SortByScene extends SceneObjectBase<SortBySceneState> {
101110

102111
public static Component = ({ model }: SceneComponentProps<SortByScene>) => {
103112
const { direction, sortBy } = model.useState();
104-
const group = model.sortingOptions.find((group) =>
105-
group.options.find((option: SelectableValue<SortBy>) => option.value === sortBy)
113+
const wasmInit = isWasmInit();
114+
const defaultOptions = wasmInit
115+
? model.sortingOptions
116+
: model.sortingOptions.map((group) => ({
117+
...group,
118+
options: group.options.filter(
119+
(opt: SelectableValue<SortBy>) => opt.value !== DEFAULT_SORT_BY && opt.value !== SORT_BY_OUTLIERS
120+
),
121+
}));
122+
const group = defaultOptions.find((g) =>
123+
g.options.find((option: SelectableValue<SortBy>) => option.value === sortBy)
106124
);
107125
const sortByValue: SelectableValue<SortBy> | undefined = group?.options.find(
108126
(option: SelectableValue<SortBy>) => option.value === sortBy
@@ -119,7 +137,7 @@ export class SortByScene extends SceneObjectBase<SortBySceneState> {
119137
value={sortByValue}
120138
width={20}
121139
isSearchable={true}
122-
options={model.sortingOptions}
140+
options={defaultOptions}
123141
placeholder={'Choose criteria'}
124142
onChange={model.onCriteriaChange}
125143
inputId="sort-by-criteria"
@@ -139,7 +157,7 @@ export class SortByScene extends SceneObjectBase<SortBySceneState> {
139157
},
140158
{
141159
label: 'Desc',
142-
value: 'desc',
160+
value: DEFAULT_SORT_DIRECTION,
143161
},
144162
]}
145163
></Select>

src/module.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const App = lazy(async () => {
1717
// Initialize i18n before loading any components
1818
await initPluginTranslations(pluginJson.id);
1919

20-
const { wasmSupported } = await import('services/sorting');
20+
const { logger } = await import('services/logger');
21+
const { setWasmSortInit, wasmSupported } = await import('services/sorting');
2122

2223
const { default: initRuntimeDs } = await import('services/datasource');
2324
const { default: initChangepoint } = await import('@bsull/augurs/changepoint');
@@ -28,8 +29,10 @@ const App = lazy(async () => {
2829
if (wasmSupported()) {
2930
try {
3031
await Promise.all([initChangepoint(), initOutlier()]);
32+
setWasmSortInit(true);
3133
} catch (e) {
32-
console.warn('grafana-lokiexplore-app: WebAssembly init failed, ML sorting disabled.', e);
34+
logger.warn('WebAssembly init failed, ML sorting disabled.');
35+
setWasmSortInit(false);
3336
}
3437
}
3538

src/services/fields.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import { DrawStyle, StackingMode } from '@grafana/ui';
1313

1414
import { PanelMenu, TimeSeriesQueryType } from '../Components/Panels/PanelMenu';
15-
import { SortBy, SortByScene } from '../Components/ServiceScene/Breakdowns/SortByScene';
15+
import { SortByScene } from '../Components/ServiceScene/Breakdowns/SortByScene';
1616
import { getDetectedFieldsFrame, getLogsPanelFrame, ServiceScene } from '../Components/ServiceScene/ServiceScene';
1717
import { LabelType } from './fieldsTypes';
1818
import { logger } from './logger';
@@ -25,6 +25,7 @@ import {
2525
} from './logsFrame';
2626
import { getLabelTypeFromFrame } from './lokiQuery';
2727
import { setLevelColorOverrides } from './panel';
28+
import { SortBy } from './sorting';
2829
import {
2930
getFieldsVariable,
3031
getJSONFieldsVariable,

src/services/sorting.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,37 @@ import { getLabelValueFromDataFrame } from './levels';
99
import { logger } from './logger';
1010

1111
export const DEFAULT_SORT_BY = 'changepoint';
12+
export const SORT_BY_OUTLIERS = 'outliers';
13+
export const DEFAULT_SORT_DIRECTION = 'desc' as const;
14+
15+
export type SortBy = typeof DEFAULT_SORT_BY | typeof SORT_BY_OUTLIERS | ReducerID | '';
16+
17+
let wasmInitSucceeded = false;
18+
19+
export const setWasmSortInit = (succeeded: boolean) => {
20+
wasmInitSucceeded = succeeded;
21+
};
22+
23+
export const isWasmInit = () => wasmInitSucceeded;
24+
25+
export const getDefaultSortBy = (): SortBy => (isWasmInit() ? DEFAULT_SORT_BY : ReducerID.stdDev);
1226

1327
export const sortSeries = memoize(
1428
(series: DataFrame[], sortBy: string, direction: string) => {
1529
if (sortBy === 'alphabetical') {
1630
return sortSeriesByName(series, direction);
1731
}
1832

19-
if (sortBy === 'outliers') {
33+
if (sortBy === SORT_BY_OUTLIERS) {
2034
initOutlierDetector(series);
2135
}
2236

2337
const reducer = (dataFrame: DataFrame) => {
2438
// ML & Wasm sorting options
2539
try {
26-
if (sortBy === 'changepoint') {
40+
if (sortBy === DEFAULT_SORT_BY) {
2741
return calculateDataFrameChangepoints(dataFrame);
28-
} else if (sortBy === 'outliers') {
42+
} else if (sortBy === SORT_BY_OUTLIERS) {
2943
return calculateOutlierValue(series, dataFrame);
3044
}
3145
} catch (e) {
@@ -73,12 +87,14 @@ export const sortSeries = memoize(
7387
'_' +
7488
frame.fields.map((field) => field.name + '_' + field.values[0] + '_' + field.values[field.values.length - 1])
7589
);
76-
return `${firstValue}_${lastValue}_${firstTimestamp}_${lastTimestamp}_${series.length}_${allSeriesKey}_${sortBy}_${direction}`;
90+
return `${firstValue}_${lastValue}_${firstTimestamp}_${lastTimestamp}_${
91+
series.length
92+
}_${allSeriesKey}_${sortBy}_${direction}_${isWasmInit()}`;
7793
}
7894
);
7995

8096
export const calculateDataFrameChangepoints = (data: DataFrame) => {
81-
if (!wasmSupported()) {
97+
if (!isWasmInit()) {
8298
throw new Error('WASM not supported, fall back to stdDev');
8399
}
84100

@@ -117,7 +133,7 @@ export const sortSeriesByName = (series: DataFrame[], direction: string) => {
117133
};
118134

119135
const initOutlierDetector = (series: DataFrame[]) => {
120-
if (!wasmSupported()) {
136+
if (!isWasmInit()) {
121137
return;
122138
}
123139

@@ -142,7 +158,7 @@ const initOutlierDetector = (series: DataFrame[]) => {
142158
let outliers: OutlierOutput | undefined = undefined;
143159

144160
export const calculateOutlierValue = (series: DataFrame[], data: DataFrame): number => {
145-
if (!wasmSupported()) {
161+
if (!isWasmInit()) {
146162
throw new Error('WASM not supported, fall back to stdDev');
147163
}
148164
if (!outliers) {

0 commit comments

Comments
 (0)