mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore / Query Library: Enable run button (#87882)
* Enable run button * First pass of shared component, tests half-implemented * cleanup
This commit is contained in:
parent
3800b97a5b
commit
c8d237dd56
157
public/app/features/explore/ExploreRunQueryButton.test.tsx
Normal file
157
public/app/features/explore/ExploreRunQueryButton.test.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { DatasourceSrvMock, MockDataSourceApi } from 'test/mocks/datasource_srv';
|
||||
|
||||
import { DataSourceApi } from '@grafana/data';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { ExploreItemState, ExploreState } from 'app/types';
|
||||
|
||||
import { Props, ExploreRunQueryButton } from './ExploreRunQueryButton';
|
||||
import { makeExplorePaneState } from './state/utils';
|
||||
|
||||
interface MockQuery extends DataQuery {
|
||||
query: string;
|
||||
queryText?: string;
|
||||
}
|
||||
|
||||
const lokiDs = {
|
||||
uid: 'loki',
|
||||
name: 'testDs',
|
||||
type: 'loki',
|
||||
meta: { mixed: false },
|
||||
getRef: () => {
|
||||
return { type: 'loki', uid: 'loki' };
|
||||
},
|
||||
} as unknown as DataSourceApi;
|
||||
|
||||
const promDs = {
|
||||
uid: 'prom',
|
||||
name: 'testDs2',
|
||||
type: 'prom',
|
||||
meta: { mixed: false },
|
||||
getRef: () => {
|
||||
return { type: 'prom', uid: 'prom' };
|
||||
},
|
||||
} as unknown as DataSourceApi;
|
||||
|
||||
const datasourceSrv = new DatasourceSrvMock(lokiDs, {
|
||||
prom: promDs,
|
||||
mixed: {
|
||||
uid: 'mixed',
|
||||
name: 'testDSMixed',
|
||||
type: 'mixed',
|
||||
meta: { mixed: true },
|
||||
} as MockDataSourceApi,
|
||||
});
|
||||
|
||||
const getDataSourceSrvMock = jest.fn().mockReturnValue(datasourceSrv);
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => getDataSourceSrvMock(),
|
||||
}));
|
||||
|
||||
const setup = (propOverrides?: Partial<Props>, paneCount = 1) => {
|
||||
const props: Props = {
|
||||
queries: [],
|
||||
rootDatasourceUid: 'loki',
|
||||
setQueries: jest.fn(),
|
||||
changeDatasource: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const panes: Record<string, ExploreItemState | undefined> = {};
|
||||
|
||||
if (paneCount > 0) {
|
||||
panes.left = makeExplorePaneState({ datasourceInstance: lokiDs });
|
||||
}
|
||||
if (paneCount === 2) {
|
||||
panes.right = makeExplorePaneState({ datasourceInstance: lokiDs });
|
||||
}
|
||||
|
||||
const store = configureStore({
|
||||
explore: {
|
||||
panes,
|
||||
} as unknown as ExploreState,
|
||||
});
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ExploreRunQueryButton {...props} />
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('ExploreRunQueryButton', () => {
|
||||
it('should disable run query button if there are no explore IDs', async () => {
|
||||
setup({}, 0);
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
expect(runQueryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be disabled if the root datasource is undefined (invalid datasource)', async () => {
|
||||
setup({
|
||||
rootDatasourceUid: undefined,
|
||||
});
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
expect(runQueryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be disabled if property is set', async () => {
|
||||
setup({
|
||||
disabled: true,
|
||||
});
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
expect(runQueryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should set new queries without changing DS when running queries from the same datasource', async () => {
|
||||
const setQueries = jest.fn();
|
||||
const changeDatasource = jest.fn();
|
||||
const queries: MockQuery[] = [
|
||||
{ query: 'query1', refId: 'A', datasource: { uid: 'loki' } },
|
||||
{ query: 'query2', refId: 'B', datasource: { uid: 'loki' } },
|
||||
];
|
||||
setup({
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
rootDatasourceUid: 'loki',
|
||||
queries,
|
||||
});
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
await userEvent.click(runQueryButton);
|
||||
|
||||
expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries);
|
||||
expect(changeDatasource).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should change datasource to mixed and set new queries when running queries from mixed datasource', async () => {
|
||||
const setQueries = jest.fn();
|
||||
const changeDatasource = jest.fn();
|
||||
const queries: MockQuery[] = [
|
||||
{ query: 'query1', refId: 'A', datasource: { type: 'loki', uid: 'loki' } },
|
||||
{ query: 'query2', refId: 'B', datasource: { type: 'prometheus', uid: 'prometheus' } },
|
||||
];
|
||||
setup({
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
rootDatasourceUid: 'mixed',
|
||||
queries,
|
||||
});
|
||||
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
await userEvent.click(runQueryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries);
|
||||
expect(changeDatasource).toHaveBeenCalledWith({ datasource: 'mixed', exploreId: 'left' });
|
||||
});
|
||||
});
|
||||
});
|
126
public/app/features/explore/ExploreRunQueryButton.tsx
Normal file
126
public/app/features/explore/ExploreRunQueryButton.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ConnectedProps, connect } from 'react-redux';
|
||||
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { Button, Dropdown, Menu, ToolbarButton } from '@grafana/ui';
|
||||
import { t } from '@grafana/ui/src/utils/i18n';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { changeDatasource } from './state/datasource';
|
||||
import { setQueries } from './state/query';
|
||||
import { isSplit, selectExploreDSMaps, selectPanesEntries } from './state/selectors';
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
};
|
||||
|
||||
const connector = connect(undefined, mapDispatchToProps);
|
||||
|
||||
interface ExploreRunQueryButtonProps {
|
||||
queries: DataQuery[];
|
||||
rootDatasourceUid?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export type Props = ConnectedProps<typeof connector> & ExploreRunQueryButtonProps;
|
||||
|
||||
/*
|
||||
This component does not validate datasources before running them. Root datasource validation should happen outside this component and can pass in an undefined if invalid
|
||||
If query level validation is done and a query datasource is invalid, pass in disabled = true
|
||||
*/
|
||||
|
||||
export function ExploreRunQueryButton({
|
||||
rootDatasourceUid,
|
||||
queries,
|
||||
disabled = false,
|
||||
changeDatasource,
|
||||
setQueries,
|
||||
}: Props) {
|
||||
const [openRunQueryButton, setOpenRunQueryButton] = useState(false);
|
||||
const isPaneSplit = useSelector(isSplit);
|
||||
const exploreActiveDS = useSelector(selectExploreDSMaps);
|
||||
const panesEntries = useSelector(selectPanesEntries);
|
||||
|
||||
const isDifferentDatasource = (uid: string, exploreId: string) =>
|
||||
!exploreActiveDS.dsToExplore.find((di) => di.datasource.uid === uid)?.exploreIds.includes(exploreId);
|
||||
|
||||
// exploreId on where the query will be ran, and the datasource ID for the item's DS
|
||||
const runQueryText = (exploreId: string, dsUid?: string) => {
|
||||
// if the datasource or exploreID is undefined, it will be disabled, but give it default query button text
|
||||
return dsUid !== undefined && exploreId !== undefined && isDifferentDatasource(dsUid, exploreId)
|
||||
? {
|
||||
fallbackText: 'Switch data source and run query',
|
||||
translation: t('explore.run-query.switch-datasource-button', 'Switch data source and run query'),
|
||||
}
|
||||
: {
|
||||
fallbackText: 'Run query',
|
||||
translation: t('explore.run-query.run-query-button', 'Run query'),
|
||||
};
|
||||
};
|
||||
|
||||
const runQuery = async (exploreId: string) => {
|
||||
const differentDataSource = isDifferentDatasource(rootDatasourceUid!, exploreId);
|
||||
if (differentDataSource) {
|
||||
await changeDatasource({ exploreId, datasource: rootDatasourceUid! });
|
||||
}
|
||||
setQueries(exploreId, queries);
|
||||
|
||||
reportInteraction('grafana_explore_query_history_run', {
|
||||
queryHistoryEnabled: config.queryHistoryEnabled,
|
||||
differentDataSource,
|
||||
});
|
||||
};
|
||||
|
||||
const runButton = () => {
|
||||
const isInvalid = disabled || queries.length === 0 || rootDatasourceUid === undefined;
|
||||
if (!isPaneSplit) {
|
||||
const exploreId = exploreActiveDS.exploreToDS[0]?.exploreId; // may be undefined if explore is refreshed while the pane is up
|
||||
const buttonText = runQueryText(exploreId, rootDatasourceUid);
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label={buttonText.translation}
|
||||
onClick={() => runQuery(exploreId)}
|
||||
disabled={isInvalid || exploreId === undefined}
|
||||
>
|
||||
{buttonText.translation}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
const menu = (
|
||||
<Menu>
|
||||
{panesEntries.map((pane, i) => {
|
||||
const buttonText = runQueryText(pane[0], rootDatasourceUid);
|
||||
const paneLabel =
|
||||
i === 0 ? t('explore.run-query.left-pane', 'Left pane') : t('explore.run-query.right-pane', 'Right pane');
|
||||
return (
|
||||
<Menu.Item
|
||||
key={i}
|
||||
ariaLabel={buttonText.fallbackText}
|
||||
onClick={() => {
|
||||
runQuery(pane[0]);
|
||||
}}
|
||||
label={`${paneLabel}: ${buttonText.translation}`}
|
||||
disabled={isInvalid || pane[0] === undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown onVisibleChange={(state) => setOpenRunQueryButton(state)} placement="bottom-start" overlay={menu}>
|
||||
<ToolbarButton aria-label="run query options" variant="canvas" isOpen={openRunQueryButton}>
|
||||
{t('explore.run-query.run-query-button', 'Run query')}
|
||||
</ToolbarButton>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return <>{runButton()}</>;
|
||||
}
|
||||
|
||||
export default connector(ExploreRunQueryButton);
|
@ -1,13 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from '@grafana/ui';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
|
||||
export function ActionsCell() {
|
||||
return (
|
||||
<>
|
||||
<Button disabled={true} variant="primary">
|
||||
Run
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
import ExploreRunQueryButton from '../../ExploreRunQueryButton';
|
||||
|
||||
interface ActionsCellProps {
|
||||
query?: DataQuery;
|
||||
rootDatasourceUid?: string;
|
||||
}
|
||||
|
||||
function ActionsCell({ query, rootDatasourceUid }: ActionsCellProps) {
|
||||
return <ExploreRunQueryButton queries={query ? [query] : []} rootDatasourceUid={rootDatasourceUid} />;
|
||||
}
|
||||
|
||||
export default ActionsCell;
|
||||
|
@ -4,7 +4,7 @@ import { SortByFn } from 'react-table';
|
||||
|
||||
import { Column, InteractiveTable } from '@grafana/ui';
|
||||
|
||||
import { ActionsCell } from './ActionsCell';
|
||||
import ActionsCell from './ActionsCell';
|
||||
import { AddedByCell } from './AddedByCell';
|
||||
import { DatasourceTypeCell } from './DatasourceTypeCell';
|
||||
import { DateAddedCell } from './DateAddedCell';
|
||||
@ -22,7 +22,13 @@ const columns: Array<Column<QueryTemplateRow>> = [
|
||||
{ id: 'addedBy', header: 'Added by', cell: AddedByCell },
|
||||
{ id: 'datasourceType', header: 'Datasource type', cell: DatasourceTypeCell, sortType: 'string' },
|
||||
{ id: 'createdAtTimestamp', header: 'Date added', cell: DateAddedCell, sortType: timestampSort },
|
||||
{ id: 'actions', header: '', cell: ActionsCell },
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
cell: ({ row: { original } }) => (
|
||||
<ActionsCell query={original.query} rootDatasourceUid={original.datasourceRef?.uid} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const styles = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, getByText, waitFor } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, getByText } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
@ -213,12 +213,6 @@ describe('RichHistoryCard', () => {
|
||||
expect(datasourceName).toHaveTextContent('Data source does not exist anymore');
|
||||
});
|
||||
|
||||
it('should disable run query button if there are no explore IDs', async () => {
|
||||
setup({}, true);
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
expect(runQueryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
describe('copy queries to clipboard', () => {
|
||||
it('should copy query model to clipboard when copying a query from a non existent datasource', async () => {
|
||||
setup({
|
||||
@ -304,117 +298,6 @@ describe('RichHistoryCard', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('run queries', () => {
|
||||
it('should be disabled if at least one query datasource is missing when using mixed', async () => {
|
||||
const setQueries = jest.fn();
|
||||
const changeDatasource = jest.fn();
|
||||
const queries: MockQuery[] = [
|
||||
{ query: 'query1', refId: 'A', datasource: { uid: 'nonexistent-ds' } },
|
||||
{ query: 'query2', refId: 'B', datasource: { uid: 'loki' } },
|
||||
];
|
||||
setup({
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
queryHistoryItem: {
|
||||
id: '2',
|
||||
createdAt: 1,
|
||||
datasourceUid: 'mixed',
|
||||
datasourceName: 'Mixed',
|
||||
starred: false,
|
||||
comment: '',
|
||||
queries,
|
||||
},
|
||||
});
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
|
||||
expect(runQueryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be disabled if at datasource is missing', async () => {
|
||||
const setQueries = jest.fn();
|
||||
const changeDatasource = jest.fn();
|
||||
const queries: MockQuery[] = [
|
||||
{ query: 'query1', refId: 'A' },
|
||||
{ query: 'query2', refId: 'B' },
|
||||
];
|
||||
setup({
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
queryHistoryItem: {
|
||||
id: '2',
|
||||
createdAt: 1,
|
||||
datasourceUid: 'nonexistent-ds',
|
||||
datasourceName: 'nonexistent-ds',
|
||||
starred: false,
|
||||
comment: '',
|
||||
queries,
|
||||
},
|
||||
});
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
|
||||
expect(runQueryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should only set new queries when running queries from the same datasource', async () => {
|
||||
const setQueries = jest.fn();
|
||||
const changeDatasource = jest.fn();
|
||||
const queries: MockQuery[] = [
|
||||
{ query: 'query1', refId: 'A' },
|
||||
{ query: 'query2', refId: 'B' },
|
||||
];
|
||||
setup({
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
queryHistoryItem: {
|
||||
id: '2',
|
||||
createdAt: 1,
|
||||
datasourceUid: 'loki',
|
||||
datasourceName: 'Loki',
|
||||
starred: false,
|
||||
comment: '',
|
||||
queries,
|
||||
},
|
||||
});
|
||||
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
await userEvent.click(runQueryButton);
|
||||
|
||||
expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries);
|
||||
expect(changeDatasource).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should change datasource to mixed and set new queries when running queries from mixed datasource', async () => {
|
||||
const setQueries = jest.fn();
|
||||
const changeDatasource = jest.fn();
|
||||
const queries: MockQuery[] = [
|
||||
{ query: 'query1', refId: 'A', datasource: { type: 'loki', uid: 'loki' } },
|
||||
{ query: 'query2', refId: 'B', datasource: { type: 'prometheus', uid: 'prometheus' } },
|
||||
];
|
||||
setup({
|
||||
setQueries,
|
||||
changeDatasource,
|
||||
queryHistoryItem: {
|
||||
id: '2',
|
||||
createdAt: 1,
|
||||
datasourceUid: 'mixed',
|
||||
datasourceName: 'Mixed',
|
||||
starred: false,
|
||||
comment: '',
|
||||
queries,
|
||||
},
|
||||
datasourceInstances: [dsStore.loki, dsStore.prometheus, dsStore.mixed],
|
||||
});
|
||||
|
||||
const runQueryButton = await screen.findByRole('button', { name: /run query/i });
|
||||
await userEvent.click(runQueryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries);
|
||||
expect(changeDatasource).toHaveBeenCalledWith({ datasource: 'mixed', exploreId: 'left' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('commenting', () => {
|
||||
it('should render comment, if comment present', async () => {
|
||||
setup({ queryHistoryItem: starredQueryWithComment });
|
||||
|
@ -5,7 +5,7 @@ import { connect, ConnectedProps } from 'react-redux';
|
||||
import { GrafanaTheme2, DataSourceApi } from '@grafana/data';
|
||||
import { config, reportInteraction, getAppEvents } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { TextArea, Button, IconButton, useStyles2, ToolbarButton, Dropdown, Menu } from '@grafana/ui';
|
||||
import { TextArea, Button, IconButton, useStyles2 } from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
@ -16,11 +16,10 @@ import { changeDatasource } from 'app/features/explore/state/datasource';
|
||||
import { starHistoryItem, commentHistoryItem, deleteHistoryItem } from 'app/features/explore/state/history';
|
||||
import { setQueries } from 'app/features/explore/state/query';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { useSelector } from 'app/types';
|
||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
import { RichHistoryQuery } from 'app/types/explore';
|
||||
|
||||
import { isSplit, selectExploreDSMaps, selectPanesEntries } from '../state/selectors';
|
||||
import ExploreRunQueryButton from '../ExploreRunQueryButton';
|
||||
|
||||
const mapDispatchToProps = {
|
||||
changeDatasource,
|
||||
@ -134,46 +133,16 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
};
|
||||
|
||||
export function RichHistoryCard(props: Props) {
|
||||
const {
|
||||
queryHistoryItem,
|
||||
commentHistoryItem,
|
||||
starHistoryItem,
|
||||
deleteHistoryItem,
|
||||
changeDatasource,
|
||||
setQueries,
|
||||
datasourceInstances,
|
||||
} = props;
|
||||
const { queryHistoryItem, commentHistoryItem, starHistoryItem, deleteHistoryItem, datasourceInstances } = props;
|
||||
|
||||
const [activeUpdateComment, setActiveUpdateComment] = useState(false);
|
||||
const [openRunQueryButton, setOpenRunQueryButton] = useState(false);
|
||||
const [comment, setComment] = useState<string | undefined>(queryHistoryItem.comment);
|
||||
const panesEntries = useSelector(selectPanesEntries);
|
||||
const exploreActiveDS = useSelector(selectExploreDSMaps);
|
||||
const isPaneSplit = useSelector(isSplit);
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const cardRootDatasource = datasourceInstances
|
||||
? datasourceInstances.find((di) => di.uid === queryHistoryItem.datasourceUid)
|
||||
: undefined;
|
||||
|
||||
const isDifferentDatasource = (uid: string, exploreId: string) =>
|
||||
!exploreActiveDS.dsToExplore.find((di) => di.datasource.uid === uid)?.exploreIds.includes(exploreId);
|
||||
|
||||
const onRunQuery = async (exploreId: string) => {
|
||||
const queriesToRun = queryHistoryItem.queries;
|
||||
const differentDataSource = isDifferentDatasource(queryHistoryItem.datasourceUid, exploreId);
|
||||
if (differentDataSource) {
|
||||
await changeDatasource({ exploreId, datasource: queryHistoryItem.datasourceUid });
|
||||
}
|
||||
setQueries(exploreId, queriesToRun);
|
||||
|
||||
reportInteraction('grafana_explore_query_history_run', {
|
||||
queryHistoryEnabled: config.queryHistoryEnabled,
|
||||
differentDataSource,
|
||||
});
|
||||
};
|
||||
|
||||
const onCopyQuery = async () => {
|
||||
const datasources = [...queryHistoryItem.queries.map((query) => query.datasource?.type || 'unknown')];
|
||||
reportInteraction('grafana_explore_query_history_copy_query', {
|
||||
@ -344,68 +313,6 @@ export function RichHistoryCard(props: Props) {
|
||||
</div>
|
||||
);
|
||||
|
||||
// exploreId on where the query will be ran, and the datasource ID for the item's DS
|
||||
const runQueryText = (exploreId: string, dsUid: string) => {
|
||||
return dsUid !== undefined && exploreId !== undefined && isDifferentDatasource(dsUid, exploreId)
|
||||
? {
|
||||
fallbackText: 'Switch data source and run query',
|
||||
translation: t('explore.rich-history-card.switch-datasource-button', 'Switch data source and run query'),
|
||||
}
|
||||
: {
|
||||
fallbackText: 'Run query',
|
||||
translation: t('explore.rich-history-card.run-query-button', 'Run query'),
|
||||
};
|
||||
};
|
||||
|
||||
const runButton = () => {
|
||||
const disabled = cardRootDatasource?.uid === undefined;
|
||||
if (!isPaneSplit) {
|
||||
const exploreId = exploreActiveDS.exploreToDS[0]?.exploreId; // may be undefined if explore is refreshed while the pane is up
|
||||
const buttonText = runQueryText(exploreId, props.queryHistoryItem.datasourceUid);
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label={buttonText.translation}
|
||||
onClick={() => onRunQuery(exploreId)}
|
||||
disabled={disabled || exploreId === undefined}
|
||||
>
|
||||
{buttonText.translation}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
const menu = (
|
||||
<Menu>
|
||||
{panesEntries.map((pane, i) => {
|
||||
const buttonText = runQueryText(pane[0], props.queryHistoryItem.datasourceUid);
|
||||
const paneLabel =
|
||||
i === 0
|
||||
? t('explore.rich-history-card.left-pane', 'Left pane')
|
||||
: t('explore.rich-history-card.right-pane', 'Right pane');
|
||||
return (
|
||||
<Menu.Item
|
||||
key={i}
|
||||
ariaLabel={buttonText.fallbackText}
|
||||
onClick={() => {
|
||||
onRunQuery(pane[0]);
|
||||
}}
|
||||
label={`${paneLabel}: ${buttonText.translation}`}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown onVisibleChange={(state) => setOpenRunQueryButton(state)} placement="bottom-start" overlay={menu}>
|
||||
<ToolbarButton aria-label="run query options" variant="canvas" isOpen={openRunQueryButton}>
|
||||
{t('explore.rich-history-card.run-query-button', 'Run query')}
|
||||
</ToolbarButton>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.queryCard}>
|
||||
<div className={styles.cardRow}>
|
||||
@ -435,7 +342,11 @@ export function RichHistoryCard(props: Props) {
|
||||
)}
|
||||
{activeUpdateComment && updateComment}
|
||||
</div>
|
||||
{!activeUpdateComment && <div className={styles.runButton}>{runButton()}</div>}
|
||||
{!activeUpdateComment && (
|
||||
<div className={styles.runButton}>
|
||||
<ExploreRunQueryButton queries={queryHistoryItem.queries} rootDatasourceUid={cardRootDatasource?.uid} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -491,15 +491,11 @@
|
||||
"delete-query-tooltip": "Delete query",
|
||||
"delete-starred-query-confirmation-text": "Are you sure you want to permanently delete your starred query?",
|
||||
"edit-comment-tooltip": "Edit comment",
|
||||
"left-pane": "Left pane",
|
||||
"optional-description": "An optional description of what the query does.",
|
||||
"query-comment-label": "Query comment",
|
||||
"query-text-label": "Query text",
|
||||
"right-pane": "Right pane",
|
||||
"run-query-button": "Run query",
|
||||
"save-comment": "Save comment",
|
||||
"star-query-tooltip": "Star query",
|
||||
"switch-datasource-button": "Switch data source and run query",
|
||||
"unstar-query-tooltip": "Unstar query",
|
||||
"update-comment-form": "Update comment form"
|
||||
},
|
||||
@ -568,6 +564,12 @@
|
||||
"saving-failed": "Saving rich history failed",
|
||||
"update-failed": "Rich History update failed"
|
||||
},
|
||||
"run-query": {
|
||||
"left-pane": "Left pane",
|
||||
"right-pane": "Right pane",
|
||||
"run-query-button": "Run query",
|
||||
"switch-datasource-button": "Switch data source and run query"
|
||||
},
|
||||
"secondary-actions": {
|
||||
"query-add-button": "Add query",
|
||||
"query-add-button-aria-label": "Add query",
|
||||
|
@ -491,15 +491,11 @@
|
||||
"delete-query-tooltip": "Đęľęŧę qūęřy",
|
||||
"delete-starred-query-confirmation-text": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő pęřmäʼnęʼnŧľy đęľęŧę yőūř şŧäřřęđ qūęřy?",
|
||||
"edit-comment-tooltip": "Ēđįŧ čőmmęʼnŧ",
|
||||
"left-pane": "Ŀęƒŧ päʼnę",
|
||||
"optional-description": "Åʼn őpŧįőʼnäľ đęşčřįpŧįőʼn őƒ ŵĥäŧ ŧĥę qūęřy đőęş.",
|
||||
"query-comment-label": "Qūęřy čőmmęʼnŧ",
|
||||
"query-text-label": "Qūęřy ŧęχŧ",
|
||||
"right-pane": "Ŗįģĥŧ päʼnę",
|
||||
"run-query-button": "Ŗūʼn qūęřy",
|
||||
"save-comment": "Ŝävę čőmmęʼnŧ",
|
||||
"star-query-tooltip": "Ŝŧäř qūęřy",
|
||||
"switch-datasource-button": "Ŝŵįŧčĥ đäŧä şőūřčę äʼnđ řūʼn qūęřy",
|
||||
"unstar-query-tooltip": "Ůʼnşŧäř qūęřy",
|
||||
"update-comment-form": "Ůpđäŧę čőmmęʼnŧ ƒőřm"
|
||||
},
|
||||
@ -568,6 +564,12 @@
|
||||
"saving-failed": "Ŝävįʼnģ řįčĥ ĥįşŧőřy ƒäįľęđ",
|
||||
"update-failed": "Ŗįčĥ Ħįşŧőřy ūpđäŧę ƒäįľęđ"
|
||||
},
|
||||
"run-query": {
|
||||
"left-pane": "Ŀęƒŧ päʼnę",
|
||||
"right-pane": "Ŗįģĥŧ päʼnę",
|
||||
"run-query-button": "Ŗūʼn qūęřy",
|
||||
"switch-datasource-button": "Ŝŵįŧčĥ đäŧä şőūřčę äʼnđ řūʼn qūęřy"
|
||||
},
|
||||
"secondary-actions": {
|
||||
"query-add-button": "Åđđ qūęřy",
|
||||
"query-add-button-aria-label": "Åđđ qūęřy",
|
||||
|
Loading…
Reference in New Issue
Block a user