Cleanup: Remove CSV drag-and-drop snapshot query feature (#113645)
* Chore: Remove editPanelCSVDragAndDrop feature * update i18n * fix issues from PR
This commit is contained in:
@@ -84,10 +84,6 @@ export interface FeatureToggles {
|
|||||||
*/
|
*/
|
||||||
alertingBacktesting?: boolean;
|
alertingBacktesting?: boolean;
|
||||||
/**
|
/**
|
||||||
* Enables drag and drop for CSV and Excel files
|
|
||||||
*/
|
|
||||||
editPanelCSVDragAndDrop?: boolean;
|
|
||||||
/**
|
|
||||||
* Allow datasource to provide custom UI for context view
|
* Allow datasource to provide custom UI for context view
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -124,13 +124,6 @@ var (
|
|||||||
Stage: FeatureStageExperimental,
|
Stage: FeatureStageExperimental,
|
||||||
Owner: grafanaAlertingSquad,
|
Owner: grafanaAlertingSquad,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "editPanelCSVDragAndDrop",
|
|
||||||
Description: "Enables drag and drop for CSV and Excel files",
|
|
||||||
FrontendOnly: true,
|
|
||||||
Stage: FeatureStageExperimental,
|
|
||||||
Owner: grafanaDatavizSquad,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "logsContextDatasourceUi",
|
Name: "logsContextDatasourceUi",
|
||||||
Description: "Allow datasource to provide custom UI for context view",
|
Description: "Allow datasource to provide custom UI for context view",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false
|
|||||||
showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,false,false,false
|
showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,false,false,false
|
||||||
mysqlAnsiQuotes,experimental,@grafana/search-and-storage,false,false,false
|
mysqlAnsiQuotes,experimental,@grafana/search-and-storage,false,false,false
|
||||||
alertingBacktesting,experimental,@grafana/alerting-squad,false,false,false
|
alertingBacktesting,experimental,@grafana/alerting-squad,false,false,false
|
||||||
editPanelCSVDragAndDrop,experimental,@grafana/dataviz-squad,false,false,true
|
|
||||||
logsContextDatasourceUi,GA,@grafana/observability-logs,false,false,true
|
logsContextDatasourceUi,GA,@grafana/observability-logs,false,false,true
|
||||||
lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
|
lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
|
||||||
lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
|
lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
|
||||||
|
|||||||
|
Generated
-4
@@ -67,10 +67,6 @@ const (
|
|||||||
// Rule backtesting API for alerting
|
// Rule backtesting API for alerting
|
||||||
FlagAlertingBacktesting = "alertingBacktesting"
|
FlagAlertingBacktesting = "alertingBacktesting"
|
||||||
|
|
||||||
// FlagEditPanelCSVDragAndDrop
|
|
||||||
// Enables drag and drop for CSV and Excel files
|
|
||||||
FlagEditPanelCSVDragAndDrop = "editPanelCSVDragAndDrop"
|
|
||||||
|
|
||||||
// FlagLogsContextDatasourceUi
|
// FlagLogsContextDatasourceUi
|
||||||
// Allow datasource to provide custom UI for context view
|
// Allow datasource to provide custom UI for context view
|
||||||
FlagLogsContextDatasourceUi = "logsContextDatasourceUi"
|
FlagLogsContextDatasourceUi = "logsContextDatasourceUi"
|
||||||
|
|||||||
@@ -1357,6 +1357,7 @@
|
|||||||
"name": "editPanelCSVDragAndDrop",
|
"name": "editPanelCSVDragAndDrop",
|
||||||
"resourceVersion": "1762783224740",
|
"resourceVersion": "1762783224740",
|
||||||
"creationTimestamp": "2023-01-24T09:43:44Z",
|
"creationTimestamp": "2023-01-24T09:43:44Z",
|
||||||
|
"deletionTimestamp": "2025-11-10T16:31:10Z",
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"grafana.app/updatedTimestamp": "2025-11-10 14:00:24.740459 +0000 UTC"
|
"grafana.app/updatedTimestamp": "2025-11-10 14:00:24.740459 +0000 UTC"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import { Accept } from 'react-dropzone';
|
|
||||||
|
|
||||||
export const acceptedFiles: Accept = {
|
|
||||||
'text/plain': ['.csv', '.txt'],
|
|
||||||
'application/json': ['.json'],
|
|
||||||
};
|
|
||||||
|
|
||||||
//This should probably set from grafana conf
|
|
||||||
export const maxFileSize = 500000;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { queryByTestId, render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
PluginType,
|
PluginType,
|
||||||
locationUtil,
|
locationUtil,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
|
|
||||||
import { DataSourceModal, DataSourceModalProps } from './DataSourceModal';
|
import { DataSourceModal, DataSourceModalProps } from './DataSourceModal';
|
||||||
|
|
||||||
@@ -106,44 +105,6 @@ describe('DataSourceDropdown', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only displays the file drop area when the ff is enabled', async () => {
|
|
||||||
const defaultValue = config.featureToggles.editPanelCSVDragAndDrop;
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = true;
|
|
||||||
setup({ uploadFile: true });
|
|
||||||
|
|
||||||
expect(await screen.queryByTestId('file-drop-zone-default-children')).toBeInTheDocument();
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = defaultValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not show the file drop area when the ff is disabled', async () => {
|
|
||||||
const defaultValue = config.featureToggles.editPanelCSVDragAndDrop;
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = false;
|
|
||||||
|
|
||||||
setup({ uploadFile: true });
|
|
||||||
expect(await screen.queryByTestId('file-drop-zone-default-children')).toBeNull();
|
|
||||||
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = defaultValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not display the drop zone by default', async () => {
|
|
||||||
const defaultValue = config.featureToggles.editPanelCSVDragAndDrop;
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = true;
|
|
||||||
|
|
||||||
const component = setup();
|
|
||||||
|
|
||||||
expect(queryByTestId(component.container, 'file-drop-zone-default-children')).toBeNull();
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = defaultValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the drop zone when uploadFile is enabled', async () => {
|
|
||||||
const defaultValue = config.featureToggles.editPanelCSVDragAndDrop;
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = true;
|
|
||||||
setup({ uploadFile: true });
|
|
||||||
|
|
||||||
expect(await screen.queryByTestId('file-drop-zone-default-children')).toBeInTheDocument();
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = defaultValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fetch the DS applying the correct filters consistently across lists', async () => {
|
it('should fetch the DS applying the correct filters consistently across lists', async () => {
|
||||||
const filters = {
|
const filters = {
|
||||||
mixed: true,
|
mixed: true,
|
||||||
@@ -197,29 +158,6 @@ describe('DataSourceDropdown', () => {
|
|||||||
expect(await screen.findByText('No data sources found')).toBeInTheDocument();
|
expect(await screen.findByText('No data sources found')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Skipping this test as it's flaky on drone
|
|
||||||
it.skip('calls the onChange with the default query containing the file', async () => {
|
|
||||||
const user = userEvent.setup();
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = true;
|
|
||||||
const onChange = jest.fn();
|
|
||||||
setup({ onChange, uploadFile: true });
|
|
||||||
|
|
||||||
const fileInput = (
|
|
||||||
await screen.queryByTestId('file-drop-zone-default-children')!
|
|
||||||
).parentElement!.parentElement!.querySelector('input');
|
|
||||||
const file = new File([''], 'test.csv', { type: 'text/plain' });
|
|
||||||
expect(fileInput).toBeInTheDocument();
|
|
||||||
await user.upload(fileInput!, file);
|
|
||||||
const defaultQuery = onChange.mock.lastCall[1][0];
|
|
||||||
expect(defaultQuery).toMatchObject({
|
|
||||||
refId: 'A',
|
|
||||||
datasource: { type: 'grafana', uid: 'grafana' },
|
|
||||||
queryType: 'snapshot',
|
|
||||||
file: { path: 'test.csv' },
|
|
||||||
});
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call the onChange handler with the correct datasource', async () => {
|
it('should call the onChange handler with the correct datasource', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
|
|||||||
@@ -4,22 +4,12 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import { DataSourceInstanceSettings, DataSourceRef, GrafanaTheme2 } from '@grafana/data';
|
import { DataSourceInstanceSettings, DataSourceRef, GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans, t } from '@grafana/i18n';
|
||||||
import { config, reportInteraction, useFavoriteDatasources } from '@grafana/runtime';
|
import { reportInteraction, useFavoriteDatasources } from '@grafana/runtime';
|
||||||
import { DataQuery } from '@grafana/schema';
|
import { DataQuery } from '@grafana/schema';
|
||||||
import {
|
import { Modal, useStyles2, Input, Icon, ScrollContainer } from '@grafana/ui';
|
||||||
Modal,
|
|
||||||
FileDropzone,
|
|
||||||
FileDropzoneDefaultChildren,
|
|
||||||
useStyles2,
|
|
||||||
Input,
|
|
||||||
Icon,
|
|
||||||
ScrollContainer,
|
|
||||||
} from '@grafana/ui';
|
|
||||||
import { acceptedFiles, maxFileSize } from 'app/features/dataframe-import/constants';
|
|
||||||
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
|
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
|
||||||
import { getFileDropToQueryHandler } from 'app/plugins/datasource/grafana/utils';
|
|
||||||
|
|
||||||
import { useDatasource, useDatasources } from '../../hooks';
|
import { useDatasources } from '../../hooks';
|
||||||
|
|
||||||
import { AddNewDataSourceButton } from './AddNewDataSourceButton';
|
import { AddNewDataSourceButton } from './AddNewDataSourceButton';
|
||||||
import { BuiltInDataSourceList } from './BuiltInDataSourceList';
|
import { BuiltInDataSourceList } from './BuiltInDataSourceList';
|
||||||
@@ -29,7 +19,6 @@ import { matchDataSourceWithSearch } from './utils';
|
|||||||
const INTERACTION_EVENT_NAME = 'dashboards_dspickermodal_clicked';
|
const INTERACTION_EVENT_NAME = 'dashboards_dspickermodal_clicked';
|
||||||
const INTERACTION_ITEM = {
|
const INTERACTION_ITEM = {
|
||||||
SELECT_DS: 'select_ds',
|
SELECT_DS: 'select_ds',
|
||||||
UPLOAD_FILE: 'upload_file',
|
|
||||||
CONFIG_NEW_DS: 'config_new_ds',
|
CONFIG_NEW_DS: 'config_new_ds',
|
||||||
CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state',
|
CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state',
|
||||||
SEARCH: 'search',
|
SEARCH: 'search',
|
||||||
@@ -56,7 +45,6 @@ export interface DataSourceModalProps {
|
|||||||
alerting?: boolean;
|
alerting?: boolean;
|
||||||
pluginId?: string;
|
pluginId?: string;
|
||||||
logs?: boolean;
|
logs?: boolean;
|
||||||
uploadFile?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataSourceModal({
|
export function DataSourceModal({
|
||||||
@@ -70,7 +58,6 @@ export function DataSourceModal({
|
|||||||
alerting,
|
alerting,
|
||||||
pluginId,
|
pluginId,
|
||||||
logs,
|
logs,
|
||||||
uploadFile,
|
|
||||||
filter,
|
filter,
|
||||||
onChange,
|
onChange,
|
||||||
current,
|
current,
|
||||||
@@ -96,8 +83,6 @@ export function DataSourceModal({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const grafanaDS = useDatasource('-- Grafana --');
|
|
||||||
|
|
||||||
// Get all datasources to report total_configured count
|
// Get all datasources to report total_configured count
|
||||||
const dataSources = useDatasources({
|
const dataSources = useDatasources({
|
||||||
tracing,
|
tracing,
|
||||||
@@ -134,22 +119,6 @@ export function DataSourceModal({
|
|||||||
[analyticsInteractionSrc]
|
[analyticsInteractionSrc]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFileDrop = getFileDropToQueryHandler((query, fileRejections) => {
|
|
||||||
if (!grafanaDS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onChange(grafanaDS, [query]);
|
|
||||||
|
|
||||||
reportInteraction(INTERACTION_EVENT_NAME, {
|
|
||||||
item: INTERACTION_ITEM.UPLOAD_FILE,
|
|
||||||
src: analyticsInteractionSrc,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fileRejections.length < 1) {
|
|
||||||
onDismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Built-in data sources used twice because of mobile layout adjustments
|
// Built-in data sources used twice because of mobile layout adjustments
|
||||||
// In movile the list is appended to the bottom of the DS list
|
// In movile the list is appended to the bottom of the DS list
|
||||||
const BuiltInList = ({ className }: { className?: string }) => {
|
const BuiltInList = ({ className }: { className?: string }) => {
|
||||||
@@ -231,20 +200,6 @@ export function DataSourceModal({
|
|||||||
<BuiltInList />
|
<BuiltInList />
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</div>
|
</div>
|
||||||
{uploadFile && config.featureToggles.editPanelCSVDragAndDrop && (
|
|
||||||
<FileDropzone
|
|
||||||
readAs="readAsArrayBuffer"
|
|
||||||
fileListRenderer={() => undefined}
|
|
||||||
options={{
|
|
||||||
maxSize: maxFileSize,
|
|
||||||
multiple: false,
|
|
||||||
accept: acceptedFiles,
|
|
||||||
onDrop: onFileDrop,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileDropzoneDefaultChildren />
|
|
||||||
</FileDropzone>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.newDSSection}>
|
<div className={styles.newDSSection}>
|
||||||
<span className={styles.newDSDescription}>
|
<span className={styles.newDSDescription}>
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { ModalRoot, ModalsProvider } from '@grafana/ui';
|
import { ModalRoot, ModalsProvider } from '@grafana/ui';
|
||||||
import config from 'app/core/config';
|
|
||||||
import { defaultFileUploadQuery } from 'app/plugins/datasource/grafana/types';
|
|
||||||
|
|
||||||
import { DataSourcePicker, DataSourcePickerProps } from './DataSourcePicker';
|
import { DataSourcePicker, DataSourcePickerProps } from './DataSourcePicker';
|
||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
@@ -290,18 +288,6 @@ describe('DataSourcePicker', () => {
|
|||||||
expect(screen.getByRole('link')).toHaveAttribute('href', '/my-sub-path/connections/datasources/new');
|
expect(screen.getByRole('link')).toHaveAttribute('href', '/my-sub-path/connections/datasources/new');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onChange with the default query when add csv is clicked', async () => {
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = true;
|
|
||||||
const onChange = jest.fn();
|
|
||||||
await setupOpenDropdown(user, { onChange, uploadFile: true });
|
|
||||||
|
|
||||||
await user.click(await screen.findByText('Add csv or spreadsheet'));
|
|
||||||
|
|
||||||
expect(onChange.mock.lastCall[1]).toEqual([defaultFileUploadQuery]);
|
|
||||||
expect(screen.queryByText('Open advanced data source picker')).toBeNull(); //Drop down is closed
|
|
||||||
config.featureToggles.editPanelCSVDragAndDrop = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should open the modal when open advanced is clicked', async () => {
|
it('should open the modal when open advanced is clicked', async () => {
|
||||||
const props = { onChange: jest.fn(), current: mockDS1.name };
|
const props = { onChange: jest.fn(), current: mockDS1.name };
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ import { Trans, t } from '@grafana/i18n';
|
|||||||
import { FavoriteDatasources, reportInteraction, useFavoriteDatasources } from '@grafana/runtime';
|
import { FavoriteDatasources, reportInteraction, useFavoriteDatasources } from '@grafana/runtime';
|
||||||
import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema';
|
import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema';
|
||||||
import { Button, floatingUtils, Icon, Input, ModalsController, Portal, ScrollContainer, useStyles2 } from '@grafana/ui';
|
import { Button, floatingUtils, Icon, Input, ModalsController, Portal, ScrollContainer, useStyles2 } from '@grafana/ui';
|
||||||
import config from 'app/core/config';
|
|
||||||
import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection';
|
import { useKeyNavigationListener } from 'app/features/search/hooks/useSearchKeyboardSelection';
|
||||||
import { defaultFileUploadQuery, GrafanaQuery } from 'app/plugins/datasource/grafana/types';
|
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
|
||||||
|
|
||||||
import { useDatasource, useDatasources } from '../../hooks';
|
import { useDatasource, useDatasources } from '../../hooks';
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ export const INTERACTION_ITEM = {
|
|||||||
SEARCH: 'search',
|
SEARCH: 'search',
|
||||||
OPEN_DROPDOWN: 'open_dspicker',
|
OPEN_DROPDOWN: 'open_dspicker',
|
||||||
SELECT_DS: 'select_ds',
|
SELECT_DS: 'select_ds',
|
||||||
ADD_FILE: 'add_file',
|
|
||||||
OPEN_ADVANCED_DS_PICKER: 'open_advanced_ds_picker',
|
OPEN_ADVANCED_DS_PICKER: 'open_advanced_ds_picker',
|
||||||
CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state',
|
CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state',
|
||||||
TOGGLE_FAVORITE: 'toggle_favorite',
|
TOGGLE_FAVORITE: 'toggle_favorite',
|
||||||
@@ -58,7 +56,6 @@ export interface DataSourcePickerProps {
|
|||||||
alerting?: boolean;
|
alerting?: boolean;
|
||||||
pluginId?: string;
|
pluginId?: string;
|
||||||
logs?: boolean;
|
logs?: boolean;
|
||||||
uploadFile?: boolean;
|
|
||||||
filter?: (ds: DataSourceInstanceSettings) => boolean;
|
filter?: (ds: DataSourceInstanceSettings) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +96,6 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
|
|||||||
// Used to move the focus to the footer when tabbing from the input
|
// Used to move the focus to the footer when tabbing from the input
|
||||||
const [footerRef, setFooterRef] = useState<HTMLElement | null>();
|
const [footerRef, setFooterRef] = useState<HTMLElement | null>();
|
||||||
const currentDataSourceInstanceSettings = useDatasource(current);
|
const currentDataSourceInstanceSettings = useDatasource(current);
|
||||||
const grafanaDS = useDatasource('-- Grafana --');
|
|
||||||
const currentValue = Boolean(!current && noDefault) ? undefined : currentDataSourceInstanceSettings;
|
const currentValue = Boolean(!current && noDefault) ? undefined : currentDataSourceInstanceSettings;
|
||||||
const prefixIcon =
|
const prefixIcon =
|
||||||
filterTerm && isOpen ? <DataSourceLogoPlaceHolder /> : <DataSourceLogo dataSource={currentValue} />;
|
filterTerm && isOpen ? <DataSourceLogoPlaceHolder /> : <DataSourceLogo dataSource={currentValue} />;
|
||||||
@@ -178,14 +174,6 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
|
|||||||
markerElement?.focus();
|
markerElement?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickAddCSV() {
|
|
||||||
if (!grafanaDS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(grafanaDS, [defaultFileUploadQuery]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyDownInput(keyEvent: React.KeyboardEvent<HTMLInputElement>) {
|
function onKeyDownInput(keyEvent: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
// From the input, it navigates to the footer
|
// From the input, it navigates to the footer
|
||||||
if (keyEvent.key === 'Tab' && !keyEvent.shiftKey && isOpen) {
|
if (keyEvent.key === 'Tab' && !keyEvent.shiftKey && isOpen) {
|
||||||
@@ -302,7 +290,6 @@ export function DataSourcePicker(props: DataSourcePickerProps) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onClickAddCSV={onClickAddCSV}
|
|
||||||
onDismiss={onClose}
|
onDismiss={onClose}
|
||||||
onNavigateOutsiteFooter={onNavigateOutsiteFooter}
|
onNavigateOutsiteFooter={onNavigateOutsiteFooter}
|
||||||
dataSources={dataSources}
|
dataSources={dataSources}
|
||||||
@@ -335,7 +322,6 @@ function getStylesDropdown(theme: GrafanaTheme2, props: DataSourcePickerProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PickerContentProps extends DataSourcePickerProps {
|
export interface PickerContentProps extends DataSourcePickerProps {
|
||||||
onClickAddCSV?: () => void;
|
|
||||||
keyboardEvents: Observable<React.KeyboardEvent>;
|
keyboardEvents: Observable<React.KeyboardEvent>;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
filterTerm?: string;
|
filterTerm?: string;
|
||||||
@@ -348,7 +334,7 @@ export interface PickerContentProps extends DataSourcePickerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((props, ref) => {
|
const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((props, ref) => {
|
||||||
const { filterTerm, onChange, onClose, onClickAddCSV, current, filter, dataSources, favoriteDataSources } = props;
|
const { filterTerm, onChange, current, filter, dataSources, favoriteDataSources } = props;
|
||||||
|
|
||||||
const changeCallback = useCallback(
|
const changeCallback = useCallback(
|
||||||
(ds: DataSourceInstanceSettings) => {
|
(ds: DataSourceInstanceSettings) => {
|
||||||
@@ -357,12 +343,6 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
|
|||||||
[onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const clickAddCSVCallback = useCallback(() => {
|
|
||||||
onClickAddCSV?.();
|
|
||||||
onClose();
|
|
||||||
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.ADD_FILE });
|
|
||||||
}, [onClickAddCSV, onClose]);
|
|
||||||
|
|
||||||
const styles = useStyles2(getStylesPickerContent);
|
const styles = useStyles2(getStylesPickerContent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -385,12 +365,7 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
|
|||||||
></DataSourceList>
|
></DataSourceList>
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
<FocusScope>
|
<FocusScope>
|
||||||
<Footer
|
<Footer {...props} onChange={changeCallback} onNavigateOutsiteFooter={props.onNavigateOutsiteFooter} />
|
||||||
{...props}
|
|
||||||
onClickAddCSV={clickAddCSVCallback}
|
|
||||||
onChange={changeCallback}
|
|
||||||
onNavigateOutsiteFooter={props.onNavigateOutsiteFooter}
|
|
||||||
/>
|
|
||||||
</FocusScope>
|
</FocusScope>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -427,20 +402,14 @@ function getStylesPickerContent(theme: GrafanaTheme2) {
|
|||||||
|
|
||||||
export interface FooterProps extends PickerContentProps {}
|
export interface FooterProps extends PickerContentProps {}
|
||||||
|
|
||||||
function Footer({ onClose, onChange, onClickAddCSV, ...props }: FooterProps) {
|
function Footer({ onClose, onChange, ...props }: FooterProps) {
|
||||||
const styles = useStyles2(getStylesFooter);
|
const styles = useStyles2(getStylesFooter);
|
||||||
const isUploadFileEnabled = props.uploadFile && config.featureToggles.editPanelCSVDragAndDrop;
|
|
||||||
|
|
||||||
const onKeyDownLastButton = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
const onKeyDownLastButton = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||||
if (e.key === 'Tab') {
|
if (e.key === 'Tab') {
|
||||||
props.onNavigateOutsiteFooter(e);
|
props.onNavigateOutsiteFooter(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onKeyDownFirstButton = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
||||||
if (e.key === 'Tab' && e.shiftKey) {
|
|
||||||
props.onNavigateOutsiteFooter(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
@@ -465,7 +434,6 @@ function Footer({ onClose, onChange, onClickAddCSV, ...props }: FooterProps) {
|
|||||||
pluginId: props.pluginId,
|
pluginId: props.pluginId,
|
||||||
logs: props.logs,
|
logs: props.logs,
|
||||||
filter: props.filter,
|
filter: props.filter,
|
||||||
uploadFile: props.uploadFile,
|
|
||||||
current: props.current,
|
current: props.current,
|
||||||
onDismiss: hideModal,
|
onDismiss: hideModal,
|
||||||
onChange: (ds, defaultQueries) => {
|
onChange: (ds, defaultQueries) => {
|
||||||
@@ -477,18 +445,13 @@ function Footer({ onClose, onChange, onClickAddCSV, ...props }: FooterProps) {
|
|||||||
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.OPEN_ADVANCED_DS_PICKER });
|
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.OPEN_ADVANCED_DS_PICKER });
|
||||||
}}
|
}}
|
||||||
ref={props.footerRef}
|
ref={props.footerRef}
|
||||||
onKeyDown={isUploadFileEnabled ? onKeyDownFirstButton : onKeyDownLastButton}
|
onKeyDown={onKeyDownLastButton}
|
||||||
>
|
>
|
||||||
<Trans i18nKey="data-source-picker.open-advanced-button">Open advanced data source picker</Trans>
|
<Trans i18nKey="data-source-picker.open-advanced-button">Open advanced data source picker</Trans>
|
||||||
<Icon name="arrow-right" />
|
<Icon name="arrow-right" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ModalsController>
|
</ModalsController>
|
||||||
{isUploadFileEnabled && (
|
|
||||||
<Button variant="secondary" size="sm" onClick={onClickAddCSV} onKeyDown={onKeyDownLastButton}>
|
|
||||||
<Trans i18nKey="datasources.footer.add-csv-or-spreadsheet">Add csv or spreadsheet</Trans>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,7 +499,6 @@ function DataSourcePickerWithPrompt({ options, onChange, ...otherProps }: DataSo
|
|||||||
dashboard: true,
|
dashboard: true,
|
||||||
variables: true,
|
variables: true,
|
||||||
current: options.dataSource,
|
current: options.dataSource,
|
||||||
uploadFile: true,
|
|
||||||
onChange: async (ds: DataSourceInstanceSettings, defaultQueries?: DataQuery[] | GrafanaQuery[]) => {
|
onChange: async (ds: DataSourceInstanceSettings, defaultQueries?: DataQuery[] | GrafanaQuery[]) => {
|
||||||
await onChange(ds, defaultQueries);
|
await onChange(ds, defaultQueries);
|
||||||
setIsDataSourceModalOpen(false);
|
setIsDataSourceModalOpen(false);
|
||||||
|
|||||||
@@ -1,39 +1,20 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { PureComponent } from 'react';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { DropEvent, FileRejection } from 'react-dropzone';
|
|
||||||
|
|
||||||
import {
|
import { QueryEditorProps, SelectableValue, rangeUtil, DataQueryRequest, Field } from '@grafana/data';
|
||||||
QueryEditorProps,
|
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||||
SelectableValue,
|
|
||||||
rangeUtil,
|
|
||||||
DataQueryRequest,
|
|
||||||
DataFrameJSON,
|
|
||||||
dataFrameToJSON,
|
|
||||||
GrafanaTheme2,
|
|
||||||
getValueFormat,
|
|
||||||
formattedValueToString,
|
|
||||||
Field,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
|
||||||
import {
|
import {
|
||||||
InlineField,
|
InlineField,
|
||||||
Select,
|
Select,
|
||||||
Alert,
|
Alert,
|
||||||
Input,
|
Input,
|
||||||
InlineFieldRow,
|
InlineFieldRow,
|
||||||
InlineLabel,
|
|
||||||
FileDropzone,
|
|
||||||
FileDropzoneDefaultChildren,
|
|
||||||
DropzoneFile,
|
|
||||||
Themeable2,
|
Themeable2,
|
||||||
withTheme2,
|
withTheme2,
|
||||||
Stack,
|
Stack,
|
||||||
|
InlineLabel,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { hasAlphaPanels } from 'app/core/config';
|
import { hasAlphaPanels } from 'app/core/config';
|
||||||
import { acceptedFiles, maxFileSize } from 'app/features/dataframe-import/constants';
|
|
||||||
import { filesToDataframes } from 'app/features/dataframe-import/utils';
|
|
||||||
import { getManagedChannelInfo } from 'app/features/live/info';
|
import { getManagedChannelInfo } from 'app/features/live/info';
|
||||||
import { SearchQuery } from 'app/features/search/service/types';
|
import { SearchQuery } from 'app/features/search/service/types';
|
||||||
|
|
||||||
@@ -53,7 +34,7 @@ interface State {
|
|||||||
folders?: Array<SelectableValue<string>>;
|
folders?: Array<SelectableValue<string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnthemedQueryEditor extends PureComponent<Props, State> {
|
export class UnthemedQueryEditor extends React.PureComponent<Props, State> {
|
||||||
state: State = { channels: [], channelFields: {} };
|
state: State = { channels: [], channelFields: {} };
|
||||||
|
|
||||||
queryTypes: Array<SelectableValue<GrafanaQueryType>> = [
|
queryTypes: Array<SelectableValue<GrafanaQueryType>> = [
|
||||||
@@ -91,13 +72,6 @@ export class UnthemedQueryEditor extends PureComponent<Props, State> {
|
|||||||
description: 'Search for grafana resources',
|
description: 'Search for grafana resources',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (config.featureToggles.editPanelCSVDragAndDrop) {
|
|
||||||
this.queryTypes.push({
|
|
||||||
label: 'Spreadsheet or snapshot',
|
|
||||||
value: GrafanaQueryType.Snapshot,
|
|
||||||
description: 'Query an uploaded spreadsheet or a snapshot',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadChannelInfo() {
|
loadChannelInfo() {
|
||||||
@@ -356,43 +330,8 @@ export class UnthemedQueryEditor extends PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip rendering the file list as we're handling that in this component instead.
|
|
||||||
fileListRenderer = (file: DropzoneFile, removeFile: (file: DropzoneFile) => void) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
onFileDrop = (acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent) => {
|
|
||||||
filesToDataframes(acceptedFiles).subscribe((next) => {
|
|
||||||
const snapshot: DataFrameJSON[] = [];
|
|
||||||
next.dataFrames.forEach((df) => {
|
|
||||||
const dataframeJson = dataFrameToJSON(df);
|
|
||||||
snapshot.push(dataframeJson);
|
|
||||||
});
|
|
||||||
this.props.onChange({
|
|
||||||
...this.props.query,
|
|
||||||
file: { name: next.file.name, size: next.file.size },
|
|
||||||
queryType: GrafanaQueryType.Snapshot,
|
|
||||||
snapshot,
|
|
||||||
});
|
|
||||||
this.props.onRunQuery();
|
|
||||||
|
|
||||||
reportInteraction('grafana_datasource_drop_files', {
|
|
||||||
number_of_files: fileRejections.length + acceptedFiles.length,
|
|
||||||
accepted_files: acceptedFiles.map((a) => {
|
|
||||||
return { type: a.type, size: a.size };
|
|
||||||
}),
|
|
||||||
rejected_files: fileRejections.map((r) => {
|
|
||||||
return { type: r.file.type, size: r.file.size };
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
renderSnapshotQuery() {
|
renderSnapshotQuery() {
|
||||||
const { query, theme } = this.props;
|
const { query } = this.props;
|
||||||
const file = query.file;
|
|
||||||
const styles = getStyles(theme);
|
|
||||||
const fileSize = getValueFormat('decbytes')(file ? file.size : 0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -401,32 +340,6 @@ export class UnthemedQueryEditor extends PureComponent<Props, State> {
|
|||||||
<InlineLabel>{pluralize('frame', query.snapshot?.length ?? 0, true)}</InlineLabel>
|
<InlineLabel>{pluralize('frame', query.snapshot?.length ?? 0, true)}</InlineLabel>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
{config.featureToggles.editPanelCSVDragAndDrop && (
|
|
||||||
<>
|
|
||||||
<FileDropzone
|
|
||||||
readAs="readAsArrayBuffer"
|
|
||||||
fileListRenderer={this.fileListRenderer}
|
|
||||||
options={{
|
|
||||||
onDrop: this.onFileDrop,
|
|
||||||
maxSize: maxFileSize,
|
|
||||||
multiple: false,
|
|
||||||
accept: acceptedFiles,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileDropzoneDefaultChildren
|
|
||||||
primaryText={this.props?.query?.file ? 'Replace file' : 'Drop file here or click to upload'}
|
|
||||||
/>
|
|
||||||
</FileDropzone>
|
|
||||||
{file && (
|
|
||||||
<div className={styles.file}>
|
|
||||||
<span>{file?.name}</span>
|
|
||||||
<span>
|
|
||||||
<span>{formattedValueToString(fileSize)}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -466,7 +379,7 @@ export class UnthemedQueryEditor extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
// Only show "snapshot" when it already exists
|
// Only show "snapshot" when it already exists
|
||||||
let queryTypes = this.queryTypes;
|
let queryTypes = this.queryTypes;
|
||||||
if (queryType === GrafanaQueryType.Snapshot && !config.featureToggles.editPanelCSVDragAndDrop) {
|
if (queryType === GrafanaQueryType.Snapshot) {
|
||||||
queryTypes = [
|
queryTypes = [
|
||||||
...this.queryTypes,
|
...this.queryTypes,
|
||||||
{
|
{
|
||||||
@@ -511,19 +424,3 @@ export class UnthemedQueryEditor extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const QueryEditor = withTheme2(UnthemedQueryEditor);
|
export const QueryEditor = withTheme2(UnthemedQueryEditor);
|
||||||
|
|
||||||
function getStyles(theme: GrafanaTheme2) {
|
|
||||||
return {
|
|
||||||
file: css({
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
border: `1px dashed ${theme.colors.border.medium}`,
|
|
||||||
backgroundColor: theme.colors.background.secondary,
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -53,16 +53,6 @@ export const defaultQuery: GrafanaQuery = {
|
|||||||
queryType: GrafanaQueryType.RandomWalk,
|
queryType: GrafanaQueryType.RandomWalk,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultFileUploadQuery: GrafanaQuery = {
|
|
||||||
refId: 'A',
|
|
||||||
datasource: {
|
|
||||||
type: 'grafana',
|
|
||||||
uid: 'grafana',
|
|
||||||
},
|
|
||||||
queryType: GrafanaQueryType.Snapshot,
|
|
||||||
snapshot: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------------------------------------
|
//----------------------------------------------
|
||||||
// Annotations
|
// Annotations
|
||||||
//----------------------------------------------
|
//----------------------------------------------
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { DropEvent, FileRejection } from 'react-dropzone';
|
|
||||||
|
|
||||||
import { DataFrame, DataFrameJSON, dataFrameToJSON } from '@grafana/data';
|
import { DataFrame, DataFrameJSON, dataFrameToJSON } from '@grafana/data';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
import { GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||||
import { filesToDataframes } from 'app/features/dataframe-import/utils';
|
|
||||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||||
|
|
||||||
import { defaultFileUploadQuery, GrafanaQuery, GrafanaQueryType } from './types';
|
import { GrafanaQuery, GrafanaQueryType } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will show a confirm modal if the current panel does not have a snapshot query.
|
* Will show a confirm modal if the current panel does not have a snapshot query.
|
||||||
@@ -57,18 +54,3 @@ function updateSnapshotData(frames: DataFrame[], panel: PanelModel) {
|
|||||||
|
|
||||||
panel.refresh();
|
panel.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFileDropToQueryHandler(
|
|
||||||
onFileLoaded: (query: GrafanaQuery, fileRejections: FileRejection[]) => void
|
|
||||||
) {
|
|
||||||
return (acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent) => {
|
|
||||||
filesToDataframes(acceptedFiles).subscribe(async (next) => {
|
|
||||||
const snapshot: DataFrameJSON[] = [];
|
|
||||||
next.dataFrames.forEach((df: DataFrame) => {
|
|
||||||
const dataframeJson = dataFrameToJSON(df);
|
|
||||||
snapshot.push(dataframeJson);
|
|
||||||
});
|
|
||||||
onFileLoaded({ ...defaultFileUploadQuery, ...{ snapshot: snapshot, file: next.file } }, fileRejections);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6750,9 +6750,6 @@
|
|||||||
"error-details-link": {
|
"error-details-link": {
|
||||||
"aria-label-more-details-about-the-error": "More details about the error"
|
"aria-label-more-details-about-the-error": "More details about the error"
|
||||||
},
|
},
|
||||||
"footer": {
|
|
||||||
"add-csv-or-spreadsheet": "Add csv or spreadsheet"
|
|
||||||
},
|
|
||||||
"get-enterprise-phantom-plugins": {
|
"get-enterprise-phantom-plugins": {
|
||||||
"description": {
|
"description": {
|
||||||
"adobe-analytics-datasource": "Adobe Analytics datasource",
|
"adobe-analytics-datasource": "Adobe Analytics datasource",
|
||||||
|
|||||||
Reference in New Issue
Block a user