Skip to content

Commit

Permalink
feat: saved query list actions (#11109)
Browse files Browse the repository at this point in the history
  • Loading branch information
riahk authored Oct 1, 2020
1 parent 5b284e6 commit e7a4265
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import SavedQueryList from 'src/views/CRUD/data/savedquery/SavedQueryList';
import SubMenu from 'src/components/Menu/SubMenu';
import ListView from 'src/components/ListView';
import Filters from 'src/components/ListView/Filters';
import ActionsBar from 'src/components/ListView/ActionsBar';
import DeleteModal from 'src/components/DeleteModal';
import Button from 'src/components/Button';
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import { act } from 'react-dom/test-utils';

Expand All @@ -34,6 +38,7 @@ const store = mockStore({});

const queriesInfoEndpoint = 'glob:*/api/v1/saved_query/_info*';
const queriesEndpoint = 'glob:*/api/v1/saved_query/?*';
const queryEndpoint = 'glob:*/api/v1/saved_query/*';
const queriesRelatedEndpoint = 'glob:*/api/v1/saved_query/related/database?*';
const queriesDistinctEndpoint = 'glob:*/api/v1/saved_query/distinct/schema?*';

Expand All @@ -51,6 +56,7 @@ const mockqueries = [...new Array(3)].map((_, i) => ({
changed_on_delta_humanized: '1 day ago',
db_id: i,
description: `SQL for ${i}`,
id: i,
label: `query ${i}`,
schema: 'public',
sql: `SELECT ${i} FROM table`,
Expand All @@ -71,6 +77,9 @@ fetchMock.get(queriesEndpoint, {
count: 3,
});

fetchMock.delete(queryEndpoint, {});
fetchMock.delete(queriesEndpoint, {});

fetchMock.get(queriesRelatedEndpoint, {
count: 0,
result: [],
Expand Down Expand Up @@ -108,6 +117,51 @@ describe('SavedQueryList', () => {
);
});

it('renders ActionsBar in table', () => {
expect(wrapper.find(ActionsBar)).toExist();
expect(wrapper.find(ActionsBar)).toHaveLength(3);
});

it('deletes', async () => {
act(() => {
wrapper.find('span[data-test="delete-action"]').first().props().onClick();
});
await waitForComponentToPaint(wrapper);

expect(
wrapper.find(DeleteModal).first().props().description,
).toMatchInlineSnapshot(
`"This action will permanently delete the saved query."`,
);

act(() => {
wrapper
.find('#delete')
.first()
.props()
.onChange({ target: { value: 'DELETE' } });
});
await waitForComponentToPaint(wrapper);
act(() => {
wrapper.find('button').last().props().onClick();
});

await waitForComponentToPaint(wrapper);

expect(fetchMock.calls(/saved_query\/0/, 'DELETE')).toHaveLength(1);
});

it('shows/hides bulk actions when bulk actions is clicked', async () => {
const button = wrapper.find(Button).at(0);
act(() => {
button.props().onClick();
});
await waitForComponentToPaint(wrapper);
expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
mockqueries.length + 1, // 1 for each row and 1 for select all
);
});

it('searches', async () => {
const filtersWrapper = wrapper.find(Filters);
act(() => {
Expand Down
6 changes: 5 additions & 1 deletion superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,12 @@ class TabbedSqlEditors extends React.PureComponent {
this.props.actions.addQueryEditor(newQueryEditor);
}
this.popNewTab();
} else if (this.props.queryEditors.length === 0) {
} else if (query.new || this.props.queryEditors.length === 0) {
this.newQueryEditor();

if (query.new) {
window.history.replaceState({}, document.title, this.state.sqlLabUrl);
}
} else {
const qe = this.activeQueryEditor();
const latestQuery = this.props.queries[qe.latestQueryId];
Expand Down
2 changes: 1 addition & 1 deletion superset-frontend/src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ import { ReactComponent as WarningIcon } from 'images/icons/warning.svg';
import { ReactComponent as XLargeIcon } from 'images/icons/x-large.svg';
import { ReactComponent as XSmallIcon } from 'images/icons/x-small.svg';

type IconName =
export type IconName =
| 'alert-solid'
| 'alert'
| 'binoculars'
Expand Down
92 changes: 92 additions & 0 deletions superset-frontend/src/components/ListView/ActionsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { styled } from '@superset-ui/core';
import TooltipWrapper from 'src/components/TooltipWrapper';
import Icon, { IconName } from 'src/components/Icon';

export type ActionProps = {
label: string;
tooltip?: string | React.ReactElement;
placement?: string;
icon: IconName;
onClick: () => void;
};

interface ActionsBarProps {
actions: Array<ActionProps>;
}

const StyledActions = styled.span`
white-space: nowrap;
min-width: 100px;
svg,
i {
margin-right: 8px;
&:hover {
path {
fill: ${({ theme }) => theme.colors.primary.base};
}
}
}
`;

export default function ActionsBar({ actions }: ActionsBarProps) {
return (
<StyledActions className="actions">
{actions.map((action, index) => {
if (action.tooltip) {
return (
<TooltipWrapper
label={action.label}
tooltip={action.tooltip}
placement={action.placement || ''}
key={index}
>
<span
role="button"
tabIndex={0}
className="action-button"
data-test={action.label}
onClick={action.onClick}
>
<Icon name={action.icon} />
</span>
</TooltipWrapper>
);
}

return (
<span
role="button"
tabIndex={0}
className="action-button"
onClick={action.onClick}
data-test={action.label}
key={index}
>
<Icon name={action.icon} />
</span>
);
})}
</StyledActions>
);
}
Loading

0 comments on commit e7a4265

Please sign in to comment.