Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(RLS): RESTful apis and react view for RLS #22325

Merged
merged 25 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f5ea5ab
initial
mayurnewase Dec 3, 2022
3c194bb
Merge branch 'master' of github.com:apache/superset into feat/restful…
mayurnewase Dec 4, 2022
b359956
added related fields for table and roles on api, added modal to creat…
mayurnewase Dec 7, 2022
9bcd0f1
all features complete
mayurnewase Dec 10, 2022
a3b352c
add license
mayurnewase Dec 10, 2022
6bfe1f2
add more license, remove todo comments
mayurnewase Dec 10, 2022
d92bc9e
raise correct exception on bulk_delete failure
mayurnewase Dec 10, 2022
5e74273
remove commented code
mayurnewase Dec 10, 2022
58388f2
optimize create and update command by batching table read queries
mayurnewase Dec 10, 2022
b931886
add tooltip, add list and show schema
mayurnewase Dec 12, 2022
dc66f89
lint
mayurnewase Dec 12, 2022
afdb088
add tests
mayurnewase Dec 14, 2022
3372e55
update tests
mayurnewase Dec 14, 2022
f9bc5e6
remove unused import
mayurnewase Dec 14, 2022
6bfaac5
add modal tests
mayurnewase Dec 17, 2022
e0ce970
add modal tests
mayurnewase Dec 17, 2022
62161bb
add more api tests, review pass
mayurnewase Dec 17, 2022
cabcf7b
remove un-necessary lines
mayurnewase Dec 17, 2022
e679385
add tests for related filters
mayurnewase Dec 18, 2022
f0e5427
remove metaobject type
mayurnewase Dec 19, 2022
d8ace28
update UPDATE.md
mayurnewase Dec 19, 2022
917c689
fix indentation in updating.md file
mayurnewase Dec 19, 2022
7af708d
handle table schema
mayurnewase Dec 22, 2022
7524acb
Merge branch 'master' of github.com:apache/superset into feat/restful…
mayurnewase Dec 25, 2022
3bfcb81
rename config variable
mayurnewase Dec 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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