Skip to content

Commit

Permalink
feat(RLS): RESTful apis and react view for RLS (#22325)
Browse files Browse the repository at this point in the history
  • Loading branch information
mayurnewase committed Jan 9, 2023
1 parent 516d56b commit 159dcd7
Show file tree
Hide file tree
Showing 21 changed files with 2,643 additions and 161 deletions.
1 change: 1 addition & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ assists people when migrating to a new version.

- [22328](https://github.com/apache/superset/pull/22328): For deployments that have enabled the "THUMBNAILS" feature flag, the function that calculates dashboard digests has been updated to consider additional properties to more accurately identify changes in the dashboard metadata. This change will invalidate all currently cached dashboard thumbnails.
- [21765](https://github.com/apache/superset/pull/21765): For deployments that have enabled the "ALERT_REPORTS" feature flag, Gamma users will no longer have read and write access to Alerts & Reports by default. To give Gamma users the ability to schedule reports from the Dashboard and Explore view like before, create an additional role with "can read on ReportSchedule" and "can write on ReportSchedule" permissions. To further give Gamma users access to the "Alerts & Reports" menu and CRUD view, add "menu access on Manage" and "menu access on Alerts & Report" permissions to the role.
- [22325](https://github.com/apache/superset/pull/22325): "RLS_FORM_QUERY_REL_FIELDS" is replaced by "RLS_BASE_RELATED_FIELD_FILTERS" feature flag.Its value format stays same.

### Potential Downtime

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/**
* 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 fetchMock from 'fetch-mock';
import RowLevelSecurityList from 'src/views/CRUD/rowlevelsecurity/RowLevelSecurityList';
import { render, screen, within } from 'spec/helpers/testing-library';
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import { QueryParamProvider } from 'use-query-params';
import { styledMount as mount } from 'spec/helpers/theming';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import ListView from 'src/components/ListView/ListView';
import userEvent from '@testing-library/user-event';

const ruleListEndpoint = 'glob:*/api/v1/rowlevelsecurity/?*';
const ruleInfoEndpoint = 'glob:*/api/v1/rowlevelsecurity/_info*';

const mockRules = [
{
changed_on_delta_humanized: '1 days ago',
clause: '1=1',
description: 'some description',
filter_type: 'Regular',
group_key: 'group-1',
id: 1,
name: 'rule 1',
roles: [
{
id: 3,
name: 'Alpha',
},
{
id: 5,
name: 'granter',
},
],
tables: [
{
id: 6,
table_name: 'flights',
},
{
id: 13,
table_name: 'messages',
},
],
},
{
changed_on_delta_humanized: '2 days ago',
clause: '2=2',
description: 'some description 2',
filter_type: 'Base',
group_key: 'group-1',
id: 2,
name: 'rule 2',
roles: [
{
id: 3,
name: 'Alpha',
},
{
id: 5,
name: 'granter',
},
],
tables: [
{
id: 6,
table_name: 'flights',
},
{
id: 13,
table_name: 'messages',
},
],
},
];
fetchMock.get(ruleListEndpoint, { result: mockRules, count: 2 });
fetchMock.get(ruleInfoEndpoint, { permissions: ['can_read', 'can_write'] });
global.URL.createObjectURL = jest.fn();

const mockUser = {
userId: 1,
};

const mockedProps = {};

const mockStore = configureStore([thunk]);
const store = mockStore({});

describe('RulesList Enzyme', () => {
let wrapper: any;

beforeAll(async () => {
fetchMock.resetHistory();
wrapper = mount(
<MemoryRouter>
<Provider store={store}>
<RowLevelSecurityList {...mockedProps} user={mockUser} />
</Provider>
</MemoryRouter>,
);

await waitForComponentToPaint(wrapper);
});

it('renders', () => {
expect(wrapper.find(RowLevelSecurityList)).toExist();
});
it('renders a ListView', () => {
expect(wrapper.find(ListView)).toExist();
});
it('fetched data', () => {
// wrapper.update();
const apiCalls = fetchMock.calls(/rowlevelsecurity\/\?q/);
expect(apiCalls).toHaveLength(1);
expect(apiCalls[0][0]).toMatchInlineSnapshot(
`"http://localhost/api/v1/rowlevelsecurity/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
);
});
});

describe('RuleList RTL', () => {
async function renderAndWait() {
const mounted = act(async () => {
const mockedProps = {};
render(
<MemoryRouter>
<QueryParamProvider>
<RowLevelSecurityList {...mockedProps} user={mockUser} />
</QueryParamProvider>
</MemoryRouter>,
{ useRedux: true },
);
});
return mounted;
}

it('renders add rule button on empty state', async () => {
fetchMock.get(
ruleListEndpoint,
{ result: [], count: 0 },
{ overwriteRoutes: true },
);
await renderAndWait();

const emptyAddRuleButton = await screen.findByTestId('add-rule-empty');
expect(emptyAddRuleButton).toBeInTheDocument();
fetchMock.get(
ruleListEndpoint,
{ result: mockRules, count: 2 },
{ overwriteRoutes: true },
);
});

it('renders a "Rule" button to add a rule in bulk action', async () => {
await renderAndWait();

const addRuleButton = await screen.findByTestId('add-rule');
const emptyAddRuleButton = screen.queryByTestId('add-rule-empty');
expect(addRuleButton).toBeInTheDocument();
expect(emptyAddRuleButton).not.toBeInTheDocument();
});

it('renders filter options', async () => {
await renderAndWait();

const searchFilters = screen.queryAllByTestId('filters-search');
expect(searchFilters).toHaveLength(2);

const typeFilter = await screen.findByTestId('filters-select');
expect(typeFilter).toBeInTheDocument();
});

it('renders correct list columns', async () => {
await renderAndWait();

const table = screen.getByRole('table');
expect(table).toBeInTheDocument();

const nameColumn = await within(table).findByText('Name');
const fitlerTypeColumn = await within(table).findByText('Filter Type');
const groupKeyColumn = await within(table).findByText('Group Key');
const clauseColumn = await within(table).findByText('Clause');
const modifiedColumn = await within(table).findByText('Modified');
const actionsColumn = await within(table).findByText('Actions');

expect(nameColumn).toBeInTheDocument();
expect(fitlerTypeColumn).toBeInTheDocument();
expect(groupKeyColumn).toBeInTheDocument();
expect(clauseColumn).toBeInTheDocument();
expect(modifiedColumn).toBeInTheDocument();
expect(actionsColumn).toBeInTheDocument();
});

it('renders correct action buttons with write permission', async () => {
await renderAndWait();

const deleteActionIcon = screen.queryAllByTestId('rls-list-trash-icon');
expect(deleteActionIcon).toHaveLength(2);

const editActionIcon = screen.queryAllByTestId('edit-alt');
expect(editActionIcon).toHaveLength(2);
});

it('should not renders correct action buttons without write permission', async () => {
fetchMock.get(
ruleInfoEndpoint,
{ permissions: ['can_read'] },
{ overwriteRoutes: true },
);

await renderAndWait();

const deleteActionIcon = screen.queryByTestId('rls-list-trash-icon');
expect(deleteActionIcon).not.toBeInTheDocument();

const editActionIcon = screen.queryByTestId('edit-alt');
expect(editActionIcon).not.toBeInTheDocument();

fetchMock.get(
ruleInfoEndpoint,
{ permissions: ['can_read', 'can_write'] },
{ overwriteRoutes: true },
);
});

it('renders popover on new clicking rule button', async () => {
await renderAndWait();

const modal = screen.queryByTestId('rls-modal-title');
expect(modal).not.toBeInTheDocument();

const addRuleButton = await screen.findByTestId('add-rule');
userEvent.click(addRuleButton);

const modalAfterClick = screen.queryByTestId('rls-modal-title');
expect(modalAfterClick).toBeInTheDocument();
});
});
Loading

0 comments on commit 159dcd7

Please sign in to comment.