diff --git a/.betterer.results b/.betterer.results
index d305a5130af..7436903ef0d 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -5638,12 +5638,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
- "public/app/plugins/datasource/tempo/_importedDependencies/store.ts:5381": [
- [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
- [0, 0, 0, "Do not use any type assertions.", "2"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "3"]
- ],
"public/app/plugins/datasource/tempo/_importedDependencies/test/helpers/createFetchResponse.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
diff --git a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx
index cabc3e3c493..8a7003a576c 100644
--- a/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx
+++ b/packages/grafana-o11y-ds-frontend/src/TemporaryAlert.tsx
@@ -58,5 +58,17 @@ export const TemporaryAlert = (props: AlertProps) => {
}
}, [props.severity, props.text]);
- return <>{visible && }>;
+ return (
+ <>
+ {visible && (
+ setVisible(false)}
+ severity={props.severity}
+ title={props.text}
+ />
+ )}
+ >
+ );
};
diff --git a/public/app/plugins/datasource/tempo/NativeSearch/NativeSearch.tsx b/public/app/plugins/datasource/tempo/NativeSearch/NativeSearch.tsx
index ae78da4318f..1653cc04de7 100644
--- a/public/app/plugins/datasource/tempo/NativeSearch/NativeSearch.tsx
+++ b/public/app/plugins/datasource/tempo/NativeSearch/NativeSearch.tsx
@@ -2,12 +2,10 @@ import { css } from '@emotion/css';
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import { GrafanaTheme2, isValidGoDuration, SelectableValue, toOption } from '@grafana/data';
+import { TemporaryAlert } from '@grafana/o11y-ds-frontend';
import { FetchError, getTemplateSrv, isFetchError, TemplateSrv } from '@grafana/runtime';
import { InlineFieldRow, InlineField, Input, Alert, useStyles2, fuzzyMatch, Select } from '@grafana/ui';
-import { notifyApp } from '../_importedDependencies/actions/appNotification';
-import { createErrorNotification } from '../_importedDependencies/core/appNotification';
-import { dispatch } from '../_importedDependencies/store';
import { DEFAULT_LIMIT, TempoDatasource } from '../datasource';
import TempoLanguageProvider from '../language_provider';
import { TempoQuery } from '../types';
@@ -26,6 +24,7 @@ const durationPlaceholder = 'e.g. 1.2s, 100ms';
const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props) => {
const styles = useStyles2(getStyles);
+ const [alertText, setAlertText] = useState();
const languageProvider = useMemo(() => new TempoLanguageProvider(datasource), [datasource]);
const [serviceOptions, setServiceOptions] = useState>>();
const [spanOptions, setSpanOptions] = useState>>();
@@ -47,19 +46,21 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
try {
const options = await languageProvider.getOptionsV1(lpName);
const filteredOptions = options.filter((item) => (item.value ? fuzzyMatch(item.value, query).found : false));
+ setAlertText(undefined);
+ setError(null);
return filteredOptions;
} catch (error) {
if (isFetchError(error) && error?.status === 404) {
setError(error);
} else if (error instanceof Error) {
- dispatch(notifyApp(createErrorNotification('Error', error)));
+ setAlertText(`Error: ${error.message}`);
}
return [];
} finally {
setIsLoading((prevValue) => ({ ...prevValue, [name]: false }));
}
},
- [languageProvider]
+ [languageProvider, setAlertText]
);
useEffect(() => {
@@ -74,17 +75,19 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
spans.push(toOption(query.spanName));
}
setSpanOptions(spans);
+ setAlertText(undefined);
+ setError(null);
} catch (error) {
// Display message if Tempo is connected but search 404's
if (isFetchError(error) && error?.status === 404) {
setError(error);
} else if (error instanceof Error) {
- dispatch(notifyApp(createErrorNotification('Error', error)));
+ setAlertText(`Error: ${error.message}`);
}
}
};
fetchOptions();
- }, [languageProvider, loadOptions, query.serviceName, query.spanName]);
+ }, [languageProvider, loadOptions, query.serviceName, query.spanName, setAlertText]);
const onKeyDown = (keyEvent: React.KeyboardEvent) => {
if (keyEvent.key === 'Enter' && (keyEvent.shiftKey || keyEvent.ctrlKey)) {
@@ -255,6 +258,7 @@ const NativeSearch = ({ datasource, query, onChange, onBlur, onRunQuery }: Props
configure it in the datasource settings.
) : null}
+ {alertText && }
>
);
};
diff --git a/public/app/plugins/datasource/tempo/NativeSearch/TagsField/TagsField.tsx b/public/app/plugins/datasource/tempo/NativeSearch/TagsField/TagsField.tsx
index 3869c3fcb3c..9acc12b2899 100644
--- a/public/app/plugins/datasource/tempo/NativeSearch/TagsField/TagsField.tsx
+++ b/public/app/plugins/datasource/tempo/NativeSearch/TagsField/TagsField.tsx
@@ -1,12 +1,10 @@
import { css } from '@emotion/css';
-import React, { useEffect, useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
+import { TemporaryAlert } from '@grafana/o11y-ds-frontend';
import { CodeEditor, Monaco, monacoTypes, useTheme2 } from '@grafana/ui';
-import { notifyApp } from '../../_importedDependencies/actions/appNotification';
-import { createErrorNotification } from '../../_importedDependencies/core/appNotification';
-import { dispatch } from '../../_importedDependencies/store';
import { TempoDatasource } from '../../datasource';
import { CompletionProvider } from './autocomplete';
@@ -21,40 +19,44 @@ interface Props {
}
export function TagsField(props: Props) {
+ const [alertText, setAlertText] = useState();
const { onChange, onBlur, placeholder } = props;
- const setupAutocompleteFn = useAutocomplete(props.datasource);
+ const setupAutocompleteFn = useAutocomplete(props.datasource, setAlertText);
const theme = useTheme2();
const styles = getStyles(theme, placeholder);
return (
- {
- setupAutocompleteFn(editor, monaco);
- setupPlaceholder(editor, monaco, styles);
- setupAutoSize(editor);
- }}
- />
+ <>
+ {
+ setupAutocompleteFn(editor, monaco);
+ setupPlaceholder(editor, monaco, styles);
+ setupAutoSize(editor);
+ }}
+ />
+ {alertText && }
+ >
);
}
@@ -103,9 +105,10 @@ function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) {
/**
* Hook that returns function that will set up monaco autocomplete for the label selector
- * @param datasource
+ * @param datasource the Tempo datasource instance
+ * @param setAlertText setter for the alert text
*/
-function useAutocomplete(datasource: TempoDatasource) {
+function useAutocomplete(datasource: TempoDatasource, setAlertText: (text?: string) => void) {
// We need the provider ref so we can pass it the label/values data later. This is because we run the call for the
// values here but there is additional setup needed for the provider later on. We could run the getSeries() in the
// returned function but that is run after the monaco is mounted so would delay the request a bit when it does not
@@ -118,14 +121,15 @@ function useAutocomplete(datasource: TempoDatasource) {
const fetchTags = async () => {
try {
await datasource.languageProvider.start();
+ setAlertText(undefined);
} catch (error) {
if (error instanceof Error) {
- dispatch(notifyApp(createErrorNotification('Error', error)));
+ setAlertText(`Error: ${error.message}`);
}
}
};
fetchTags();
- }, [datasource]);
+ }, [datasource, setAlertText]);
const autocompleteDisposeFun = useRef<(() => void) | null>(null);
useEffect(() => {
diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx
index e81d0208cad..42a4935371f 100644
--- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx
+++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx
@@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event';
import React from 'react';
import { LanguageProvider } from '@grafana/data';
-import { FetchError } from '@grafana/runtime';
import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen';
import { TempoDatasource } from '../datasource';
@@ -290,9 +289,7 @@ const renderSearchField = (
datasource={datasource}
updateFilter={updateFilter}
filter={filter}
- setError={function (error: FetchError): void {
- throw error;
- }}
+ setError={() => {}}
tags={tags || []}
hideTag={hideTag}
query={'{}'}
diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx
index 68cc5a2676d..eaaeda32221 100644
--- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx
+++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.tsx
@@ -4,12 +4,10 @@ import React, { useState, useEffect, useMemo } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { SelectableValue } from '@grafana/data';
+import { TemporaryAlert } from '@grafana/o11y-ds-frontend';
import { FetchError, getTemplateSrv, isFetchError } from '@grafana/runtime';
import { Select, HorizontalGroup, useStyles2 } from '@grafana/ui';
-import { notifyApp } from '../_importedDependencies/actions/appNotification';
-import { createErrorNotification } from '../_importedDependencies/core/appNotification';
-import { dispatch } from '../_importedDependencies/store';
import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen';
import { TempoDatasource } from '../datasource';
import { operators as allOperators, stringOperators, numberOperators, keywordOperators } from '../traceql/traceql';
@@ -26,7 +24,8 @@ interface Props {
filter: TraceqlFilter;
datasource: TempoDatasource;
updateFilter: (f: TraceqlFilter) => void;
- setError: (error: FetchError) => void;
+ deleteFilter?: (f: TraceqlFilter) => void;
+ setError: (error: FetchError | null) => void;
isTagsLoading?: boolean;
tags: string[];
hideScope?: boolean;
@@ -51,6 +50,7 @@ const SearchField = ({
allowCustomValue = true,
}: Props) => {
const styles = useStyles2(getStyles);
+ const [alertText, setAlertText] = useState();
const scopedTag = useMemo(() => filterScopedTag(filter), [filter]);
// We automatically change the operator to the regex op when users select 2 or more values
// However, they expect this to be automatically rolled back to the previous operator once
@@ -60,13 +60,16 @@ const SearchField = ({
const updateOptions = async () => {
try {
- return filter.tag ? await datasource.languageProvider.getOptionsV2(scopedTag, query) : [];
+ const result = filter.tag ? await datasource.languageProvider.getOptionsV2(scopedTag, query) : [];
+ setAlertText(undefined);
+ setError(null);
+ return result;
} catch (error) {
// Display message if Tempo is connected but search 404's
if (isFetchError(error) && error?.status === 404) {
setError(error);
} else if (error instanceof Error) {
- dispatch(notifyApp(createErrorNotification('Error', error)));
+ setAlertText(`Error: ${error.message}`);
}
}
return [];
@@ -135,78 +138,85 @@ const SearchField = ({
};
return (
-
- {!hideScope && (
+ <>
+
+ {!hideScope && (
+
+ {!hideValue && (
+ {
+ if (Array.isArray(val)) {
+ updateFilter({
+ ...filter,
+ value: val.map((v) => v.value),
+ valueType: val[0]?.type || uniqueOptionType,
+ });
+ } else {
+ updateFilter({ ...filter, value: val?.value, valueType: val?.type || uniqueOptionType });
+ }
+ }}
+ placeholder="Select value"
+ isClearable={true}
+ aria-label={`select ${filter.id} value`}
+ allowCustomValue={allowCustomValue}
+ isMulti={isMulti}
+ allowCreateWhileLoading
+ />
+ )}
+
+ {alertText && }
+ >
);
};
diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx
index 6a81c3c2844..0edd42264bc 100644
--- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx
+++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx
@@ -2,8 +2,6 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
-import { FetchError } from '@grafana/runtime';
-
import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen';
import { TempoDatasource } from '../datasource';
import TempoLanguageProvider from '../language_provider';
@@ -124,9 +122,7 @@ describe('TagsInput', () => {
updateFilter={jest.fn}
deleteFilter={jest.fn}
filters={[filter]}
- setError={function (error: FetchError): void {
- throw error;
- }}
+ setError={() => {}}
staticTags={[]}
isTagsLoading={false}
query={''}
diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx
index eed0e73384f..6bd8c62afc2 100644
--- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx
+++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.tsx
@@ -34,7 +34,7 @@ interface Props {
deleteFilter: (f: TraceqlFilter) => void;
filters: TraceqlFilter[];
datasource: TempoDatasource;
- setError: (error: FetchError) => void;
+ setError: (error: FetchError | null) => void;
staticTags: Array;
isTagsLoading: boolean;
hideValues?: boolean;
diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx
index 1240317deaa..262582da68c 100644
--- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx
+++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx
@@ -137,8 +137,8 @@ describe('TraceQLSearch', () => {
expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(1); // filled in the default tag, so can remove values
await user.click(screen.getAllByLabelText(/Remove tag/)[0]);
- jest.advanceTimersByTime(1000);
await act(async () => {
+ jest.advanceTimersByTime(1000);
expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one
expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(0); // mot filled in the default tag, so no values to remove
});
diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx
index 43cb42ce2c4..548cc67773e 100644
--- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx
+++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.tsx
@@ -2,13 +2,11 @@ import { css } from '@emotion/css';
import React, { useCallback, useEffect, useState } from 'react';
import { CoreApp, GrafanaTheme2 } from '@grafana/data';
+import { TemporaryAlert } from '@grafana/o11y-ds-frontend';
import { config, FetchError, getTemplateSrv, reportInteraction } from '@grafana/runtime';
import { Alert, Button, HorizontalGroup, Select, useStyles2 } from '@grafana/ui';
-import { notifyApp } from '../_importedDependencies/actions/appNotification';
-import { createErrorNotification } from '../_importedDependencies/core/appNotification';
import { RawQuery } from '../_importedDependencies/datasources/prometheus/RawQuery';
-import { dispatch } from '../_importedDependencies/store';
import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen';
import { TempoDatasource } from '../datasource';
import { TempoQueryBuilderOptions } from '../traceql/TempoQueryBuilderOptions';
@@ -35,6 +33,7 @@ const hardCodedFilterIds = ['min-duration', 'max-duration', 'status'];
const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app }: Props) => {
const styles = useStyles2(getStyles);
+ const [alertText, setAlertText] = useState();
const [error, setError] = useState(null);
const [isTagsLoading, setIsTagsLoading] = useState(true);
@@ -73,14 +72,15 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app }: Pro
try {
await datasource.languageProvider.start();
setIsTagsLoading(false);
+ setAlertText(undefined);
} catch (error) {
if (error instanceof Error) {
- dispatch(notifyApp(createErrorNotification('Error', error)));
+ setAlertText(`Error: ${error.message}`);
}
}
};
fetchTags();
- }, [datasource]);
+ }, [datasource, setAlertText]);
useEffect(() => {
// Initialize state with configured static filters that already have a value from the config
@@ -250,6 +250,7 @@ const TraceQLSearch = ({ datasource, query, onChange, onClearResults, app }: Pro
configure it in the datasource settings.
) : null}
+ {alertText && }
>
);
};
diff --git a/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx b/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx
index 827959621bd..6b770fb5c19 100644
--- a/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx
+++ b/public/app/plugins/datasource/tempo/ServiceGraphSection.tsx
@@ -6,8 +6,8 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Alert, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui';
import { AdHocFilter } from './_importedDependencies/components/AdHocFilter/AdHocFilter';
+import { AdHocVariableFilter } from './_importedDependencies/components/AdHocFilter/types';
import { PrometheusDatasource } from './_importedDependencies/datasources/prometheus/types';
-import { AdHocVariableFilter } from './_importedDependencies/types';
import { TempoQuery } from './types';
import { getDS } from './utils';
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/actions/appNotification.ts b/public/app/plugins/datasource/tempo/_importedDependencies/actions/appNotification.ts
deleted file mode 100644
index 1ec3138d0f0..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/actions/appNotification.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
-
-import { AppNotification, AppNotificationSeverity, AppNotificationsState } from './types/appNotifications';
-
-const MAX_STORED_NOTIFICATIONS = 25;
-export const STORAGE_KEY = 'notifications';
-export const NEW_NOTIFS_KEY = `${STORAGE_KEY}/lastRead`;
-type StoredNotification = Omit;
-
-export const initialState: AppNotificationsState = {
- byId: deserializeNotifications(),
- lastRead: Number.parseInt(window.localStorage.getItem(NEW_NOTIFS_KEY) ?? `${Date.now()}`, 10),
-};
-
-/**
- * Reducer and action to show toast notifications of various types (success, warnings, errors etc). Use to show
- * transient info to user, like errors that cannot be otherwise handled or success after an action.
- *
- * Use factory functions in core/copy/appNotifications to create the payload.
- */
-const appNotificationsSlice = createSlice({
- name: 'appNotifications',
- initialState,
- reducers: {
- notifyApp: (state, { payload: newAlert }: PayloadAction) => {
- if (Object.values(state.byId).some((alert) => isSimilar(newAlert, alert) && alert.showing)) {
- return;
- }
-
- state.byId[newAlert.id] = newAlert;
- serializeNotifications(state.byId);
- },
- hideAppNotification: (state, { payload: alertId }: PayloadAction) => {
- if (!(alertId in state.byId)) {
- return;
- }
-
- state.byId[alertId].showing = false;
- serializeNotifications(state.byId);
- },
- clearNotification: (state, { payload: alertId }: PayloadAction) => {
- delete state.byId[alertId];
- serializeNotifications(state.byId);
- },
- clearAllNotifications: (state) => {
- state.byId = {};
- serializeNotifications(state.byId);
- },
- readAllNotifications: (state, { payload: timestamp }: PayloadAction) => {
- state.lastRead = timestamp;
- },
- },
-});
-
-export const { notifyApp, hideAppNotification, clearNotification, clearAllNotifications, readAllNotifications } =
- appNotificationsSlice.actions;
-
-export const appNotificationsReducer = appNotificationsSlice.reducer;
-
-// Selectors
-
-export const selectLastReadTimestamp = (state: AppNotificationsState) => state.lastRead;
-export const selectById = (state: AppNotificationsState) => state.byId;
-export const selectAll = createSelector(selectById, (byId) =>
- Object.values(byId).sort((a, b) => b.timestamp - a.timestamp)
-);
-export const selectWarningsAndErrors = createSelector(selectAll, (all) => all.filter(isAtLeastWarning));
-export const selectVisible = createSelector(selectById, (byId) => Object.values(byId).filter((n) => n.showing));
-
-// Helper functions
-
-function isSimilar(a: AppNotification, b: AppNotification): boolean {
- return a.icon === b.icon && a.severity === b.severity && a.text === b.text && a.title === b.title;
-}
-
-function isAtLeastWarning(notif: AppNotification) {
- return notif.severity === AppNotificationSeverity.Warning || notif.severity === AppNotificationSeverity.Error;
-}
-
-function isStoredNotification(obj: unknown): obj is StoredNotification {
- return typeof obj === 'object' && obj !== null && 'id' in obj && 'icon' in obj && 'title' in obj && 'text' in obj;
-}
-
-// (De)serialization
-
-export function deserializeNotifications(): Record {
- const storedNotifsRaw = window.localStorage.getItem(STORAGE_KEY);
- if (!storedNotifsRaw) {
- return {};
- }
-
- const parsed = JSON.parse(storedNotifsRaw);
- if (!Object.values(parsed).every((v) => isStoredNotification(v))) {
- return {};
- }
-
- return parsed;
-}
-
-function serializeNotifications(notifs: Record) {
- const reducedNotifs = Object.values(notifs)
- .filter(isAtLeastWarning)
- .sort((a, b) => b.timestamp - a.timestamp)
- .slice(0, MAX_STORED_NOTIFICATIONS)
- .reduce>((prev, cur) => {
- prev[cur.id] = {
- id: cur.id,
- severity: cur.severity,
- icon: cur.icon,
- title: cur.title,
- text: cur.text,
- traceId: cur.traceId,
- timestamp: cur.timestamp,
- // we don't care about still showing toasts after refreshing
- // https://github.com/grafana/grafana/issues/71932
- showing: false,
- };
-
- return prev;
- }, {});
-
- try {
- window.localStorage.setItem(STORAGE_KEY, JSON.stringify(reducedNotifs));
- } catch (err) {
- console.error('Unable to persist notifications to local storage');
- console.error(err);
- }
-}
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/actions/index.ts b/public/app/plugins/datasource/tempo/_importedDependencies/actions/index.ts
deleted file mode 100644
index 35a16cfc24f..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/actions/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { appNotificationsReducer as appNotifications } from './appNotification';
-
-export default {
- appNotifications,
-};
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/actions/types/appNotifications.ts b/public/app/plugins/datasource/tempo/_importedDependencies/actions/types/appNotifications.ts
deleted file mode 100644
index a3a7ea2aed7..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/actions/types/appNotifications.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-export interface AppNotification {
- id: string;
- severity: AppNotificationSeverity;
- icon: string;
- title: string;
- text: string;
- traceId?: string;
- component?: React.ReactElement;
- showing: boolean;
- timestamp: number;
-}
-
-export enum AppNotificationSeverity {
- Success = 'success',
- Warning = 'warning',
- Error = 'error',
- Info = 'info',
-}
-
-export enum AppNotificationTimeout {
- Success = 3000,
- Warning = 5000,
- Error = 7000,
-}
-
-export const timeoutMap = {
- [AppNotificationSeverity.Success]: AppNotificationTimeout.Success,
- [AppNotificationSeverity.Warning]: AppNotificationTimeout.Warning,
- [AppNotificationSeverity.Error]: AppNotificationTimeout.Error,
- [AppNotificationSeverity.Info]: AppNotificationTimeout.Success,
-};
-
-export interface AppNotificationsState {
- byId: Record;
- lastRead: number;
-}
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/actions/types/index.ts b/public/app/plugins/datasource/tempo/_importedDependencies/actions/types/index.ts
deleted file mode 100644
index 916b3970c84..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/actions/types/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './appNotifications';
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/types/index.ts b/public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/types.ts
similarity index 100%
rename from public/app/plugins/datasource/tempo/_importedDependencies/types/index.ts
rename to public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/types.ts
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/core/appNotification.ts b/public/app/plugins/datasource/tempo/_importedDependencies/core/appNotification.ts
deleted file mode 100644
index 53c6d3b8e63..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/core/appNotification.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { v4 as uuidv4 } from 'uuid';
-
-import { AppNotification, AppNotificationSeverity } from '../actions/types';
-
-import { getMessageFromError } from './errors';
-
-const defaultSuccessNotification = {
- title: '',
- text: '',
- severity: AppNotificationSeverity.Success,
- icon: 'check',
-};
-
-const defaultErrorNotification = {
- title: '',
- text: '',
- severity: AppNotificationSeverity.Error,
- icon: 'exclamation-triangle',
-};
-
-export const createSuccessNotification = (title: string, text = '', traceId?: string): AppNotification => ({
- ...defaultSuccessNotification,
- title,
- text,
- id: uuidv4(),
- timestamp: Date.now(),
- showing: true,
-});
-
-export const createErrorNotification = (
- title: string,
- text: string | Error = '',
- traceId?: string,
- component?: React.ReactElement
-): AppNotification => {
- return {
- ...defaultErrorNotification,
- text: getMessageFromError(text),
- title,
- id: uuidv4(),
- traceId,
- component,
- timestamp: Date.now(),
- showing: true,
- };
-};
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/core/errors.ts b/public/app/plugins/datasource/tempo/_importedDependencies/core/errors.ts
deleted file mode 100644
index 42992b6975b..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/core/errors.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { isFetchError } from '@grafana/runtime';
-
-export function getMessageFromError(err: unknown): string {
- if (typeof err === 'string') {
- return err;
- }
-
- if (err) {
- if (err instanceof Error) {
- return err.message;
- } else if (isFetchError(err)) {
- if (err.data && err.data.message) {
- return err.data.message;
- } else if (err.statusText) {
- return err.statusText;
- }
- }
- }
-
- return JSON.stringify(err);
-}
diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/store.ts b/public/app/plugins/datasource/tempo/_importedDependencies/store.ts
deleted file mode 100644
index b41b59309af..00000000000
--- a/public/app/plugins/datasource/tempo/_importedDependencies/store.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Store } from 'redux';
-
-export let store: Store;
-export const initialKeyedVariablesState: any = { keys: {} };
-
-type StoreState = ReturnType>;
-
-export function setStore(newStore: Store) {
- store = newStore;
-}
-
-export function getState(): StoreState {
- if (!store || !store.getState) {
- return { templating: { ...initialKeyedVariablesState, lastKey: 'key' } } as StoreState; // used by tests
- }
-
- return store.getState();
-}
-
-// This was `any` before
-export function dispatch(action: any) {
- if (!store || !store.getState) {
- return;
- }
-
- return store.dispatch(action);
-}
diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json
index 80adea389e4..352dd7a5ac0 100644
--- a/public/app/plugins/datasource/tempo/package.json
+++ b/public/app/plugins/datasource/tempo/package.json
@@ -20,7 +20,6 @@
"@opentelemetry/api": "1.7.0",
"@opentelemetry/exporter-collector": "0.25.0",
"@opentelemetry/semantic-conventions": "1.21.0",
- "@reduxjs/toolkit": "1.9.5",
"buffer": "6.0.3",
"events": "3.3.0",
"i18next": "^23.0.0",
@@ -31,7 +30,6 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-use": "17.5.0",
- "redux": "4.2.1",
"rxjs": "7.8.1",
"semver": "7.6.0",
"stream-browserify": "3.0.0",
diff --git a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx
index ff5e252393f..3f40bc006ac 100644
--- a/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx
+++ b/public/app/plugins/datasource/tempo/traceql/TraceQLEditor.tsx
@@ -23,7 +23,7 @@ interface Props {
}
export function TraceQLEditor(props: Props) {
- const [alertText, setAlertText] = useState('');
+ const [alertText, setAlertText] = useState();
const { query, onChange, onRunQuery, placeholder } = props;
const setupAutocompleteFn = useAutocomplete(props.datasource, setAlertText);
@@ -116,7 +116,7 @@ export function TraceQLEditor(props: Props) {
});
}}
/>
- {alertText && }
+ {alertText && }
>
);
}
@@ -193,23 +193,23 @@ function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) {
* @param datasource the Tempo datasource instance
* @param setAlertText setter for alert's text
*/
-function useAutocomplete(datasource: TempoDatasource, setAlertText: (text: string) => void) {
+function useAutocomplete(datasource: TempoDatasource, setAlertText: (text?: string) => void) {
// We need the provider ref so we can pass it the label/values data later. This is because we run the call for the
// values here but there is additional setup needed for the provider later on. We could run the getSeries() in the
// returned function but that is run after the monaco is mounted so would delay the request a bit when it does not
// need to.
const providerRef = useRef(
- new CompletionProvider({ languageProvider: datasource.languageProvider })
+ new CompletionProvider({ languageProvider: datasource.languageProvider, setAlertText })
);
useEffect(() => {
const fetchTags = async () => {
try {
await datasource.languageProvider.start();
+ setAlertText(undefined);
} catch (error) {
if (error instanceof Error) {
- console.error(error);
- setAlertText(error.message);
+ setAlertText(`Error: ${error.message}`);
}
}
};
diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts
index a19e9bf92cb..fafc6ccf361 100644
--- a/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts
+++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.test.ts
@@ -395,7 +395,7 @@ function setup(value: string, offset: number, tagsV1?: string[], tagsV2?: Scope[
} else if (tagsV2) {
lp.setV2Tags(tagsV2);
}
- const provider = new CompletionProvider({ languageProvider: lp });
+ const provider = new CompletionProvider({ languageProvider: lp, setAlertText: () => {} });
const model = makeModel(value, offset);
provider.monaco = {
Range: {
diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts
index 502ecbc2838..15b81656bbf 100644
--- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts
+++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts
@@ -4,9 +4,6 @@ import { SelectableValue } from '@grafana/data';
import { isFetchError } from '@grafana/runtime';
import type { Monaco, monacoTypes } from '@grafana/ui';
-import { notifyApp } from '../_importedDependencies/actions/appNotification';
-import { createErrorNotification } from '../_importedDependencies/core/appNotification';
-import { dispatch } from '../_importedDependencies/store';
import TempoLanguageProvider from '../language_provider';
import { getSituation, Situation } from './situation';
@@ -14,6 +11,7 @@ import { intrinsics, scopes } from './traceql';
interface Props {
languageProvider: TempoLanguageProvider;
+ setAlertText: (text?: string) => void;
}
type MinimalCompletionItem = {
@@ -33,9 +31,11 @@ type MinimalCompletionItem = {
export class CompletionProvider implements monacoTypes.languages.CompletionItemProvider {
languageProvider: TempoLanguageProvider;
registerInteractionCommandId: string | null;
+ setAlertText: (text?: string) => void;
constructor(props: Props) {
this.languageProvider = props.languageProvider;
+ this.setAlertText = props.setAlertText;
this.registerInteractionCommandId = null;
}
@@ -243,7 +243,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP
const { range, offset } = getRangeAndOffset(this.monaco, model, position);
const situation = getSituation(model.getValue(), offset);
- const completionItems = situation != null ? this.getCompletions(situation) : Promise.resolve([]);
+ const completionItems = situation != null ? this.getCompletions(situation, this.setAlertText) : Promise.resolve([]);
return completionItems.then((items) => {
// monaco by-default alphabetically orders the items.
@@ -298,7 +298,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP
* @param situation
* @private
*/
- private async getCompletions(situation: Situation): Promise {
+ private async getCompletions(situation: Situation, setAlertText: (text?: string) => void): Promise {
switch (situation.type) {
// This should only happen for cases that we do not support yet
case 'UNKNOWN': {
@@ -370,11 +370,12 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP
let tagValues;
try {
tagValues = await this.getTagValues(situation.tagName, situation.query);
+ setAlertText(undefined);
} catch (error) {
if (isFetchError(error)) {
- dispatch(notifyApp(createErrorNotification(error.data.error, new Error(error.data.message))));
+ setAlertText(error.data.error);
} else if (error instanceof Error) {
- dispatch(notifyApp(createErrorNotification('Error', error)));
+ setAlertText(`Error: ${error.message}`);
}
}
diff --git a/yarn.lock b/yarn.lock
index 38d89210473..4047573b287 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3445,7 +3445,6 @@ __metadata:
"@opentelemetry/api": "npm:1.7.0"
"@opentelemetry/exporter-collector": "npm:0.25.0"
"@opentelemetry/semantic-conventions": "npm:1.21.0"
- "@reduxjs/toolkit": "npm:1.9.5"
"@testing-library/jest-dom": "npm:6.4.2"
"@testing-library/react": "npm:14.2.1"
"@testing-library/user-event": "npm:14.5.2"
@@ -3469,7 +3468,6 @@ __metadata:
react-dom: "npm:18.2.0"
react-select-event: "npm:5.5.1"
react-use: "npm:17.5.0"
- redux: "npm:4.2.1"
rxjs: "npm:7.8.1"
semver: "npm:7.6.0"
stream-browserify: "npm:3.0.0"