From 80bc6ad53539450b6426d8fffd07bb69c007dee9 Mon Sep 17 00:00:00 2001 From: Gurinder Singh Date: Wed, 18 Oct 2023 00:34:04 -0400 Subject: [PATCH 1/4] typing error fix and modifications --- package.json | 7 +- .../src/dataframe/ArrayDataFrame.ts | 69 ++++ .../grafana-data/src/themes/fnCreateColors.ts | 2 + .../src/types/featureToggles.gen.ts | 2 + .../RelativeTimeRangePicker.tsx | 1 - .../src/components/Dropdown/Dropdown.tsx | 2 + .../grafana-ui/src/components/Icon/Icon.tsx | 3 +- .../src/components/Menu/MenuItem.tsx | 37 +- .../src/components/Menu/SubMenu.tsx | 2 + .../src/components/Select/SelectBase.tsx | 4 +- .../ToolbarButton/ToolbarButton.tsx | 1 - .../grafana-ui/src/themes/ThemeContext.tsx | 2 + .../components/Page/SectionNavItem.test.tsx | 25 -- .../TraceToMetrics/TraceToMetricsSettings.tsx | 4 +- .../app/core/navigation/GrafanaRoute.test.tsx | 1 - public/app/core/utils/ConfigProvider.tsx | 2 +- .../features/alerting/AlertRuleList.test.tsx | 3 - .../rule-editor/GrafanaEvaluationBehavior.tsx | 1 - .../AlertType.test.tsx | 77 ----- .../alerting/unified/state/actions.ts | 5 +- .../alerting/unified/utils/alertmanager.ts | 1 - .../alerting/unified/utils/rule-form.ts | 2 - .../features/canvas/elements/metricValue.tsx | 1 - .../app/features/connections/Connections.tsx | 1 - .../connections/pages/DataSourcesListPage.tsx | 1 - .../containers/DashboardPage.test.tsx | 327 ------------------ .../dashboard/containers/DashboardPage.tsx | 13 +- .../features/dashboard/dashgrid/liveTimer.ts | 4 - .../dashboard/state/DashboardMigrator.test.ts | 51 ++- .../features/explore/EmptyStateWrapper.tsx | 2 +- .../app/features/explore/LogsMetaRow.test.tsx | 186 ---------- .../app/features/explore/LogsSample.test.tsx | 141 -------- .../app/features/explore/LogsSamplePanel.tsx | 130 ------- .../explore/LogsVolumePanelList.test.tsx | 70 ---- .../features/explore/LogsVolumePanelList.tsx | 139 -------- .../explore/RawPrometheusContainer.test.tsx | 84 ----- .../explore/RawPrometheusContainer.tsx | 177 ---------- .../NewTracePageHeader.test.tsx | 162 ++++++++- .../TracePageHeader/NewTracePageHeader.tsx | 269 +++++++++----- .../features/invites/SignupInvited.test.tsx | 2 - .../app/features/org/OrgDetailsPage.test.tsx | 3 - .../playlist/PlaylistEditPage.test.tsx | 1 - .../playlist/PlaylistNewPage.test.tsx | 1 - .../features/playlist/PlaylistPage.test.tsx | 2 - .../plugins/systemjsPlugins/pluginCDN.test.ts | 4 +- .../profile/ChangePasswordPage.test.tsx | 2 - public/app/features/profile/UserSessions.tsx | 1 + .../features/scenes/builders/panelBuilders.ts | 6 +- .../ServiceAccountCreatePage.test.tsx | 1 - .../ServiceAccountPage.test.tsx | 3 - .../ServiceAccountsListPage.test.tsx | 3 - public/app/features/teams/CreateTeam.test.tsx | 2 - public/app/features/teams/TeamList.test.tsx | 2 - .../fn-dashboard-page/render-fn-dashboard.tsx | 2 - .../CrossAccountLogsQueryField.test.tsx | 190 ---------- .../components/CrossAccountLogsQueryField.tsx | 179 ---------- .../components/LogGroupSelection.test.tsx | 86 ----- .../components/LogGroupSelection.tsx | 64 ---- .../plugins/datasource/cloudwatch/hooks.ts | 1 - .../fifemon-graphql-datasource/DataSource.ts | 15 +- .../QueryEditor.tsx | 2 +- .../builder/abstract/Code.tsx | 3 +- .../builder/abstract/Table.tsx | 2 +- .../app/plugins/panel/canvas/Connections.tsx | 1 + .../editor/layer/TreeNavigationEditor.tsx | 1 + .../FlameGraph/FlameGraphMetadata.test.ts | 85 ----- public/microfrontends/fn_dashboard/index.html | 4 +- yarn.lock | 44 ++- 68 files changed, 520 insertions(+), 2203 deletions(-) delete mode 100644 public/app/core/components/Page/SectionNavItem.test.tsx delete mode 100644 public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/AlertType.test.tsx delete mode 100644 public/app/features/dashboard/containers/DashboardPage.test.tsx delete mode 100644 public/app/features/explore/LogsMetaRow.test.tsx delete mode 100644 public/app/features/explore/LogsSample.test.tsx delete mode 100644 public/app/features/explore/LogsSamplePanel.tsx delete mode 100644 public/app/features/explore/LogsVolumePanelList.test.tsx delete mode 100644 public/app/features/explore/LogsVolumePanelList.tsx delete mode 100644 public/app/features/explore/RawPrometheusContainer.test.tsx delete mode 100644 public/app/features/explore/RawPrometheusContainer.tsx delete mode 100644 public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.test.tsx delete mode 100644 public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.tsx delete mode 100644 public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.test.tsx delete mode 100644 public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.tsx delete mode 100644 public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.test.ts diff --git a/package.json b/package.json index e3b4f71589f76..2e5b974090aa1 100644 --- a/package.json +++ b/package.json @@ -147,8 +147,9 @@ "@types/papaparse": "5.3.7", "@types/pluralize": "^0.0.29", "@types/prismjs": "1.26.0", - "@types/react": "18.2.15", + "@types/react": "^18.2.28", "@types/react-beautiful-dnd": "13.1.3", + "@types/react-datepicker": "^4.19.0", "@types/react-dom": "18.2.7", "@types/react-grid-layout": "1.3.2", "@types/react-highlight-words": "0.16.4", @@ -275,7 +276,7 @@ "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "^7.4.0", "@leeoniya/ufuzzy": "1.0.8", - "@lezer/common": "1.0.2", + "@lezer/common": "^1.1.0", "@lezer/highlight": "1.1.3", "@lezer/lr": "1.3.3", "@locker/near-membrane-dom": "^0.12.15", @@ -287,7 +288,7 @@ "@opentelemetry/exporter-collector": "0.25.0", "@opentelemetry/semantic-conventions": "1.15.0", "@popperjs/core": "2.11.6", - "@prometheus-io/lezer-promql": "^0.37.0-rc.1", + "@prometheus-io/lezer-promql": "0.37.0-rc.1", "@pyroscope/flamegraph": "^0.35.5", "@react-aria/button": "3.8.0", "@react-aria/dialog": "3.5.3", diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts index e69de29bb2d1d..ae8b821175ef2 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts @@ -0,0 +1,69 @@ +import { QueryResultMeta } from '../types'; +import { Field, FieldType, DataFrame, TIME_SERIES_VALUE_FIELD_NAME } from '../types/dataFrame'; + +import { guessFieldTypeForField } from './processDataFrame'; + +/** + * The ArrayDataFrame takes an array of objects and presents it as a DataFrame + * + * @deprecated use arrayToDataFrame + */ +export class ArrayDataFrame implements DataFrame { + fields: Field[] = []; + length = 0; + name?: string; + refId?: string; + meta?: QueryResultMeta; + + constructor(source: T[], names?: string[]) { + return arrayToDataFrame(source, names) as ArrayDataFrame; // returns a standard DataFrame + } +} + +/** + * arrayToDataFrame will convert any array into a DataFrame + * + * @public + */ +export function arrayToDataFrame(source: any[], names?: string[]): DataFrame { + const df: DataFrame = { + fields: [], + length: source.length, + }; + if (!source?.length) { + return df; + } + + if (names) { + for (const name of names) { + df.fields.push( + makeFieldFromValues( + name, + source.map((v) => v[name]) + ) + ); + } + return df; + } + + const first = source.find((v) => v != null); // first not null|undefined + if (first != null) { + if (typeof first === 'object') { + df.fields = Object.keys(first).map((name) => { + return makeFieldFromValues( + name, + source.map((v) => v[name]) + ); + }); + } else { + df.fields.push(makeFieldFromValues(TIME_SERIES_VALUE_FIELD_NAME, source)); + } + } + return df; +} + +function makeFieldFromValues(name: string, values: unknown[]): Field { + const f = { name, config: {}, values, type: FieldType.other }; + f.type = guessFieldTypeForField(f) ?? FieldType.other; + return f; +} diff --git a/packages/grafana-data/src/themes/fnCreateColors.ts b/packages/grafana-data/src/themes/fnCreateColors.ts index b5abc999ee486..f7705578cdf61 100644 --- a/packages/grafana-data/src/themes/fnCreateColors.ts +++ b/packages/grafana-data/src/themes/fnCreateColors.ts @@ -75,6 +75,7 @@ class FnDarkColors implements ThemeColorsBase> { disabledText: this.text.disabled, disabledBackground: `rgba(${this.whiteBase}, 0.04)`, disabledOpacity: 0.38, + selectedBorder: palette.orangeDarkMain, }; gradients = { @@ -155,6 +156,7 @@ class FnLightColors implements ThemeColorsBase> { disabledBackground: `rgba(${this.blackBase}, 0.04)`, disabledText: this.text.disabled, disabledOpacity: 0.38, + selectedBorder: palette.orangeLightMain, }; gradients = { diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index bb0df3399fe28..6d0fe537e43ca 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -57,6 +57,7 @@ export interface FeatureToggles { accessTokenExpirationCheck?: boolean; emptyDashboardPage?: boolean; disablePrometheusExemplarSampling?: boolean; + datasourceOnboarding?: boolean; alertingBacktesting?: boolean; editPanelCSVDragAndDrop?: boolean; alertingNoNormalState?: boolean; @@ -72,6 +73,7 @@ export interface FeatureToggles { clientTokenRotation?: boolean; prometheusDataplane?: boolean; lokiMetricDataplane?: boolean; + newTraceView?: boolean; lokiLogsDataplane?: boolean; dataplaneFrontendFallback?: boolean; disableSSEDataplane?: boolean; diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index ccdf1568e25c2..ec797abbb770b 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -14,7 +14,6 @@ import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar'; import { Field } from '../../Forms/Field'; import { Icon } from '../../Icon/Icon'; import { getInputStyles, Input } from '../../Input/Input'; -import { Portal } from '../../Portal/Portal'; import { Tooltip } from '../../Tooltip/Tooltip'; import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle'; import { TimeRangeList } from '../TimeRangePicker/TimeRangeList'; diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx index 02ba88da787ec..cd811cae1322c 100644 --- a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx +++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx @@ -51,6 +51,7 @@ export const Dropdown = React.memo(({ children, overlay, placement, offset, onVi return ( <> + {/* @ts-ignore */} {React.cloneElement(typeof children === 'function' ? children(visible) : children, { ref: setTriggerRef, })} @@ -71,6 +72,7 @@ export const Dropdown = React.memo(({ children, overlay, placement, offset, onVi timeout={{ appear: animationDuration, exit: 0, enter: 0 }} classNames={animationStyles} > + {/* @ts-ignore */}
{ReactUtils.renderOrCallToRender(overlay, {})}
diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index 48f75cbb11a8f..c881e284d9840 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -37,7 +37,7 @@ const getIconStyles = (theme: GrafanaTheme2) => { }; export const Icon = React.forwardRef( - ({ size = 'md', type = 'default', name, className, style, title = '', ...divElementProps }, ref) => { + ({ size = 'md', type = 'default', name, className, style, title = '', ...divElementProps }, ref): JSX.Element => { const styles = useStyles2(getIconStyles); /* Temporary solution to display also font awesome icons */ @@ -65,6 +65,7 @@ export const Icon = React.forwardRef( return (
+ {/* @ts-ignore */} <> + {/* @ts-ignore */} {icon && } {label}
- {hasShortcut && ( -
- - {shortcut} -
- )} - {hasSubMenu && ( - - )} + { + hasShortcut && ( +
+ + {shortcut} +
+ ) + } + { + hasSubMenu && ( + + ) + }
diff --git a/packages/grafana-ui/src/components/Menu/SubMenu.tsx b/packages/grafana-ui/src/components/Menu/SubMenu.tsx index a3db8c0723341..398d34c1cc1f2 100644 --- a/packages/grafana-ui/src/components/Menu/SubMenu.tsx +++ b/packages/grafana-ui/src/components/Menu/SubMenu.tsx @@ -50,6 +50,7 @@ export const SubMenu = React.memo( return ( <>
+ {/* @ts-ignore */}
{isOpen && ( @@ -60,6 +61,7 @@ export const SubMenu = React.memo( style={customStyle} >
+ {/* @ts-ignore */} {items}
diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index c47d9cf3c5ce9..33c05b1cc5ee4 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -125,7 +125,7 @@ export function SelectBase({ menuPlacement = 'auto', menuPosition, menuShouldPortal = true, - noOptionsMessage = t('grafana-ui.select.no-options-label', 'No options found'), + noOptionsMessage = t('grafana-ui.select.no-options-label', 'No options found') as string, onBlur, onChange, onCloseMenu, @@ -138,7 +138,7 @@ export function SelectBase({ onFocus, openMenuOnFocus = false, options = [], - placeholder = t('grafana-ui.select.placeholder', 'Choose'), + placeholder = t('grafana-ui.select.placeholder', 'Choose') as string, prefix, renderControl, showAllSelectedWhenOpen = true, diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index f8d096769ddf3..5e44c91e7bfd5 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -1,5 +1,4 @@ import { cx, css } from '@emotion/css'; -import { isString } from 'lodash'; import React, { forwardRef, ButtonHTMLAttributes, ReactNode } from 'react'; import { GrafanaTheme2, IconName, isIconName } from '@grafana/data'; diff --git a/packages/grafana-ui/src/themes/ThemeContext.tsx b/packages/grafana-ui/src/themes/ThemeContext.tsx index 03a6a1483a410..7d5d35638b09c 100644 --- a/packages/grafana-ui/src/themes/ThemeContext.tsx +++ b/packages/grafana-ui/src/themes/ThemeContext.tsx @@ -40,6 +40,7 @@ export const withTheme =

(Component: Rea }; WithTheme.displayName = `WithTheme(${Component.displayName})`; + // @ts-ignore hoistNonReactStatics(WithTheme, Component); type Hoisted = typeof WithTheme & S; return WithTheme as Hoisted; @@ -60,6 +61,7 @@ export const withTheme2 =

(Component: R }; WithTheme.displayName = `WithTheme(${Component.displayName})`; + // @ts-ignore hoistNonReactStatics(WithTheme, Component); type Hoisted = typeof WithTheme & S; return WithTheme as Hoisted; diff --git a/public/app/core/components/Page/SectionNavItem.test.tsx b/public/app/core/components/Page/SectionNavItem.test.tsx deleted file mode 100644 index 90d92e7546217..0000000000000 --- a/public/app/core/components/Page/SectionNavItem.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { NavModelItem } from '@grafana/data'; - -import { SectionNavItem } from './SectionNavItem'; - -describe('SectionNavItem', () => { - it('should only show the img for a section root if both img and icon are present', () => { - const item: NavModelItem = { - text: 'Test', - icon: 'k6', - img: 'img', - children: [ - { - text: 'Child', - }, - ], - }; - - render(); - expect(screen.getByTestId('section-image')).toBeInTheDocument(); - expect(screen.queryByTestId('section-icon')).not.toBeInTheDocument(); - }); -}); diff --git a/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx b/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx index a9595c984d0c2..94fadc46590cc 100644 --- a/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx +++ b/public/app/core/components/TraceToMetrics/TraceToMetricsSettings.tsx @@ -82,7 +82,7 @@ export function TraceToMetricsSettings({ options, onOptionsChange }: Props) { { updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToMetrics', { @@ -97,7 +97,7 @@ export function TraceToMetricsSettings({ options, onOptionsChange }: Props) { { updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToMetrics', { diff --git a/public/app/core/navigation/GrafanaRoute.test.tsx b/public/app/core/navigation/GrafanaRoute.test.tsx index 90804bc94c7ea..46c99b2413974 100644 --- a/public/app/core/navigation/GrafanaRoute.test.tsx +++ b/public/app/core/navigation/GrafanaRoute.test.tsx @@ -12,7 +12,6 @@ import { GrafanaRoute, Props } from './GrafanaRoute'; import { GrafanaRouteComponentProps } from './types'; function setup(overrides: Partial) { - const store = configureStore(); const props: Props = { location: { search: '?query=hello&test=asd' } as Location, history: {} as History, diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index a65f020a86fc4..bed3e2fedaef2 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -36,6 +36,6 @@ export const ThemeProvider = ({ children, value }: { children: React.ReactNode; export const provideTheme = (component: React.ComponentType, theme: GrafanaTheme2) => { return function ThemeProviderWrapper(props: any) { - return ; + return {React.createElement(component, { ...props })}; }; }; diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index e62a4bb11c89b..df8f7c795de70 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -1,7 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { Provider } from 'react-redux'; import { openMenu } from 'react-select-event'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; import { TestProvider } from 'test/helpers/TestProvider'; @@ -10,7 +9,6 @@ import { locationService } from '@grafana/runtime'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import appEvents from '../../core/app_events'; -import { configureStore } from '../../store/configureStore'; import { ShowModalReactEvent } from '../../types/events'; import { AlertHowToModal } from './AlertHowToModal'; @@ -32,7 +30,6 @@ const defaultProps: Props = { }; const setup = (propOverrides?: object) => { - const store = configureStore(); const props: Props = { ...defaultProps, ...propOverrides, diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx index 7ea077d755a7f..0d40e3de5b3b5 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx @@ -156,7 +156,6 @@ function FolderGroupAndEvaluationInterval({ function ForInput({ evaluateEvery }: { evaluateEvery: string }) { const styles = useStyles2(getStyles); const { - watch, register, formState: { errors }, } = useFormContext(); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/AlertType.test.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/AlertType.test.tsx deleted file mode 100644 index dd1b173c07118..0000000000000 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/AlertType.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { render } from '@testing-library/react'; -import React from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; -import { Provider } from 'react-redux'; -import { byText } from 'testing-library-selector'; - -import { contextSrv } from 'app/core/services/context_srv'; -import { configureStore } from 'app/store/configureStore'; -import { AccessControlAction } from 'app/types'; - -import { AlertType } from './AlertType'; - -const ui = { - ruleTypePicker: { - grafanaManagedButton: byText('Grafana managed alert'), - mimirOrLokiButton: byText('Mimir or Loki alert'), - mimirOrLokiRecordingButton: byText('Mimir or Loki recording rule'), - }, -}; - -const FormProviderWrapper = ({ children }: React.PropsWithChildren<{}>) => { - const methods = useForm({}); - return {children}; -}; - -function renderAlertTypeStep() { - const store = configureStore(); - - render( - - - , - { wrapper: FormProviderWrapper } - ); -} - -describe('RuleTypePicker', () => { - describe('RBAC', () => { - it('Should display grafana, mimir alert and mimir recording buttons when user has rule create and write permissions', async () => { - jest.spyOn(contextSrv, 'hasPermission').mockImplementation((action) => { - return [AccessControlAction.AlertingRuleCreate, AccessControlAction.AlertingRuleExternalWrite].includes( - action as AccessControlAction - ); - }); - - renderAlertTypeStep(); - - expect(ui.ruleTypePicker.grafanaManagedButton.get()).toBeInTheDocument(); - expect(ui.ruleTypePicker.mimirOrLokiButton.get()).toBeInTheDocument(); - expect(ui.ruleTypePicker.mimirOrLokiRecordingButton.get()).toBeInTheDocument(); - }); - - it('Should hide grafana button when user does not have rule create permission', () => { - jest.spyOn(contextSrv, 'hasPermission').mockImplementation((action) => { - return [AccessControlAction.AlertingRuleExternalWrite].includes(action as AccessControlAction); - }); - - renderAlertTypeStep(); - - expect(ui.ruleTypePicker.grafanaManagedButton.query()).not.toBeInTheDocument(); - expect(ui.ruleTypePicker.mimirOrLokiButton.get()).toBeInTheDocument(); - expect(ui.ruleTypePicker.mimirOrLokiRecordingButton.get()).toBeInTheDocument(); - }); - - it('Should hide mimir alert and mimir recording when user does not have rule external write permission', () => { - jest.spyOn(contextSrv, 'hasPermission').mockImplementation((action) => { - return [AccessControlAction.AlertingRuleCreate].includes(action as AccessControlAction); - }); - - renderAlertTypeStep(); - - expect(ui.ruleTypePicker.grafanaManagedButton.get()).toBeInTheDocument(); - expect(ui.ruleTypePicker.mimirOrLokiButton.query()).not.toBeInTheDocument(); - expect(ui.ruleTypePicker.mimirOrLokiRecordingButton.query()).not.toBeInTheDocument(); - }); - }); -}); diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 5ba3675fbfcef..79b0aa71e781c 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -1,7 +1,7 @@ import { AsyncThunk, createAsyncThunk } from '@reduxjs/toolkit'; import { isEmpty } from 'lodash'; -import { config, locationService } from '@grafana/runtime'; +import { locationService } from '@grafana/runtime'; import { AlertmanagerAlert, AlertManagerCortexConfig, @@ -32,9 +32,8 @@ import { RulerRulesConfigDTO, } from 'app/types/unified-alerting-dto'; -import { contextSrv } from '../../../../core/core'; import { backendSrv } from '../../../../core/services/backend_srv'; -import { logInfo, LogMessages, trackNewAlerRuleFormSaved, withPerformanceLogging } from '../Analytics'; +import { logInfo, LogMessages, withPerformanceLogging } from '../Analytics'; import { addAlertManagers, createOrUpdateSilence, diff --git a/public/app/features/alerting/unified/utils/alertmanager.ts b/public/app/features/alerting/unified/utils/alertmanager.ts index c5f0342c9c425..02488b99ad0ec 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.ts @@ -9,7 +9,6 @@ import { Route, TimeInterval, TimeRange, - ObjectMatcher, } from 'app/plugins/datasource/alertmanager/types'; import { Labels } from 'app/types/unified-alerting-dto'; diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index aece545aabc8b..f1301654ac817 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -8,7 +8,6 @@ import { RelativeTimeRange, ScopedVars, TimeRange, - DataSourceInstanceSettings, } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; @@ -32,7 +31,6 @@ import { } from 'app/types/unified-alerting-dto'; import { EvalFunction } from '../../state/alertDef'; -import { MINUTE } from '../components/rule-editor/AlertRuleForm'; import { RuleFormType, RuleFormValues } from '../types/rule-form'; import { getRulesAccess } from './access-control'; diff --git a/public/app/features/canvas/elements/metricValue.tsx b/public/app/features/canvas/elements/metricValue.tsx index 61908d8084b10..5ae95019ea146 100644 --- a/public/app/features/canvas/elements/metricValue.tsx +++ b/public/app/features/canvas/elements/metricValue.tsx @@ -13,7 +13,6 @@ import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimen import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { getDataLinks } from 'app/plugins/panel/canvas/utils'; -import { getDataLinks } from '../../../plugins/panel/canvas/utils'; import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element'; import { ElementState } from '../runtime/element'; import { Align, TextConfig, TextData, VAlign } from '../types'; diff --git a/public/app/features/connections/Connections.tsx b/public/app/features/connections/Connections.tsx index d54cdbd53921d..bf96adbfac088 100644 --- a/public/app/features/connections/Connections.tsx +++ b/public/app/features/connections/Connections.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; -import { NavLandingPage } from 'app/core/components/AppChrome/NavLandingPage'; import { DataSourcesRoutesContext } from 'app/features/datasources/state'; import { StoreState, useSelector } from 'app/types'; diff --git a/public/app/features/connections/pages/DataSourcesListPage.tsx b/public/app/features/connections/pages/DataSourcesListPage.tsx index d4b9b26fdace7..7c75b08d03923 100644 --- a/public/app/features/connections/pages/DataSourcesListPage.tsx +++ b/public/app/features/connections/pages/DataSourcesListPage.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { config } from '@grafana/runtime'; import { Page } from 'app/core/components/Page/Page'; import { DataSourceAddButton } from 'app/features/datasources/components/DataSourceAddButton'; import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx deleted file mode 100644 index 0fe6cfef4a9af..0000000000000 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ /dev/null @@ -1,327 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import { KBarProvider } from 'kbar'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { match, Router } from 'react-router-dom'; -import { useEffectOnce } from 'react-use'; -import { AutoSizerProps } from 'react-virtualized-auto-sizer'; -import { mockToolkitActionCreator } from 'test/core/redux/mocks'; -import { TestProvider } from 'test/helpers/TestProvider'; -import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; - -import { createTheme } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { config, locationService, setDataSourceSrv } from '@grafana/runtime'; -import { Dashboard } from '@grafana/schema'; -import { notifyApp } from 'app/core/actions'; -import { AppChrome } from 'app/core/components/AppChrome/AppChrome'; -import { GrafanaContext } from 'app/core/context/GrafanaContext'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; -import { RouteDescriptor } from 'app/core/navigation/types'; -import { HOME_NAV_ID } from 'app/core/reducers/navModel'; -import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types'; - -import { configureStore } from '../../../store/configureStore'; -import { Props as LazyLoaderProps } from '../dashgrid/LazyLoader'; -import { DashboardSrv, setDashboardSrv } from '../services/DashboardSrv'; -import { DashboardModel } from '../state'; -import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures'; - -import { Props, UnthemedDashboardPage } from './DashboardPage'; - -jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => { - const LazyLoader = ({ children, onLoad }: Pick) => { - useEffectOnce(() => { - onLoad?.(); - }); - return <>{typeof children === 'function' ? children({ isInView: true }) : children}; - }; - return { LazyLoader }; -}); - -jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => { - class GeneralSettings extends React.Component<{}, {}> { - render() { - return <>general settings; - } - } - - return { GeneralSettings }; -}); - -jest.mock('app/features/query/components/QueryGroup', () => { - return { - QueryGroup: () => null, - }; -}); - -jest.mock('app/core/core', () => ({ - appEvents: { - subscribe: () => { - return { unsubscribe: () => {} }; - }, - }, - contextSrv: { - user: { orgId: 1 }, - }, -})); - -jest.mock('react-virtualized-auto-sizer', () => { - // The size of the children need to be small enough to be outside the view. - // So it does not trigger the query to be run by the PanelQueryRunner. - return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 }); -}); - -function getTestDashboard(overrides?: Partial, metaOverrides?: Partial): DashboardModel { - const data = Object.assign( - { - title: 'My dashboard', - panels: [ - { - id: 1, - type: 'timeseries', - title: 'My panel title', - gridPos: { x: 0, y: 0, w: 1, h: 1 }, - }, - ], - }, - overrides - ); - - return createDashboardModelFixture(data, metaOverrides); -} - -const mockInitDashboard = jest.fn(); -const mockCleanUpDashboardAndVariables = jest.fn(); - -function setup(propOverrides?: Partial) { - config.bootData.navTree = [ - { text: 'Dashboards', id: 'dashboards/browse' }, - { text: 'Home', id: HOME_NAV_ID }, - { - text: 'Help', - id: 'help', - }, - ]; - - const store = configureStore(); - const props: Props = { - ...getRouteComponentProps({ - match: { params: { slug: 'my-dash', uid: '11' } } as unknown as match, - route: { routeName: DashboardRoutes.Normal } as RouteDescriptor, - }), - navIndex: { - 'dashboards/browse': { - text: 'Dashboards', - id: 'dashboards/browse', - parentItem: { text: 'Home', id: HOME_NAV_ID }, - }, - [HOME_NAV_ID]: { text: 'Home', id: HOME_NAV_ID }, - }, - initPhase: DashboardInitPhase.NotStarted, - initError: null, - initDashboard: mockInitDashboard, - notifyApp: mockToolkitActionCreator(notifyApp), - cleanUpDashboardAndVariables: mockCleanUpDashboardAndVariables, - cancelVariables: jest.fn(), - templateVarsChangedInUrl: jest.fn(), - dashboard: null, - theme: createTheme(), - }; - - Object.assign(props, propOverrides); - - const context = getGrafanaContextMock(); - - const { unmount, rerender } = render( - - - - - - - - ); - - const wrappedRerender = (newProps: Partial) => { - Object.assign(props, newProps); - return rerender( - - - - - - - - ); - }; - - return { rerender: wrappedRerender, unmount }; -} - -describe('DashboardPage', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('Should call initDashboard on mount', () => { - setup(); - expect(mockInitDashboard).toBeCalledWith({ - fixUrl: true, - routeName: 'normal-dashboard', - urlSlug: 'my-dash', - urlUid: '11', - keybindingSrv: expect.anything(), - }); - }); - - describe('Given a simple dashboard', () => { - it('Should render panels', async () => { - setup({ dashboard: getTestDashboard() }); - expect(await screen.findByText('My panel title')).toBeInTheDocument(); - }); - - it('Should update title', async () => { - setup({ dashboard: getTestDashboard() }); - await waitFor(() => { - expect(document.title).toBe('My dashboard - Dashboards - Grafana'); - }); - }); - - it('only calls initDashboard once when wrapped in AppChrome', async () => { - const props: Props = { - ...getRouteComponentProps({ - match: { params: { slug: 'my-dash', uid: '11' } } as unknown as match, - route: { routeName: DashboardRoutes.Normal } as RouteDescriptor, - }), - navIndex: { - 'dashboards/browse': { - text: 'Dashboards', - id: 'dashboards/browse', - parentItem: { text: 'Home', id: HOME_NAV_ID }, - }, - [HOME_NAV_ID]: { text: 'Home', id: HOME_NAV_ID }, - }, - initPhase: DashboardInitPhase.Completed, - initError: null, - initDashboard: mockInitDashboard, - notifyApp: mockToolkitActionCreator(notifyApp), - cleanUpDashboardAndVariables: mockCleanUpDashboardAndVariables, - cancelVariables: jest.fn(), - templateVarsChangedInUrl: jest.fn(), - dashboard: getTestDashboard(), - theme: createTheme(), - }; - - render( - - - - - - - - ); - - await screen.findByText('My dashboard'); - expect(mockInitDashboard).toHaveBeenCalledTimes(1); - }); - }); - - describe('When going into view mode', () => { - beforeEach(() => { - setDataSourceSrv({ - get: jest.fn().mockResolvedValue({ getRef: jest.fn(), query: jest.fn().mockResolvedValue([]) }), - getInstanceSettings: jest.fn().mockReturnValue({ meta: {} }), - getList: jest.fn(), - reload: jest.fn(), - }); - setDashboardSrv({ - getCurrent: () => getTestDashboard(), - } as DashboardSrv); - }); - - it('Should render panel in view mode', async () => { - const dashboard = getTestDashboard(); - setup({ - dashboard, - queryParams: { viewPanel: '1' }, - }); - await waitFor(() => { - expect(dashboard.panelInView).toBeDefined(); - expect(dashboard.panels[0].isViewing).toBe(true); - }); - }); - - it('Should reset state when leaving', async () => { - const dashboard = getTestDashboard(); - const { rerender } = setup({ - dashboard, - queryParams: { viewPanel: '1' }, - }); - rerender({ queryParams: {}, dashboard }); - - await waitFor(() => { - expect(dashboard.panelInView).toBeUndefined(); - expect(dashboard.panels[0].isViewing).toBe(false); - }); - }); - }); - - describe('When going into edit mode', () => { - it('Should render panel in edit mode', async () => { - const dashboard = getTestDashboard(); - setup({ - dashboard, - queryParams: { editPanel: '1' }, - }); - await waitFor(() => { - expect(dashboard.panelInEdit).toBeDefined(); - }); - }); - }); - - describe('When dashboard unmounts', () => { - it('Should call close action', async () => { - const { rerender, unmount } = setup(); - rerender({ dashboard: getTestDashboard() }); - unmount(); - await waitFor(() => { - expect(mockCleanUpDashboardAndVariables).toHaveBeenCalledTimes(1); - }); - }); - }); - - describe('When dashboard changes', () => { - it('Should call clean up action and init', async () => { - const { rerender } = setup(); - rerender({ dashboard: getTestDashboard() }); - rerender({ - match: { params: { uid: 'new-uid' } } as unknown as match, - dashboard: getTestDashboard({ title: 'Another dashboard' }), - }); - await waitFor(() => { - expect(mockCleanUpDashboardAndVariables).toHaveBeenCalledTimes(1); - expect(mockInitDashboard).toHaveBeenCalledTimes(2); - }); - }); - }); - - describe('No kiosk mode tv', () => { - it('should render dashboard page toolbar and submenu', async () => { - setup({ dashboard: getTestDashboard() }); - expect(await screen.findAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(1); - expect(screen.getAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(1); - }); - }); - - describe('When in full kiosk mode', () => { - it('should not render page toolbar and submenu', async () => { - setup({ dashboard: getTestDashboard(), queryParams: { kiosk: true } }); - await waitFor(() => { - expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(0); - expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0); - }); - }); - }); -}); diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 5a1aa3d5c40f8..509e32d6c1471 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -58,6 +58,7 @@ export type DashboardPageRouteSearchParams = { viewPanel?: string; editview?: string; shareView?: string; + addWidget?: boolean; panelType?: string; inspect?: string; from?: string; @@ -67,10 +68,10 @@ export type DashboardPageRouteSearchParams = { }; export type MapStateToDashboardPageProps = MapStateToProps< - Pick & { dashboard: ReturnType } & Pick< - FnGlobalState, - 'FNDashboard' - >, + Pick & { + dashboard: ReturnType; + navIndex: StoreState['navIndex']; + } & Pick, OwnProps, StoreState >; @@ -451,7 +452,7 @@ export class UnthemedDashboardPage extends PureComponent { {inspectPanel && !FNDashboard && } - {editPanel && !FNDashboard && ( + {editPanel && !FNDashboard && sectionNav && pageNav && ( { pageNav={pageNav} /> )} - {queryParams.editview && !FNDashboard && ( + {queryParams.editview && !FNDashboard && pageNav && sectionNav && ( > & { liveTimeChanged: (tr: TimeRange) => void }; - interface LiveListener { last: number; intervalMs: number; diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index 1c7ce5e9bb514..2c6fccfaa5299 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -90,7 +90,6 @@ describe('DashboardModel', () => { type: 'singlestat', // @ts-expect-error legend: true, - // @ts-expect-error thresholds: '10,20,30', colors: ['#FF0000', 'green', 'orange'], aliasYAxis: { test: 2 }, @@ -668,7 +667,7 @@ describe('DashboardModel', () => { list: [ // @ts-expect-error { - // @ts-expect-error + multi: false, current: { value: ['value'], @@ -677,7 +676,7 @@ describe('DashboardModel', () => { }, // @ts-expect-error { - // @ts-expect-error + multi: true, current: { value: ['value'], @@ -727,7 +726,6 @@ describe('DashboardModel', () => { }, { type: 'query', - // @ts-expect-error current: { // @ts-expect-error tags: [ @@ -897,7 +895,7 @@ describe('DashboardModel', () => { type: 'query', hide: VariableHide.dontHide, datasource: null, - // @ts-expect-error + allFormat: '', }, // @ts-expect-error @@ -905,7 +903,7 @@ describe('DashboardModel', () => { type: 'query', hide: VariableHide.hideLabel, datasource: null, - // @ts-expect-error + allFormat: '', }, // @ts-expect-error @@ -913,7 +911,7 @@ describe('DashboardModel', () => { type: 'query', hide: VariableHide.hideVariable, datasource: null, - // @ts-expect-error + allFormat: '', }, // @ts-expect-error @@ -921,7 +919,7 @@ describe('DashboardModel', () => { type: 'constant', hide: VariableHide.dontHide, query: 'default value', - // @ts-expect-error + current: { selected: true, text: 'A', value: 'B' }, options: [{ selected: true, text: 'A', value: 'B' }], datasource: null, @@ -932,7 +930,7 @@ describe('DashboardModel', () => { type: 'constant', hide: VariableHide.hideLabel, query: 'default value', - // @ts-expect-error + current: { selected: true, text: 'A', value: 'B' }, options: [{ selected: true, text: 'A', value: 'B' }], datasource: null, @@ -943,7 +941,7 @@ describe('DashboardModel', () => { type: 'constant', hide: VariableHide.hideVariable, query: 'default value', - // @ts-expect-error + current: { selected: true, text: 'A', value: 'B' }, options: [{ selected: true, text: 'A', value: 'B' }], datasource: null, @@ -1009,7 +1007,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_never_refresh_with_options', - // @ts-expect-error + options: [{ text: 'A', value: 'A' }], refresh: 0, }, @@ -1017,7 +1015,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_never_refresh_without_options', - // @ts-expect-error + options: [], refresh: 0, }, @@ -1025,7 +1023,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_dashboard_refresh_with_options', - // @ts-expect-error + options: [{ text: 'A', value: 'A' }], refresh: 1, }, @@ -1033,7 +1031,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_dashboard_refresh_without_options', - // @ts-expect-error + options: [], refresh: 1, }, @@ -1041,7 +1039,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_timerange_refresh_with_options', - // @ts-expect-error + options: [{ text: 'A', value: 'A' }], refresh: 2, }, @@ -1049,7 +1047,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_timerange_refresh_without_options', - // @ts-expect-error + options: [], refresh: 2, }, @@ -1057,21 +1055,21 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_no_refresh_with_options', - // @ts-expect-error + options: [{ text: 'A', value: 'A' }], }, // @ts-expect-error { type: 'query', name: 'variable_with_no_refresh_without_options', - // @ts-expect-error + options: [], }, // @ts-expect-error { type: 'query', name: 'variable_with_unknown_refresh_with_options', - // @ts-expect-error + options: [{ text: 'A', value: 'A' }], refresh: 2001, }, @@ -1079,7 +1077,7 @@ describe('DashboardModel', () => { { type: 'query', name: 'variable_with_unknown_refresh_without_options', - // @ts-expect-error + options: [], refresh: 2001, }, @@ -1087,28 +1085,28 @@ describe('DashboardModel', () => { { type: 'custom', name: 'custom', - // @ts-expect-error + options: [{ text: 'custom', value: 'custom' }], }, // @ts-expect-error { type: 'textbox', name: 'textbox', - // @ts-expect-error + options: [{ text: 'Hello', value: 'World' }], }, // @ts-expect-error { type: 'datasource', name: 'datasource', - // @ts-expect-error + options: [{ text: 'ds', value: 'ds' }], // fake example doesn't exist }, // @ts-expect-error { type: 'interval', name: 'interval', - // @ts-expect-error + options: [{ text: '1m', value: '1m' }], }, ], @@ -1186,7 +1184,6 @@ describe('DashboardModel', () => { steps: [ { color: 'green', - // @ts-expect-error value: null, }, { @@ -1360,7 +1357,6 @@ describe('DashboardModel', () => { type: 'singlestat', // @ts-expect-error legend: true, - // @ts-expect-error thresholds: '10,20,30', colors: ['#FF0000', 'green', 'orange'], aliasYAxis: { test: 2 }, @@ -1423,7 +1419,6 @@ describe('DashboardModel', () => { type: 'singlestat', // @ts-expect-error legend: true, - // @ts-expect-error thresholds: '10,20,30', colors: ['#FF0000', 'green', 'orange'], aliasYAxis: { test: 2 }, @@ -2034,7 +2029,6 @@ describe('DashboardModel', () => { { type: 'query', name: 'var', - // @ts-expect-error options: [{ text: 'A', value: 'A' }], refresh: 0, datasource: null, @@ -2045,7 +2039,6 @@ describe('DashboardModel', () => { list: [ // @ts-expect-error { - // @ts-expect-error datasource: null, }, { diff --git a/public/app/features/explore/EmptyStateWrapper.tsx b/public/app/features/explore/EmptyStateWrapper.tsx index 0ab42ea2ad8ec..9eea10bc96066 100644 --- a/public/app/features/explore/EmptyStateWrapper.tsx +++ b/public/app/features/explore/EmptyStateWrapper.tsx @@ -7,7 +7,7 @@ import { ExploreQueryParams, useSelector } from 'app/types'; import { useLoadDataSources } from '../datasources/state'; -import { ExplorePage } from './ExplorePage'; +import ExplorePage from './ExplorePage'; export default function EmptyStateWrapper(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) { const { isLoading } = useLoadDataSources(); diff --git a/public/app/features/explore/LogsMetaRow.test.tsx b/public/app/features/explore/LogsMetaRow.test.tsx deleted file mode 100644 index 4096e2ab5edbe..0000000000000 --- a/public/app/features/explore/LogsMetaRow.test.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import saveAs from 'file-saver'; -import React, { ComponentProps } from 'react'; - -import { LogLevel, LogsDedupStrategy, MutableDataFrame } from '@grafana/data'; - -import { MAX_CHARACTERS } from '../logs/components/LogRowMessage'; -import { logRowsToReadableJson } from '../logs/utils'; - -import { LogsMetaRow } from './LogsMetaRow'; - -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - reportInteraction: () => null, -})); - -jest.mock('file-saver', () => jest.fn()); - -type LogsMetaRowProps = ComponentProps; -const defaultProps: LogsMetaRowProps = { - meta: [], - dedupStrategy: LogsDedupStrategy.none, - dedupCount: 0, - displayedFields: [], - hasUnescapedContent: false, - forceEscape: false, - logRows: [], - onEscapeNewlines: jest.fn(), - clearDetectedFields: jest.fn(), -}; - -const setup = (propOverrides?: object) => { - const props = { - ...defaultProps, - ...propOverrides, - }; - - return render(); -}; - -describe('LogsMetaRow', () => { - it('renders the dedupe number', async () => { - setup({ dedupStrategy: LogsDedupStrategy.numbers, dedupCount: 1234 }); - expect(await screen.findByText('1234')).toBeInTheDocument(); - }); - - it('renders a highlighting warning', async () => { - setup({ logRows: [{ entry: 'A'.repeat(MAX_CHARACTERS + 1) }] }); - expect( - await screen.findByText('Logs with more than 100,000 characters could not be parsed and highlighted') - ).toBeInTheDocument(); - }); - - it('renders the show original line button', () => { - setup({ displayedFields: ['test'] }); - expect( - screen.getByRole('button', { - name: 'Show original line', - }) - ).toBeInTheDocument(); - }); - - it('renders the displayedfield', async () => { - setup({ displayedFields: ['testField1234'] }); - expect(await screen.findByText('testField1234')).toBeInTheDocument(); - }); - - it('renders a button to clear displayedfields', () => { - const clearSpy = jest.fn(); - setup({ displayedFields: ['testField1234'], clearDetectedFields: clearSpy }); - fireEvent( - screen.getByRole('button', { - name: 'Show original line', - }), - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }) - ); - expect(clearSpy).toBeCalled(); - }); - - it('renders a button to remove escaping', () => { - setup({ hasUnescapedContent: true, forceEscape: true }); - expect( - screen.getByRole('button', { - name: 'Remove escaping', - }) - ).toBeInTheDocument(); - }); - - it('renders a button to remove escaping', () => { - setup({ hasUnescapedContent: true, forceEscape: false }); - expect( - screen.getByRole('button', { - name: 'Escape newlines', - }) - ).toBeInTheDocument(); - }); - - it('renders a button to remove escaping', () => { - const escapeSpy = jest.fn(); - setup({ hasUnescapedContent: true, forceEscape: false, onEscapeNewlines: escapeSpy }); - fireEvent( - screen.getByRole('button', { - name: 'Escape newlines', - }), - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }) - ); - expect(escapeSpy).toBeCalled(); - }); - - it('renders a button to show the download menu', () => { - setup(); - expect(screen.getByText('Download').closest('button')).toBeInTheDocument(); - }); - - it('renders a button to show the download menu', async () => { - setup(); - - expect(screen.queryAllByText('txt')).toHaveLength(0); - await userEvent.click(screen.getByText('Download').closest('button')!); - expect( - screen.getByRole('menuitem', { - name: 'txt', - }) - ).toBeInTheDocument(); - }); - - it('renders a button to download txt', async () => { - setup(); - - await userEvent.click(screen.getByText('Download').closest('button')!); - - await userEvent.click( - screen.getByRole('menuitem', { - name: 'txt', - }) - ); - - expect(saveAs).toBeCalled(); - }); - - it('renders a button to download json', async () => { - const rows = [ - { - rowIndex: 1, - entryFieldIndex: 0, - dataFrame: new MutableDataFrame(), - entry: 'test entry', - hasAnsi: false, - hasUnescapedContent: false, - labels: { - foo: 'bar', - }, - logLevel: LogLevel.info, - raw: '', - timeEpochMs: 10, - timeEpochNs: '123456789', - timeFromNow: '', - timeLocal: '', - timeUtc: '', - uid: '2', - }, - ]; - setup({ logRows: rows }); - - await userEvent.click(screen.getByText('Download').closest('button')!); - - await userEvent.click( - screen.getByRole('menuitem', { - name: 'json', - }) - ); - - expect(saveAs).toBeCalled(); - const blob = (saveAs as unknown as jest.Mock).mock.lastCall[0]; - expect(blob.type).toBe('application/json;charset=utf-8'); - const text = await blob.text(); - expect(text).toBe(JSON.stringify(logRowsToReadableJson(rows))); - }); -}); diff --git a/public/app/features/explore/LogsSample.test.tsx b/public/app/features/explore/LogsSample.test.tsx deleted file mode 100644 index 8c76cedcf5729..0000000000000 --- a/public/app/features/explore/LogsSample.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { ComponentProps } from 'react'; - -import { - ArrayVector, - FieldType, - LoadingState, - MutableDataFrame, - SupplementaryQueryType, - DataSourceApi, -} from '@grafana/data'; -import { DataQuery } from '@grafana/schema'; - -import { LogsSamplePanel } from './LogsSamplePanel'; - -jest.mock('@grafana/runtime', () => { - return { - ...jest.requireActual('@grafana/runtime'), - reportInteraction: jest.fn(), - }; -}); - -const createProps = (propOverrides?: Partial>) => { - const props = { - queryResponse: undefined, - enabled: true, - timeZone: 'timeZone', - datasourceInstance: undefined, - setLogsSampleEnabled: jest.fn(), - queries: [], - splitOpen: jest.fn(), - }; - - return { ...props, ...propOverrides }; -}; - -const sampleDataFrame = new MutableDataFrame({ - meta: { - custom: { frameType: 'LabeledTimeValues' }, - }, - fields: [ - { - name: 'labels', - type: FieldType.other, - values: new ArrayVector([ - { place: 'luna', source: 'data' }, - { place: 'luna', source: 'data' }, - ]), - }, - { - name: 'Time', - type: FieldType.time, - values: new ArrayVector(['2022-02-22T09:28:11.352440161Z', '2022-02-22T14:42:50.991981292Z']), - }, - { - name: 'Line', - type: FieldType.string, - values: new ArrayVector(['line1 ', 'line2']), - }, - ], -}); - -describe('LogsSamplePanel', () => { - it('shows empty panel if no data', () => { - render(); - expect(screen.getByText('Logs sample')).toBeInTheDocument(); - }); - - it('shows loading message', () => { - render(); - expect(screen.getByText('Logs sample is loading...')).toBeInTheDocument(); - }); - - it('shows no data message', () => { - render(); - expect(screen.getByText('No logs sample data.')).toBeInTheDocument(); - }); - - it('shows logs sample data', () => { - render( - - ); - expect(screen.getByText('2022-02-22 04:28:11.352')).toBeInTheDocument(); - expect(screen.getByText('line1')).toBeInTheDocument(); - expect(screen.getByText('2022-02-22 09:42:50.991')).toBeInTheDocument(); - expect(screen.getByText('line2')).toBeInTheDocument(); - }); - - it('shows log details', async () => { - render( - - ); - const line = screen.getByText('line1'); - expect(screen.queryByText('foo')).not.toBeInTheDocument(); - await userEvent.click(line); - expect(await screen.findByText('Fields')).toBeInTheDocument(); - expect(await screen.findByText('place')).toBeInTheDocument(); - expect(await screen.findByText('luna')).toBeInTheDocument(); - }); - - it('shows warning message', () => { - render( - - ); - expect(screen.getByText('Failed to load logs sample for this query')).toBeInTheDocument(); - expect(screen.getByText('Test error message')).toBeInTheDocument(); - }); - it('has split open button functionality', async () => { - const datasourceInstance = { - uid: 'test_uid', - getDataProvider: jest.fn(), - getSupportedSupplementaryQueryTypes: jest.fn().mockImplementation(() => [SupplementaryQueryType.LogsSample]), - getSupplementaryQuery: jest.fn().mockImplementation(() => { - return { - refId: 'test_refid', - } as DataQuery; - }), - } as unknown as DataSourceApi; - const splitOpen = jest.fn(); - render( - - ); - const splitButton = screen.getByText('Open logs in split view'); - expect(splitButton).toBeInTheDocument(); - - await userEvent.click(splitButton); - expect(splitOpen).toHaveBeenCalledWith({ datasourceUid: 'test_uid', queries: [{ refId: 'test_refid' }] }); - }); -}); diff --git a/public/app/features/explore/LogsSamplePanel.tsx b/public/app/features/explore/LogsSamplePanel.tsx deleted file mode 100644 index f1b4383f0ece3..0000000000000 --- a/public/app/features/explore/LogsSamplePanel.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { - DataQueryResponse, - DataSourceApi, - GrafanaTheme2, - hasSupplementaryQuerySupport, - LoadingState, - LogsDedupStrategy, - SplitOpen, - SupplementaryQueryType, -} from '@grafana/data'; -import { reportInteraction } from '@grafana/runtime'; -import { DataQuery, TimeZone } from '@grafana/schema'; -import { Button, Collapse, useStyles2 } from '@grafana/ui'; -import { dataFrameToLogsModel } from 'app/core/logsModel'; -import store from 'app/core/store'; - -import { LogRows } from '../logs/components/LogRows'; - -import { SupplementaryResultError } from './SupplementaryResultError'; -import { SETTINGS_KEYS } from './utils/logs'; - -type Props = { - queryResponse: DataQueryResponse | undefined; - enabled: boolean; - timeZone: TimeZone; - queries: DataQuery[]; - datasourceInstance: DataSourceApi | null | undefined; - splitOpen: SplitOpen; - setLogsSampleEnabled: (enabled: boolean) => void; -}; - -export function LogsSamplePanel(props: Props) { - const { queryResponse, timeZone, enabled, setLogsSampleEnabled, datasourceInstance, queries, splitOpen } = props; - - const styles = useStyles2(getStyles); - const onToggleLogsSampleCollapse = (isOpen: boolean) => { - setLogsSampleEnabled(isOpen); - reportInteraction('grafana_explore_logs_sample_toggle_clicked', { - datasourceType: datasourceInstance?.type ?? 'unknown', - type: isOpen ? 'open' : 'close', - }); - }; - - const OpenInSplitViewButton = () => { - if (!hasSupplementaryQuerySupport(datasourceInstance, SupplementaryQueryType.LogsSample)) { - return null; - } - - const logSampleQueries = queries - .map((query) => datasourceInstance.getSupplementaryQuery(SupplementaryQueryType.LogsSample, query)) - .filter((query): query is DataQuery => !!query); - - if (!logSampleQueries.length) { - return null; - } - - const onSplitOpen = () => { - splitOpen({ queries: logSampleQueries, datasourceUid: datasourceInstance.uid }); - reportInteraction('grafana_explore_logs_sample_split_button_clicked', { - datasourceType: datasourceInstance?.type ?? 'unknown', - queriesCount: logSampleQueries.length, - }); - }; - - return ( - - ); - }; - - let LogsSamplePanelContent: JSX.Element | null; - - if (queryResponse === undefined) { - LogsSamplePanelContent = null; - } else if (queryResponse.error !== undefined) { - LogsSamplePanelContent = ( - - ); - } else if (queryResponse.state === LoadingState.Loading) { - LogsSamplePanelContent = Logs sample is loading...; - } else if (queryResponse.data.length === 0 || queryResponse.data[0].length === 0) { - LogsSamplePanelContent = No logs sample data.; - } else { - const logs = dataFrameToLogsModel(queryResponse.data); - LogsSamplePanelContent = ( - <> - -

- -
- - ); - } - - return queryResponse?.state !== LoadingState.NotStarted ? ( - - {LogsSamplePanelContent} - - ) : null; -} - -const getStyles = (theme: GrafanaTheme2) => ({ - logSamplesButton: css` - position: absolute; - top: ${theme.spacing(1)}; - right: ${theme.spacing(1)}; - `, - logContainer: css` - overflow-x: scroll; - `, -}); diff --git a/public/app/features/explore/LogsVolumePanelList.test.tsx b/public/app/features/explore/LogsVolumePanelList.test.tsx deleted file mode 100644 index d9a0473747a71..0000000000000 --- a/public/app/features/explore/LogsVolumePanelList.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { DataQueryResponse, LoadingState, EventBusSrv } from '@grafana/data'; - -import { LogsVolumePanelList } from './LogsVolumePanelList'; - -jest.mock('./Graph/ExploreGraph', () => { - const ExploreGraph = () => ExploreGraph; - return { - ExploreGraph, - }; -}); - -function renderPanel(logsVolumeData?: DataQueryResponse, onLoadLogsVolume = () => {}) { - render( - {}} - width={100} - onUpdateTimeRange={() => {}} - logsVolumeData={logsVolumeData} - onLoadLogsVolume={onLoadLogsVolume} - onHiddenSeriesChanged={() => null} - eventBus={new EventBusSrv()} - /> - ); -} - -describe('LogsVolumePanelList', () => { - it('shows loading message', () => { - renderPanel({ state: LoadingState.Loading, error: undefined, data: [] }); - expect(screen.getByText('Loading...')).toBeInTheDocument(); - }); - - it('shows short warning message', () => { - renderPanel({ state: LoadingState.Error, error: { data: { message: 'Test error message' } }, data: [] }); - expect(screen.getByText('Failed to load log volume for this query')).toBeInTheDocument(); - expect(screen.getByText('Test error message')).toBeInTheDocument(); - }); - - it('shows long warning message', async () => { - // we make a long message - const messagePart = 'One two three four five six seven eight nine ten.'; - const message = messagePart + ' ' + messagePart + ' ' + messagePart; - - renderPanel({ state: LoadingState.Error, error: { data: { message } }, data: [] }); - expect(screen.getByText('Failed to load log volume for this query')).toBeInTheDocument(); - expect(screen.queryByText(message)).not.toBeInTheDocument(); - await userEvent.click(screen.getByRole('button', { name: 'Show details' })); - expect(screen.getByText(message)).toBeInTheDocument(); - }); - - it('a custom message for timeout errors', async () => { - const onLoadCallback = jest.fn(); - renderPanel( - { - state: LoadingState.Error, - error: { data: { message: '{"status":"error","errorType":"timeout","error":"context deadline exceeded"}' } }, - data: [], - }, - onLoadCallback - ); - expect(screen.getByText('The logs volume query is taking too long and has timed out')).toBeInTheDocument(); - await userEvent.click(screen.getByRole('button', { name: 'Retry' })); - expect(onLoadCallback).toHaveBeenCalled(); - }); -}); diff --git a/public/app/features/explore/LogsVolumePanelList.tsx b/public/app/features/explore/LogsVolumePanelList.tsx deleted file mode 100644 index c3295896ef639..0000000000000 --- a/public/app/features/explore/LogsVolumePanelList.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { css } from '@emotion/css'; -import { groupBy, mapValues } from 'lodash'; -import React, { useMemo } from 'react'; - -import { - AbsoluteTimeRange, - DataFrame, - DataQueryResponse, - EventBus, - GrafanaTheme2, - isLogsVolumeLimited, - LoadingState, - SplitOpen, - TimeZone, -} from '@grafana/data'; -import { Button, InlineField, useStyles2 } from '@grafana/ui'; - -import { mergeLogsVolumeDataFrames } from '../logs/utils'; - -import { LogsVolumePanel } from './LogsVolumePanel'; -import { SupplementaryResultError } from './SupplementaryResultError'; -import { isTimeoutErrorResponse } from './utils/logsVolumeResponse'; - -type Props = { - logsVolumeData: DataQueryResponse | undefined; - absoluteRange: AbsoluteTimeRange; - timeZone: TimeZone; - splitOpen: SplitOpen; - width: number; - onUpdateTimeRange: (timeRange: AbsoluteTimeRange) => void; - onLoadLogsVolume: () => void; - onHiddenSeriesChanged: (hiddenSeries: string[]) => void; - eventBus: EventBus; - onClose?(): void; -}; - -export const LogsVolumePanelList = ({ - logsVolumeData, - absoluteRange, - onUpdateTimeRange, - width, - onLoadLogsVolume, - onHiddenSeriesChanged, - eventBus, - splitOpen, - timeZone, - onClose, -}: Props) => { - const logVolumes: Record = useMemo(() => { - const grouped = groupBy(logsVolumeData?.data || [], 'meta.custom.datasourceName'); - return mapValues(grouped, (value) => { - return mergeLogsVolumeDataFrames(value); - }); - }, [logsVolumeData]); - - const styles = useStyles2(getStyles); - - const numberOfLogVolumes = Object.keys(logVolumes).length; - - const containsZoomed = Object.values(logVolumes).some((data: DataFrame[]) => { - const zoomRatio = logsLevelZoomRatio(data, absoluteRange); - return !isLogsVolumeLimited(data) && zoomRatio && zoomRatio < 1; - }); - - const timeoutError = isTimeoutErrorResponse(logsVolumeData); - - if (logsVolumeData?.state === LoadingState.Loading) { - return Loading...; - } else if (timeoutError) { - return ( - - ); - } else if (logsVolumeData?.error !== undefined) { - return ; - } - return ( -
- {Object.keys(logVolumes).map((name, index) => { - const logsVolumeData = { data: logVolumes[name] }; - return ( - 1 ? () => {} : onHiddenSeriesChanged} - eventBus={eventBus} - /> - ); - })} - {containsZoomed && ( -
- -
- )} -
- ); -}; - -const getStyles = (theme: GrafanaTheme2) => { - return { - listContainer: css` - padding-top: 10px; - `, - extraInfoContainer: css` - display: flex; - justify-content: end; - position: absolute; - right: 5px; - top: 5px; - `, - oldInfoText: css` - font-size: ${theme.typography.bodySmall.fontSize}; - color: ${theme.colors.text.secondary}; - `, - }; -}; - -function logsLevelZoomRatio( - logsVolumeData: DataFrame[] | undefined, - selectedTimeRange: AbsoluteTimeRange -): number | undefined { - const dataRange = logsVolumeData && logsVolumeData[0] && logsVolumeData[0].meta?.custom?.absoluteRange; - return dataRange ? (selectedTimeRange.from - selectedTimeRange.to) / (dataRange.from - dataRange.to) : undefined; -} diff --git a/public/app/features/explore/RawPrometheusContainer.test.tsx b/public/app/features/explore/RawPrometheusContainer.test.tsx deleted file mode 100644 index 2f750d7f75271..0000000000000 --- a/public/app/features/explore/RawPrometheusContainer.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { fireEvent, render, screen, within } from '@testing-library/react'; -import React from 'react'; - -import { FieldType, getDefaultTimeRange, InternalTimeZones, toDataFrame } from '@grafana/data'; -import { ExploreId, TABLE_RESULTS_STYLE } from 'app/types/explore'; - -import { RawPrometheusContainer } from './RawPrometheusContainer'; - -function getTable(): HTMLElement { - return screen.getAllByRole('table')[0]; -} - -function getTableToggle(): HTMLElement { - return screen.getAllByRole('radio')[0]; -} - -function getRowsData(rows: HTMLElement[]): Object[] { - let content = []; - for (let i = 1; i < rows.length; i++) { - content.push({ - time: within(rows[i]).getByText(/2021*/).textContent, - text: within(rows[i]).getByText(/test_string_*/).textContent, - }); - } - return content; -} - -const dataFrame = toDataFrame({ - name: 'A', - fields: [ - { - name: 'time', - type: FieldType.time, - values: [1609459200000, 1609470000000, 1609462800000, 1609466400000], - config: { - custom: { - filterable: false, - }, - }, - }, - { - name: 'text', - type: FieldType.string, - values: ['test_string_1', 'test_string_2', 'test_string_3', 'test_string_4'], - config: { - custom: { - filterable: false, - }, - }, - }, - ], -}); - -const defaultProps = { - exploreId: ExploreId.left, - loading: false, - width: 800, - onCellFilterAdded: jest.fn(), - tableResult: [dataFrame], - splitOpenFn: () => {}, - range: getDefaultTimeRange(), - timeZone: InternalTimeZones.utc, - resultsStyle: TABLE_RESULTS_STYLE.raw, - showRawPrometheus: false, -}; - -describe('RawPrometheusContainer', () => { - it('should render component for prometheus', () => { - render(); - - expect(screen.queryAllByRole('table').length).toBe(1); - fireEvent.click(getTableToggle()); - - expect(getTable()).toBeInTheDocument(); - const rows = within(getTable()).getAllByRole('row'); - expect(rows).toHaveLength(5); - expect(getRowsData(rows)).toEqual([ - { time: '2021-01-01 00:00:00', text: 'test_string_1' }, - { time: '2021-01-01 03:00:00', text: 'test_string_2' }, - { time: '2021-01-01 01:00:00', text: 'test_string_3' }, - { time: '2021-01-01 02:00:00', text: 'test_string_4' }, - ]); - }); -}); diff --git a/public/app/features/explore/RawPrometheusContainer.tsx b/public/app/features/explore/RawPrometheusContainer.tsx deleted file mode 100644 index c37a985985267..0000000000000 --- a/public/app/features/explore/RawPrometheusContainer.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { css } from '@emotion/css'; -import React, { PureComponent } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; - -import { applyFieldOverrides, DataFrame, SelectableValue, SplitOpen, TimeZone, ValueLinkConfig } from '@grafana/data'; -import { reportInteraction } from '@grafana/runtime/src'; -import { Collapse, RadioButtonGroup, Table, AdHocFilterItem } from '@grafana/ui'; -import { config } from 'app/core/config'; -import { PANEL_BORDER } from 'app/core/constants'; -import { StoreState, TABLE_RESULTS_STYLE } from 'app/types'; -import { ExploreId, ExploreItemState, TABLE_RESULTS_STYLES, TableResultsStyle } from 'app/types/explore'; - -import { MetaInfoText } from './MetaInfoText'; -import RawListContainer from './PrometheusListView/RawListContainer'; -import { getFieldLinksForExplore } from './utils/links'; - -interface RawPrometheusContainerProps { - ariaLabel?: string; - exploreId: ExploreId; - width: number; - timeZone: TimeZone; - onCellFilterAdded?: (filter: AdHocFilterItem) => void; - showRawPrometheus?: boolean; - splitOpenFn: SplitOpen; -} - -interface PrometheusContainerState { - resultsStyle: TableResultsStyle; -} - -function mapStateToProps(state: StoreState, { exploreId }: RawPrometheusContainerProps) { - const explore = state.explore; - const item: ExploreItemState = explore[exploreId]!; - const { loading: loadingInState, tableResult, rawPrometheusResult, range } = item; - const rawPrometheusFrame: DataFrame[] = rawPrometheusResult ? [rawPrometheusResult] : []; - const result = (tableResult?.length ?? false) > 0 && rawPrometheusResult ? tableResult : rawPrometheusFrame; - const loading = result && result.length > 0 ? false : loadingInState; - - return { loading, tableResult: result, range }; -} - -const connector = connect(mapStateToProps, {}); - -type Props = RawPrometheusContainerProps & ConnectedProps; - -export class RawPrometheusContainer extends PureComponent { - constructor(props: Props) { - super(props); - - // If resultsStyle is undefined we won't render the toggle, and the default table will be rendered - if (props.showRawPrometheus) { - this.state = { - resultsStyle: TABLE_RESULTS_STYLE.raw, - }; - } - } - - getMainFrame(frames: DataFrame[] | null) { - return frames?.find((df) => df.meta?.custom?.parentRowIndex === undefined) || frames?.[0]; - } - - onChangeResultsStyle = (resultsStyle: TableResultsStyle) => { - this.setState({ resultsStyle }); - }; - - getTableHeight() { - const { tableResult } = this.props; - const mainFrame = this.getMainFrame(tableResult); - - if (!mainFrame || mainFrame.length === 0) { - return 200; - } - - // tries to estimate table height - return Math.max(Math.min(600, mainFrame.length * 35) + 35); - } - - renderLabel = () => { - const spacing = css({ - display: 'flex', - justifyContent: 'space-between', - }); - const ALL_GRAPH_STYLE_OPTIONS: Array> = TABLE_RESULTS_STYLES.map((style) => ({ - value: style, - // capital-case it and switch `_` to ` ` - label: style[0].toUpperCase() + style.slice(1).replace(/_/, ' '), - })); - - return ( -
- {this.state.resultsStyle === TABLE_RESULTS_STYLE.raw ? 'Raw' : 'Table'} - { - const props = { - state: - this.state.resultsStyle === TABLE_RESULTS_STYLE.table - ? TABLE_RESULTS_STYLE.raw - : TABLE_RESULTS_STYLE.table, - }; - reportInteraction('grafana_explore_prometheus_instant_query_ui_toggle_clicked', props); - }} - size="sm" - options={ALL_GRAPH_STYLE_OPTIONS} - value={this.state?.resultsStyle} - onChange={this.onChangeResultsStyle} - /> -
- ); - }; - - render() { - const { loading, onCellFilterAdded, tableResult, width, splitOpenFn, range, ariaLabel, timeZone } = this.props; - const height = this.getTableHeight(); - const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER; - - let dataFrames = tableResult; - - if (dataFrames?.length) { - dataFrames = applyFieldOverrides({ - data: dataFrames, - timeZone, - theme: config.theme2, - replaceVariables: (v: string) => v, - fieldConfig: { - defaults: {}, - overrides: [], - }, - }); - // Bit of code smell here. We need to add links here to the frame modifying the frame on every render. - // Should work fine in essence but still not the ideal way to pass props. In logs container we do this - // differently and sidestep this getLinks API on a dataframe - for (const frame of dataFrames) { - for (const field of frame.fields) { - field.getLinks = (config: ValueLinkConfig) => { - return getFieldLinksForExplore({ - field, - rowIndex: config.valueRowIndex!, - splitOpenFn, - range, - dataFrame: frame!, - }); - }; - } - } - } - - const mainFrame = this.getMainFrame(dataFrames); - const subFrames = dataFrames?.filter((df) => df.meta?.custom?.parentRowIndex !== undefined); - const label = this.state?.resultsStyle !== undefined ? this.renderLabel() : 'Table'; - - // Render table as default if resultsStyle is not set. - const renderTable = !this.state?.resultsStyle || this.state?.resultsStyle === TABLE_RESULTS_STYLE.table; - - return ( - - {mainFrame?.length && ( - <> - {renderTable && ( - - )} - {this.state?.resultsStyle === TABLE_RESULTS_STYLE.raw && } - - )} - {!mainFrame?.length && } - - ); - } -} - -export default connector(RawPrometheusContainer); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx index 26b2746222242..65d7298309936 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx @@ -12,45 +12,177 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { getAllByText, getByText, render } from '@testing-library/react'; +import { getByText, render } from '@testing-library/react'; import React from 'react'; -import config from 'app/core/config'; +import { MutableDataFrame } from '@grafana/data'; -import { NewTracePageHeader } from './NewTracePageHeader'; -import { TracePageHeaderEmbedProps } from './TracePageHeader'; -import { trace } from './TracePageHeader.test'; +import { defaultFilters } from '../../useSearch'; -const setup = (propOverrides?: TracePageHeaderEmbedProps) => { +import { TracePageHeader } from './TracePageHeader'; + +const setup = () => { const defaultProps = { trace, timeZone: '', - viewRange: { time: { current: [10, 20] as [number, number] } }, - updateNextViewRangeTime: () => {}, - updateViewRangeTime: () => {}, - ...propOverrides, + search: defaultFilters, + setSearch: jest.fn(), + showSpanFilters: true, + setShowSpanFilters: jest.fn(), + showSpanFilterMatchesOnly: false, + setShowSpanFilterMatchesOnly: jest.fn(), + spanFilterMatches: undefined, + setFocusedSpanIdForSearch: jest.fn(), + datasourceType: 'tempo', + setHeaderHeight: jest.fn(), + data: new MutableDataFrame(), }; - return render(); + return render(); }; -describe('NewTracePageHeader test', () => { +describe('TracePageHeader test', () => { it('should render the new trace header', () => { - config.featureToggles.newTraceView = true; setup(); const header = document.querySelector('header'); const method = getByText(header!, 'POST'); const status = getByText(header!, '200'); const url = getByText(header!, '/v2/gamma/792edh2w897y2huehd2h89'); - const duration = getAllByText(header!, '2.36s'); + const duration = getByText(header!, '2.36s'); const timestampPart1 = getByText(header!, '2023-02-05 08:50'); const timestampPart2 = getByText(header!, ':56.289'); expect(method).toBeInTheDocument(); expect(status).toBeInTheDocument(); expect(url).toBeInTheDocument(); - expect(duration.length).toBe(2); + expect(duration).toBeInTheDocument(); expect(timestampPart1).toBeInTheDocument(); expect(timestampPart2).toBeInTheDocument(); }); }); + +export const trace = { + services: [{ name: 'serviceA', numberOfSpans: 1 }], + spans: [ + { + traceID: '164afda25df92413', + spanID: '264afda25df92413', + operationName: 'HTTP Client', + serviceName: 'serviceA', + subsidiarilyReferencedBy: [], + startTime: 1675602037286989, + duration: 5685, + logs: [], + references: [], + tags: [], + processID: '264afda25df92413', + flags: 0, + process: { + serviceName: 'lb', + tags: [], + }, + relativeStartTime: 0, + depth: 0, + hasChildren: false, + childSpanCount: 0, + warnings: [], + }, + { + traceID: '164afda25df92413', + spanID: '364afda25df92413', + operationName: 'HTTP Client', + serviceName: 'serviceB', + subsidiarilyReferencedBy: [], + startTime: 1675602037286989, + duration: 5685, + logs: [], + references: [], + tags: [ + { + key: 'http.url', + type: 'String', + value: `/v2/gamma/792edh2w897y2huehd2h89`, + }, + { + key: 'http.method', + type: 'String', + value: `POST`, + }, + { + key: 'http.status_code', + type: 'String', + value: `200`, + }, + ], + processID: '364afda25df92413', + flags: 0, + process: { + serviceName: 'lb', + tags: [], + }, + relativeStartTime: 0, + depth: 0, + hasChildren: false, + childSpanCount: 0, + warnings: [], + }, + { + traceID: '164afda25df92413', + spanID: '464afda25df92413', + operationName: 'HTTP Server', + serviceName: 'serviceC', + subsidiarilyReferencedBy: [], + startTime: 1675602037286989, + duration: 5685, + logs: [], + references: [], + tags: [ + { + key: 'http.url', + type: 'String', + value: `/v2/gamma/792edh2w897y2huehd2h89`, + }, + { + key: 'http.method', + type: 'String', + value: `POST`, + }, + { + key: 'http.status_code', + type: 'String', + value: `200`, + }, + ], + processID: '464afda25df92413', + flags: 0, + process: { + serviceName: 'db', + tags: [], + }, + relativeStartTime: 0, + depth: 0, + hasChildren: false, + childSpanCount: 0, + warnings: [], + }, + ], + traceID: '8bb35a31-eb64-512d-aaed-ddd61887bb2b', + traceName: 'serviceA: GET', + processes: { + '264afda25df92413': { + serviceName: 'serviceA', + tags: [], + }, + '364afda25df92413': { + serviceName: 'serviceB', + tags: [], + }, + '464afda25df92413': { + serviceName: 'serviceC', + tags: [], + }, + }, + duration: 2355515, + startTime: 1675605056289000, + endTime: 1675605058644515, +}; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx index b8ba1f0336f2a..1935ac0a0ea9b 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx @@ -14,75 +14,64 @@ import { css } from '@emotion/css'; import cx from 'classnames'; -import * as React from 'react'; +import React, { memo, useEffect, useMemo } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { CoreApp, DataFrame, dateTimeFormat, GrafanaTheme2 } from '@grafana/data'; +import { TimeZone } from '@grafana/schema'; import { Badge, BadgeColor, Tooltip, useStyles2 } from '@grafana/ui'; -import { autoColor } from '../Theme'; +import { SearchProps } from '../../useSearch'; import ExternalLinks from '../common/ExternalLinks'; import TraceName from '../common/TraceName'; import { getTraceLinks } from '../model/link-patterns'; import { getHeaderTags, getTraceName } from '../model/trace-viewer'; +import { Trace } from '../types'; import { formatDuration } from '../utils/date'; import TracePageActions from './Actions/TracePageActions'; -import SpanGraph from './SpanGraph'; -import { TracePageHeaderEmbedProps, timestamp, getStyles } from './TracePageHeader'; +import { SpanFilters } from './SpanFilters/SpanFilters'; -const getNewStyles = (theme: GrafanaTheme2) => { - return { - titleRow: css` - label: TracePageHeaderTitleRow; - align-items: center; - display: flex; - padding: 0 0.5em 0 0.5em; - `, - title: css` - label: TracePageHeaderTitle; - color: inherit; - flex: 1; - font-size: 1.7em; - line-height: 1em; - `, - subtitle: css` - flex: 1; - line-height: 1em; - margin: -0.5em 0.5em 1.5em 0.5em; - `, - tag: css` - margin: 0 0.5em 0 0; - `, - url: css` - margin: -2.5px 0.3em; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 30%; - display: inline-block; - `, - divider: css` - margin: 0 0.75em; - `, - header: css` - label: TracePageHeader; - background-color: ${theme.colors.background.primary}; - position: sticky; - top: 0; - z-index: 5; - padding: 0.5em 0.25em 0 0.25em; - & > :last-child { - border-bottom: 1px solid ${autoColor(theme, '#ccc')}; - } - `, - }; +export type TracePageHeaderProps = { + trace: Trace | null; + data: DataFrame; + app?: CoreApp; + timeZone: TimeZone; + search: SearchProps; + setSearch: React.Dispatch>; + showSpanFilters: boolean; + setShowSpanFilters: (isOpen: boolean) => void; + showSpanFilterMatchesOnly: boolean; + setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void; + setFocusedSpanIdForSearch: React.Dispatch>; + spanFilterMatches: Set | undefined; + datasourceType: string; + setHeaderHeight: (height: number) => void; }; -export function NewTracePageHeader(props: TracePageHeaderEmbedProps) { - const { trace, updateNextViewRangeTime, updateViewRangeTime, viewRange, timeZone } = props; +export const TracePageHeader = memo((props: TracePageHeaderProps) => { + const { + trace, + data, + app, + timeZone, + search, + setSearch, + showSpanFilters, + setShowSpanFilters, + showSpanFilterMatchesOnly, + setShowSpanFilterMatchesOnly, + setFocusedSpanIdForSearch, + spanFilterMatches, + datasourceType, + setHeaderHeight, + } = props; + const styles = useStyles2(getNewStyles); - const styles = { ...useStyles2(getStyles), ...useStyles2(getNewStyles) }; - const links = React.useMemo(() => { + useEffect(() => { + setHeaderHeight(document.querySelector('.' + styles.header)?.scrollHeight ?? 0); + }, [setHeaderHeight, showSpanFilters, styles.header]); + + const links = useMemo(() => { if (!trace) { return []; } @@ -93,18 +82,28 @@ export function NewTracePageHeader(props: TracePageHeaderEmbedProps) { return null; } - const { method, status, url } = getHeaderTags(trace.spans); + const timestamp = (trace: Trace, timeZone: TimeZone) => { + // Convert date from micro to milli seconds + const dateStr = dateTimeFormat(trace.startTime / 1000, { timeZone, defaultWithMS: true }); + const match = dateStr.match(/^(.+)(:\d\d\.\d+)$/); + return match ? ( + + {match[1]} + {match[2]} + + ) : ( + dateStr + ); + }; const title = (

- - | - {formatDuration(trace.duration)} - + {formatDuration(trace.duration)}

); + const { method, status, url } = getHeaderTags(trace.spans); let statusColor: BadgeColor = 'green'; if (status && status.length > 0) { if (status[0].value.toString().charAt(0) === '4') { @@ -119,39 +118,135 @@ export function NewTracePageHeader(props: TracePageHeaderEmbedProps) {
{links && links.length > 0 && } {title} - +
- {timestamp(trace, timeZone, styles)} - {method || status || url ? | : undefined} - {method && method.length > 0 && ( - - - - - - )} - {status && status.length > 0 && ( - - - - - - )} - {url && url.length > 0 && ( - - {url[0].value} - - )} + {timestamp(trace, timeZone)} + + {method && method.length > 0 && ( + + + + + + )} + {status && status.length > 0 && ( + + + + + + )} + {url && url.length > 0 && ( + + {url[0].value} + + )} +
- ); -} +}); + +TracePageHeader.displayName = 'TracePageHeader'; + +const getNewStyles = (theme: GrafanaTheme2) => { + return { + TracePageHeaderBack: css` + label: TracePageHeaderBack; + align-items: center; + align-self: stretch; + background-color: #fafafa; + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + color: inherit; + display: flex; + font-size: 1.4rem; + padding: 0 1rem; + margin-bottom: -1px; + &:hover { + background-color: #f0f0f0; + border-color: #ccc; + } + `, + TracePageHeaderOverviewItemValueDetail: cx( + css` + label: TracePageHeaderOverviewItemValueDetail; + color: #aaa; + `, + 'trace-item-value-detail' + ), + TracePageHeaderOverviewItemValue: css` + label: TracePageHeaderOverviewItemValue; + &:hover > .trace-item-value-detail { + color: unset; + } + `, + header: css` + label: TracePageHeader; + background-color: ${theme.colors.background.primary}; + padding: 0.5em 0 0 0; + position: sticky; + top: 0; + z-index: 5; + `, + titleRow: css` + align-items: flex-start; + display: flex; + padding: 0 8px; + `, + title: css` + color: inherit; + flex: 1; + font-size: 1.7em; + line-height: 1em; + `, + subtitle: css` + flex: 1; + line-height: 1em; + margin: -0.5em 0.5em 0.75em 0.5em; + `, + tag: css` + margin: 0 0.5em 0 0; + `, + duration: css` + color: #aaa; + margin: 0 0.75em; + `, + timestamp: css` + vertical-align: middle; + `, + tagMeta: css` + margin: 0 0.75em; + vertical-align: text-top; + `, + url: css` + margin: -2.5px 0.3em; + height: 15px; + overflow: hidden; + word-break: break-all; + line-height: 20px; + `, + TracePageHeaderTraceId: css` + label: TracePageHeaderTraceId; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 30%; + display: inline-block; + `, + }; +}; diff --git a/public/app/features/invites/SignupInvited.test.tsx b/public/app/features/invites/SignupInvited.test.tsx index 6272248f1f22a..25c5944c578d8 100644 --- a/public/app/features/invites/SignupInvited.test.tsx +++ b/public/app/features/invites/SignupInvited.test.tsx @@ -7,7 +7,6 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { backendSrv } from '../../core/services/backend_srv'; -import { configureStore } from '../../store/configureStore'; import { SignupInvitedPage, Props } from './SignupInvited'; @@ -31,7 +30,6 @@ const defaultGet = { async function setupTestContext({ get = defaultGet }: { get?: typeof defaultGet | null } = {}) { jest.clearAllMocks(); - const store = configureStore(); const getSpy = jest.spyOn(backendSrv, 'get'); getSpy.mockResolvedValue(get); diff --git a/public/app/features/org/OrgDetailsPage.test.tsx b/public/app/features/org/OrgDetailsPage.test.tsx index f3643480cfa20..d36844ac80891 100644 --- a/public/app/features/org/OrgDetailsPage.test.tsx +++ b/public/app/features/org/OrgDetailsPage.test.tsx @@ -1,7 +1,6 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { Provider } from 'react-redux'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; import { TestProvider } from 'test/helpers/TestProvider'; @@ -9,7 +8,6 @@ import { NavModel } from '@grafana/data'; import { ModalManager } from 'app/core/services/ModalManager'; import { backendSrv } from '../../core/services/backend_srv'; -import { configureStore } from '../../store/configureStore'; import { Organization } from '../../types'; import { OrgDetailsPage, Props } from './OrgDetailsPage'; @@ -25,7 +23,6 @@ jest.mock('app/core/core', () => { }); const setup = (propOverrides?: object) => { - const store = configureStore(); jest.clearAllMocks(); // needed because SharedPreferences is rendered in the test jest.spyOn(backendSrv, 'put'); diff --git a/public/app/features/playlist/PlaylistEditPage.test.tsx b/public/app/features/playlist/PlaylistEditPage.test.tsx index fb53a35502283..8807cdf88eba4 100644 --- a/public/app/features/playlist/PlaylistEditPage.test.tsx +++ b/public/app/features/playlist/PlaylistEditPage.test.tsx @@ -25,7 +25,6 @@ jest.mock('app/core/components/TagFilter/TagFilter', () => ({ async function getTestContext({ name, interval, items, uid }: Partial = {}) { jest.clearAllMocks(); - const store = configureStore(); const playlist = { name, items, interval, uid } as unknown as Playlist; const queryParams = {}; const route = {} as RouteDescriptor; diff --git a/public/app/features/playlist/PlaylistNewPage.test.tsx b/public/app/features/playlist/PlaylistNewPage.test.tsx index 6bea38cc87318..9034ca2059f13 100644 --- a/public/app/features/playlist/PlaylistNewPage.test.tsx +++ b/public/app/features/playlist/PlaylistNewPage.test.tsx @@ -23,7 +23,6 @@ jest.mock('app/core/components/TagFilter/TagFilter', () => ({ })); function getTestContext({ name, interval, items }: Partial = {}) { - const store = configureStore(); jest.clearAllMocks(); const playlist = { name, items, interval } as unknown as Playlist; const backendSrvMock = jest.spyOn(backendSrv, 'post'); diff --git a/public/app/features/playlist/PlaylistPage.test.tsx b/public/app/features/playlist/PlaylistPage.test.tsx index 02a486878621c..347af817756c6 100644 --- a/public/app/features/playlist/PlaylistPage.test.tsx +++ b/public/app/features/playlist/PlaylistPage.test.tsx @@ -4,8 +4,6 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { contextSrv } from 'app/core/services/context_srv'; -import { configureStore } from '../../store/configureStore'; - import { PlaylistPage } from './PlaylistPage'; const fnMock = jest.fn(); diff --git a/public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts b/public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts index d2c2bcfa8d3a7..bdbdea78b48cd 100644 --- a/public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts +++ b/public/app/features/plugins/systemjsPlugins/pluginCDN.test.ts @@ -1,6 +1,8 @@ import { config } from '@grafana/runtime'; -import { translateForCDN, extractPluginIdVersionFromUrl } from './pluginCDN'; +import { extractPluginIdVersionFromUrl } from '../cdn/utils'; + +import { translateForCDN } from './pluginCDN'; describe('Plugin CDN', () => { describe('translateForCDN', () => { const load = { diff --git a/public/app/features/profile/ChangePasswordPage.test.tsx b/public/app/features/profile/ChangePasswordPage.test.tsx index 7da0b6adec0b4..55c2dcb2cc3d7 100644 --- a/public/app/features/profile/ChangePasswordPage.test.tsx +++ b/public/app/features/profile/ChangePasswordPage.test.tsx @@ -6,7 +6,6 @@ import { TestProvider } from 'test/helpers/TestProvider'; import config from 'app/core/config'; import { backendSrv } from '../../core/services/backend_srv'; -import { configureStore } from '../../store/configureStore'; import { Props, ChangePasswordPage } from './ChangePasswordPage'; import { initialUserState } from './state/reducers'; @@ -28,7 +27,6 @@ const defaultProps: Props = { }; async function getTestContext(overrides: Partial = {}) { - const store = configureStore(); jest.clearAllMocks(); jest.spyOn(backendSrv, 'get').mockResolvedValue({ id: 1, diff --git a/public/app/features/profile/UserSessions.tsx b/public/app/features/profile/UserSessions.tsx index 107d3c636ed4d..e10bb1b15f6b9 100644 --- a/public/app/features/profile/UserSessions.tsx +++ b/public/app/features/profile/UserSessions.tsx @@ -57,6 +57,7 @@ class UserSessions extends PureComponent { {session.browser} on {session.os} {session.osVersion}
+ {/* @ts-ignore */} - - - - -
- -
- -
- {props.selectedLogGroups.map((lg) => ( -
- {lg.label} - props.onChange(props.selectedLogGroups.filter((slg) => slg.value !== lg.value))} - /> -
- ))} -
- - ); -}; diff --git a/public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.test.tsx b/public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.test.tsx deleted file mode 100644 index 63c4c904bbc03..0000000000000 --- a/public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -// eslint-disable-next-line lodash/import-scope -import lodash from 'lodash'; -import React from 'react'; - -import { config } from '@grafana/runtime'; - -import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; -import { CloudWatchLogsQuery } from '../types'; - -import { LogGroupSelection } from './LogGroupSelection'; - -const originalFeatureToggleValue = config.featureToggles.cloudWatchCrossAccountQuerying; -const originalDebounce = lodash.debounce; - -const defaultProps = { - datasource: setupMockedDataSource().datasource, - query: { - queryMode: 'Logs', - id: '', - region: '', - refId: '', - } as CloudWatchLogsQuery, - onChange: jest.fn(), - onRunQuery: jest.fn(), -}; -describe('LogGroupSelection', () => { - beforeEach(() => { - lodash.debounce = jest.fn().mockImplementation((fn) => { - fn.cancel = () => {}; - return fn; - }); - }); - afterEach(() => { - config.featureToggles.cloudWatchCrossAccountQuerying = originalFeatureToggleValue; - lodash.debounce = originalDebounce; - }); - it('renders the old logGroupSelector when the feature toggle is disabled and there are no linked accounts', async () => { - config.featureToggles.cloudWatchCrossAccountQuerying = false; - render(); - await waitFor(() => screen.getByText('Choose Log Groups')); - expect(screen.queryByText('Select Log Groups')).not.toBeInTheDocument(); - }); - it('renders the old logGroupSelector when the feature toggle is disabled but there are linked accounts', async () => { - config.featureToggles.cloudWatchCrossAccountQuerying = false; - const ds = setupMockedDataSource().datasource; - ds.api.getAccounts = () => - Promise.resolve([ - { - arn: 'arn', - id: 'accountId', - label: 'label', - isMonitoringAccount: true, - }, - ]); - - render(); - await waitFor(() => screen.getByText('Choose Log Groups')); - expect(screen.queryByText('Select Log Groups')).not.toBeInTheDocument(); - }); - - it('renders the old logGroupSelector when the feature toggle is enabled but there are no linked accounts', async () => { - config.featureToggles.cloudWatchCrossAccountQuerying = true; - render(); - await waitFor(() => screen.getByText('Choose Log Groups')); - expect(screen.queryByText('Select Log Groups')).not.toBeInTheDocument(); - }); - - it('renders the new logGroupSelector when the feature toggle is enabled and there are linked accounts', async () => { - config.featureToggles.cloudWatchCrossAccountQuerying = true; - const ds = setupMockedDataSource().datasource; - ds.api.getAccounts = () => - Promise.resolve([ - { - arn: 'arn', - id: 'accountId', - label: 'label', - isMonitoringAccount: true, - }, - ]); - - render(); - await waitFor(() => screen.getByText('Select Log Groups')); - expect(screen.queryByText('Choose Log Groups')).not.toBeInTheDocument(); - }); -}); diff --git a/public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.tsx b/public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.tsx deleted file mode 100644 index 36b0cc7ef8898..0000000000000 --- a/public/app/plugins/datasource/cloudwatch/components/LogGroupSelection.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { config } from '@grafana/runtime'; -import { LegacyForms } from '@grafana/ui'; - -import { SelectableResourceValue } from '../api'; -import { CloudWatchDatasource } from '../datasource'; -import { useAccountOptions } from '../hooks'; -import { CloudWatchLogsQuery, CloudWatchQuery, DescribeLogGroupsRequest } from '../types'; - -import { CrossAccountLogsQueryField } from './CrossAccountLogsQueryField'; -import { LogGroupSelector } from './LogGroupSelector'; - -type Props = { - datasource: CloudWatchDatasource; - query: CloudWatchLogsQuery; - onChange: (value: CloudWatchQuery) => void; - onRunQuery: () => void; -}; - -const rowGap = css` - gap: 3px; -`; - -export const LogGroupSelection = ({ datasource, query, onChange, onRunQuery }: Props) => { - const accountState = useAccountOptions(datasource.api, query.region); - - return ( -
- {config.featureToggles.cloudWatchCrossAccountQuerying && accountState?.value?.length ? ( - ) => - datasource.api.describeCrossAccountLogGroups({ region: query.region, ...params }) - } - onChange={(selectedLogGroups: SelectableResourceValue[]) => { - onChange({ ...query, logGroups: selectedLogGroups, logGroupNames: [] }); - }} - accountOptions={accountState.value} - onRunQuery={onRunQuery} - selectedLogGroups={query.logGroups ?? []} /* todo handle defaults */ - /> - ) : ( - - } - /> - )} -
- ); -}; diff --git a/public/app/plugins/datasource/cloudwatch/hooks.ts b/public/app/plugins/datasource/cloudwatch/hooks.ts index db71a042f4d1f..a4a6738580a1c 100644 --- a/public/app/plugins/datasource/cloudwatch/hooks.ts +++ b/public/app/plugins/datasource/cloudwatch/hooks.ts @@ -4,7 +4,6 @@ import { useAsyncFn, useDeepCompareEffect } from 'react-use'; import { SelectableValue, toOption } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { CloudWatchAPI } from './api'; import { CloudWatchDatasource } from './datasource'; import { ResourcesAPI } from './resources/ResourcesAPI'; import { GetMetricsRequest, GetDimensionKeysRequest } from './resources/types'; diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts index 9d89b809aaad7..881f384530c75 100644 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts +++ b/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts @@ -1,10 +1,7 @@ -import _ from 'lodash'; -import { isEqual } from 'lodash'; -import defaults from 'lodash/defaults'; +import { defaults, isEqual, isNumber } from 'lodash'; import { AnnotationEvent, - AnnotationQueryRequest, DataQueryRequest, DataQueryResponse, DataSourceApi, @@ -12,8 +9,11 @@ import { DataSourceInstanceSettings, ScopedVars, TimeRange, + dateTime, + MutableDataFrame, + FieldType, + DataFrame, } from '@grafana/data'; -import { dateTime, MutableDataFrame, FieldType, DataFrame } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { @@ -183,7 +183,7 @@ export class DataSource extends DataSourceApi { let t: FieldType = FieldType.string; if (fieldName === timePath || isRFC3339_ISO6801(String(doc[fieldName]))) { t = FieldType.time; - } else if (_.isNumber(doc[fieldName])) { + } else if (isNumber(doc[fieldName])) { t = FieldType.number; } let title; @@ -208,6 +208,7 @@ export class DataSource extends DataSourceApi { name: fieldName, type: t, config: { displayName: title }, + // @ts-ignore }).parse = (v: any) => { return v || ''; }; @@ -225,7 +226,7 @@ export class DataSource extends DataSourceApi { return { data: dataFrameArray }; }); } - annotationQuery(options: AnnotationQueryRequest): Promise { + annotationQuery(options: any): Promise { const query = defaults(options.annotation, defaultQuery); return Promise.all([this.createQuery(query, options.range)]).then((results: any) => { const r: AnnotationEvent[] = []; diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx b/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx index 695464de448e9..e09777a1f7607 100644 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx +++ b/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx @@ -1,4 +1,4 @@ -import defaults from 'lodash/defaults'; +import { defaults } from 'lodash'; import React, { PureComponent, ChangeEvent } from 'react'; import { QueryEditorProps } from '@grafana/data'; diff --git a/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Code.tsx b/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Code.tsx index 73c6d581d6652..742d7b7f9469f 100644 --- a/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Code.tsx +++ b/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Code.tsx @@ -1,4 +1,4 @@ -import uniqueId from 'lodash/uniqueId'; +import { uniqueId } from 'lodash'; import React from 'react'; import AceEditor from 'react-ace'; @@ -12,6 +12,7 @@ import 'ace-builds/src-noconflict/mode-hjson'; import 'ace-builds/src-noconflict/mode-sql'; import 'ace-builds/src-noconflict/theme-twilight'; + interface Props extends QueryBuilderFieldProps { lang: string; } diff --git a/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Table.tsx b/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Table.tsx index 03c10af2ef558..c751b74075673 100644 --- a/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Table.tsx +++ b/public/app/plugins/datasource/grafadruid-druid-datasource/builder/abstract/Table.tsx @@ -1,4 +1,4 @@ -import uniqueId from 'lodash/uniqueId'; +import { uniqueId } from 'lodash'; import React from 'react'; import AceEditor from 'react-ace'; diff --git a/public/app/plugins/panel/canvas/Connections.tsx b/public/app/plugins/panel/canvas/Connections.tsx index fa106ad31e1b6..e9da09b3959b0 100644 --- a/public/app/plugins/panel/canvas/Connections.tsx +++ b/public/app/plugins/panel/canvas/Connections.tsx @@ -184,6 +184,7 @@ export class Connections { options.connections = []; } if (this.didConnectionLeaveHighlight) { + // @ts-ignore this.connectionSource.options.connections = [...options.connections, connection]; this.connectionSource.onChange(this.connectionSource.options); } diff --git a/public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx b/public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx index 1125634dd1d90..0bb8eb5b08cd3 100644 --- a/public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx @@ -129,6 +129,7 @@ export const TreeNavigationEditor = ({ item }: StandardEditorProps + {/*@ts-ignore */} >, unit?: string) { - return new MutableDataFrame({ - fields: Object.keys(fields).map((key) => ({ - name: key, - values: fields[key], - config: unit - ? { - unit, - } - : {}, - })), - }); -} - -describe('should get metadata correctly', () => { - it('for bytes', () => { - const container = new FlameGraphDataContainer( - makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'bytes') - ); - const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250); - expect(metadata).toEqual({ - percentValue: 18.83, - unitTitle: 'RAM', - unitValue: '1.51 GiB', - samples: '8,624,078,250', - }); - }); - - it('with default unit', () => { - const container = new FlameGraphDataContainer( - makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'none') - ); - const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250); - expect(metadata).toEqual({ - percentValue: 18.83, - unitTitle: 'Count', - unitValue: '1624078250', - samples: '8,624,078,250', - }); - }); - - it('without unit', () => { - const container = new FlameGraphDataContainer( - makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }) - ); - const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250); - expect(metadata).toEqual({ - percentValue: 18.83, - unitTitle: 'Count', - unitValue: '1624078250', - samples: '8,624,078,250', - }); - }); - - it('for objects', () => { - const container = new FlameGraphDataContainer( - makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'short') - ); - const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250); - expect(metadata).toEqual({ - percentValue: 18.83, - unitTitle: 'Count', - unitValue: '1.62 Bil', - samples: '8,624,078,250', - }); - }); - - it('for nanoseconds', () => { - const container = new FlameGraphDataContainer( - makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'ns') - ); - const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250); - expect(metadata).toEqual({ - percentValue: 18.83, - unitTitle: 'Time', - unitValue: '1.62 s', - samples: '8,624,078,250', - }); - }); -}); diff --git a/public/microfrontends/fn_dashboard/index.html b/public/microfrontends/fn_dashboard/index.html index 37811ad878659..e16a09837df4e 100644 --- a/public/microfrontends/fn_dashboard/index.html +++ b/public/microfrontends/fn_dashboard/index.html @@ -21,13 +21,13 @@ diff --git a/yarn.lock b/yarn.lock index f5e21c65179dc..51ba301a3e30d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5359,14 +5359,7 @@ __metadata: languageName: node linkType: hard -"@lezer/common@npm:1.0.2": - version: 1.0.2 - resolution: "@lezer/common@npm:1.0.2" - checksum: bbcc58e07be02652bf0700d2856042ec089d5be0b95893d628b3e18192ade864fac83b61b19653e10b9f1472261a178b12318d934e9004edd5483a577c0db56b - languageName: node - linkType: hard - -"@lezer/common@npm:^1.0.0": +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0": version: 1.1.0 resolution: "@lezer/common@npm:1.1.0" checksum: 93c208a44d1c0bdf7407853ba7c4ddcedf1c52d1b82170813d83b9bd6301aa23587405ac54332fe39ce8bc37f706936ab237ceb4d3d535d1dead650153b6474c @@ -6611,20 +6604,20 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.11.5, @popperjs/core@npm:^2.11.8": +"@popperjs/core@npm:^2.11.5, @popperjs/core@npm:^2.11.8, @popperjs/core@npm:^2.9.2": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" checksum: e5c69fdebf52a4012f6a1f14817ca8e9599cb1be73dd1387e1785e2ed5e5f0862ff817f420a87c7fc532add1f88a12e25aeb010ffcbdc98eace3d55ce2139cf0 languageName: node linkType: hard -"@prometheus-io/lezer-promql@npm:^0.37.0-rc.1": - version: 0.37.9 - resolution: "@prometheus-io/lezer-promql@npm:0.37.9" +"@prometheus-io/lezer-promql@npm:0.37.0-rc.1": + version: 0.37.0-rc.1 + resolution: "@prometheus-io/lezer-promql@npm:0.37.0-rc.1" peerDependencies: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: afb5e2ddaff99dfb6170af0d121f6960aae76d3f73d184ae3576e68bd4501dc41182075e717008e2f8b587dfc76ff73a7b9fe0b26a7e871ee3d93118b7330c1f + checksum: 07187c66baa618912fc157e8514780c3517f6b96687423a2d268a7b74cbe226aa5013be1a842dd5c7ac23a217966dc82155a52109bb99c3e1ec4c165a6f10bf8 languageName: node linkType: hard @@ -11165,6 +11158,18 @@ __metadata: languageName: node linkType: hard +"@types/react-datepicker@npm:^4.19.0": + version: 4.19.0 + resolution: "@types/react-datepicker@npm:4.19.0" + dependencies: + "@popperjs/core": ^2.9.2 + "@types/react": "*" + date-fns: ^2.0.1 + react-popper: ^2.2.5 + checksum: 18c3a6c0aaad1476ac255497eff4fe4d16776e201bc05c3aeb8206b265133b9854652eb7b346279304b8532a97ca368c0ba9d04485721e9f0bcf6ae8fcf019d0 + languageName: node + linkType: hard + "@types/react-dom@npm:*, @types/react-dom@npm:^18.0.0": version: 18.2.13 resolution: "@types/react-dom@npm:18.2.13" @@ -11328,7 +11333,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:>=16": +"@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:^18.2.28": version: 18.2.28 resolution: "@types/react@npm:18.2.28" dependencies: @@ -16235,7 +16240,7 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:2.30.0, date-fns@npm:^2.30.0": +"date-fns@npm:2.30.0, date-fns@npm:^2.0.1, date-fns@npm:^2.30.0": version: 2.30.0 resolution: "date-fns@npm:2.30.0" dependencies: @@ -19760,7 +19765,7 @@ __metadata: "@grafana/ui": "workspace:*" "@kusto/monaco-kusto": ^7.4.0 "@leeoniya/ufuzzy": 1.0.8 - "@lezer/common": 1.0.2 + "@lezer/common": ^1.1.0 "@lezer/highlight": 1.1.3 "@lezer/lr": 1.3.3 "@locker/near-membrane-dom": ^0.12.15 @@ -19773,7 +19778,7 @@ __metadata: "@opentelemetry/semantic-conventions": 1.15.0 "@pmmmwh/react-refresh-webpack-plugin": 0.5.10 "@popperjs/core": 2.11.6 - "@prometheus-io/lezer-promql": ^0.37.0-rc.1 + "@prometheus-io/lezer-promql": 0.37.0-rc.1 "@pyroscope/flamegraph": ^0.35.5 "@react-aria/button": 3.8.0 "@react-aria/dialog": 3.5.3 @@ -19828,8 +19833,9 @@ __metadata: "@types/papaparse": 5.3.7 "@types/pluralize": ^0.0.29 "@types/prismjs": 1.26.0 - "@types/react": 18.2.15 + "@types/react": ^18.2.28 "@types/react-beautiful-dnd": 13.1.3 + "@types/react-datepicker": ^4.19.0 "@types/react-dom": 18.2.7 "@types/react-grid-layout": 1.3.2 "@types/react-highlight-words": 0.16.4 @@ -27866,7 +27872,7 @@ __metadata: languageName: node linkType: hard -"react-popper@npm:2.3.0, react-popper@npm:^2.3.0": +"react-popper@npm:2.3.0, react-popper@npm:^2.2.5, react-popper@npm:^2.3.0": version: 2.3.0 resolution: "react-popper@npm:2.3.0" dependencies: From 8907f779531faddc6c47bc65e3ffd584a7c90675 Mon Sep 17 00:00:00 2001 From: Gurinder Singh Date: Wed, 18 Oct 2023 00:39:08 -0400 Subject: [PATCH 2/4] react-use module installed --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2e5b974090aa1..9a0fcfadee2a6 100644 --- a/package.json +++ b/package.json @@ -411,7 +411,7 @@ "react-split-pane": "0.1.92", "react-table": "7.8.0", "react-transition-group": "4.4.5", - "react-use": "17.4.0", + "react-use": "^17.4.0", "react-virtual": "2.10.4", "react-virtualized-auto-sizer": "1.0.7", "react-window": "1.8.8", diff --git a/yarn.lock b/yarn.lock index 51ba301a3e30d..2d9bac7f9d317 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20024,7 +20024,7 @@ __metadata: react-table: 7.8.0 react-test-renderer: 18.2.0 react-transition-group: 4.4.5 - react-use: 17.4.0 + react-use: ^17.4.0 react-virtual: 2.10.4 react-virtualized-auto-sizer: 1.0.7 react-window: 1.8.8 @@ -28265,7 +28265,7 @@ __metadata: languageName: node linkType: hard -"react-use@npm:17.4.0": +"react-use@npm:17.4.0, react-use@npm:^17.4.0": version: 17.4.0 resolution: "react-use@npm:17.4.0" dependencies: From e69f25adf0b76b318bca49aacb6d5e6205fcb33e Mon Sep 17 00:00:00 2001 From: Spikatrix <12792882+Spikatrix@users.noreply.github.com> Date: Wed, 18 Oct 2023 08:02:21 +0000 Subject: [PATCH 3/4] Fix type error and adhoc filter alignment --- package.json | 2 +- .../grafana-ui/src/components/Icon/Icon.tsx | 3 +++ .../src/components/Menu/MenuItem.tsx | 24 +++++++++---------- packages/grafana-ui/src/themes/mixins.ts | 6 ++--- .../variables/pickers/PickerRenderer.tsx | 3 +++ public/microfrontends/fn_dashboard/index.html | 4 ++-- yarn.lock | 4 ++-- 7 files changed, 26 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 9a0fcfadee2a6..dbc0636139356 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "@types/papaparse": "5.3.7", "@types/pluralize": "^0.0.29", "@types/prismjs": "1.26.0", - "@types/react": "^18.2.28", + "@types/react": "18.2.28", "@types/react-beautiful-dnd": "13.1.3", "@types/react-datepicker": "^4.19.0", "@types/react-dom": "18.2.7", diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index c881e284d9840..91c27df0ffee8 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -42,6 +42,9 @@ export const Icon = React.forwardRef( /* Temporary solution to display also font awesome icons */ if (name?.startsWith('fa fa-')) { + { + /* @ts-ignore */ + } return ; } diff --git a/packages/grafana-ui/src/components/Menu/MenuItem.tsx b/packages/grafana-ui/src/components/Menu/MenuItem.tsx index 579a520204c54..da942f51b4c17 100644 --- a/packages/grafana-ui/src/components/Menu/MenuItem.tsx +++ b/packages/grafana-ui/src/components/Menu/MenuItem.tsx @@ -165,16 +165,16 @@ export const MenuItem = React.memo( {label}
- { - hasShortcut && ( -
- - {shortcut} -
- ) - } - { - hasSubMenu && ( + {hasShortcut && ( +
+ {/* @ts-ignore */} + + {shortcut} +
+ )} + {hasSubMenu && ( + <> + {/* @ts-ignore */} - ) - } + + )}
diff --git a/packages/grafana-ui/src/themes/mixins.ts b/packages/grafana-ui/src/themes/mixins.ts index e633ae2a98320..7c798aa3bdea1 100644 --- a/packages/grafana-ui/src/themes/mixins.ts +++ b/packages/grafana-ui/src/themes/mixins.ts @@ -40,16 +40,16 @@ export function mediaUp(breakpoint: string) { return `only screen and (min-width: ${breakpoint})`; } -const isGrafanaTheme2 = (theme: GrafanaTheme | GrafanaTheme2): theme is GrafanaTheme2 => theme.hasOwnProperty('v1'); +// const isGrafanaTheme2 = (theme: GrafanaTheme | GrafanaTheme2): theme is GrafanaTheme2 => theme.hasOwnProperty('v1'); export const focusCss = (theme: GrafanaTheme | GrafanaTheme2) => { - const isTheme2 = isGrafanaTheme2(theme); + /* const isTheme2 = isGrafanaTheme2(theme); const firstColor = isTheme2 ? theme.colors.background.canvas : theme.colors.bodyBg; const secondColor = isTheme2 ? theme.colors.primary.main : theme.colors.formFocusOutline; + box-shadow: 0 0 0 2px ${firstColor}, 0 0 0px 4px ${secondColor}; */ return ` outline: 2px dotted transparent; outline-offset: 2px; - box-shadow: 0 0 0 2px ${firstColor}, 0 0 0px 4px ${secondColor}; transition-property: outline, outline-offset, box-shadow; transition-duration: 0.2s; transition-timing-function: cubic-bezier(0.19, 1, 0.22, 1);`; diff --git a/public/app/features/variables/pickers/PickerRenderer.tsx b/public/app/features/variables/pickers/PickerRenderer.tsx index 15dc22beff52a..597eacb21d4da 100644 --- a/public/app/features/variables/pickers/PickerRenderer.tsx +++ b/public/app/features/variables/pickers/PickerRenderer.tsx @@ -25,6 +25,9 @@ const renderWrapperStyle = css` font-size: 12px; line-height: 24px; } + & span { + margin-top: 2px; + } `; export const PickerRenderer: FunctionComponent = (props) => { diff --git a/public/microfrontends/fn_dashboard/index.html b/public/microfrontends/fn_dashboard/index.html index e16a09837df4e..2b10c4efcd328 100644 --- a/public/microfrontends/fn_dashboard/index.html +++ b/public/microfrontends/fn_dashboard/index.html @@ -21,13 +21,13 @@ diff --git a/yarn.lock b/yarn.lock index 2d9bac7f9d317..3bcf97f586a88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11333,7 +11333,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:^18.2.28": +"@types/react@npm:*, @types/react@npm:18.2.28, @types/react@npm:>=16": version: 18.2.28 resolution: "@types/react@npm:18.2.28" dependencies: @@ -19833,7 +19833,7 @@ __metadata: "@types/papaparse": 5.3.7 "@types/pluralize": ^0.0.29 "@types/prismjs": 1.26.0 - "@types/react": ^18.2.28 + "@types/react": 18.2.28 "@types/react-beautiful-dnd": 13.1.3 "@types/react-datepicker": ^4.19.0 "@types/react-dom": 18.2.7 From b3abde64978958b261a4bca7ad7f90e4162c019e Mon Sep 17 00:00:00 2001 From: Spikatrix <12792882+Spikatrix@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:08:10 +0000 Subject: [PATCH 4/4] Fix one more type error --- packages/grafana-ui/src/components/Icon/Icon.tsx | 4 +--- public/microfrontends/fn_dashboard/index.html | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index 91c27df0ffee8..119e79940184e 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -42,9 +42,7 @@ export const Icon = React.forwardRef( /* Temporary solution to display also font awesome icons */ if (name?.startsWith('fa fa-')) { - { - /* @ts-ignore */ - } + /* @ts-ignore */ return ; } diff --git a/public/microfrontends/fn_dashboard/index.html b/public/microfrontends/fn_dashboard/index.html index 2b10c4efcd328..ef29e97a6157b 100644 --- a/public/microfrontends/fn_dashboard/index.html +++ b/public/microfrontends/fn_dashboard/index.html @@ -27,7 +27,7 @@