mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
fix: Owners selection in dataset edit UX (#17063)
* boilerplate * update owner select component * this is working * update onchange * refactorig * you need to useMemo or things break * update test * prettier * move logic into bootstrap data endpoint * address concerns * oops * oops * fix test
This commit is contained in:
parent
11d52cb4e1
commit
959fd763a8
@ -168,6 +168,7 @@ export default {
|
|||||||
id,
|
id,
|
||||||
granularity_sqla: [['ds', 'ds']],
|
granularity_sqla: [['ds', 'ds']],
|
||||||
name: 'birth_names',
|
name: 'birth_names',
|
||||||
|
owners: [{ first_name: 'joe', last_name: 'man', id: 1 }],
|
||||||
database: {
|
database: {
|
||||||
allow_multi_schema_metadata_fetch: null,
|
allow_multi_schema_metadata_fetch: null,
|
||||||
name: 'main',
|
name: 'main',
|
||||||
|
@ -139,7 +139,6 @@ describe('FiltersBadge', () => {
|
|||||||
wrapper.find('[data-test="incompatible-filter-count"]'),
|
wrapper.find('[data-test="incompatible-filter-count"]'),
|
||||||
).toHaveText('1');
|
).toHaveText('1');
|
||||||
// to look at the shape of the wrapper use:
|
// to look at the shape of the wrapper use:
|
||||||
// console.log(wrapper.debug())
|
|
||||||
expect(wrapper.find(Icons.AlertSolid)).toExist();
|
expect(wrapper.find(Icons.AlertSolid)).toExist();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Row, Col } from 'src/common/components';
|
import { Row, Col } from 'src/common/components';
|
||||||
import { Radio } from 'src/components/Radio';
|
import { Radio } from 'src/components/Radio';
|
||||||
@ -26,6 +26,8 @@ import Alert from 'src/components/Alert';
|
|||||||
import Badge from 'src/components/Badge';
|
import Badge from 'src/components/Badge';
|
||||||
import shortid from 'shortid';
|
import shortid from 'shortid';
|
||||||
import { styled, SupersetClient, t, supersetTheme } from '@superset-ui/core';
|
import { styled, SupersetClient, t, supersetTheme } from '@superset-ui/core';
|
||||||
|
import { Select } from 'src/components';
|
||||||
|
import { FormLabel } from 'src/components/Form';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import Tabs from 'src/components/Tabs';
|
import Tabs from 'src/components/Tabs';
|
||||||
import CertifiedIcon from 'src/components/CertifiedIcon';
|
import CertifiedIcon from 'src/components/CertifiedIcon';
|
||||||
@ -40,9 +42,7 @@ import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
|||||||
|
|
||||||
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
||||||
import TextControl from 'src/explore/components/controls/TextControl';
|
import TextControl from 'src/explore/components/controls/TextControl';
|
||||||
import { Select } from 'src/components';
|
|
||||||
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
|
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
|
||||||
import SelectAsyncControl from 'src/explore/components/controls/SelectAsyncControl';
|
|
||||||
import SpatialControl from 'src/explore/components/controls/SpatialControl';
|
import SpatialControl from 'src/explore/components/controls/SpatialControl';
|
||||||
|
|
||||||
import CollectionTable from 'src/CRUD/CollectionTable';
|
import CollectionTable from 'src/CRUD/CollectionTable';
|
||||||
@ -374,12 +374,44 @@ const defaultProps = {
|
|||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function OwnersSelector({ datasource, onChange }) {
|
||||||
|
const loadOptions = useCallback((search = '', page, pageSize) => {
|
||||||
|
const query = rison.encode({ filter: search, page, page_size: pageSize });
|
||||||
|
return SupersetClient.get({
|
||||||
|
endpoint: `/api/v1/dataset/related/owners?q=${query}`,
|
||||||
|
}).then(response => ({
|
||||||
|
data: response.json.result.map(item => ({
|
||||||
|
value: item.value,
|
||||||
|
label: item.text,
|
||||||
|
})),
|
||||||
|
totalCount: response.json.count,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
ariaLabel={t('Select owners')}
|
||||||
|
mode="multiple"
|
||||||
|
name="owners"
|
||||||
|
value={datasource.owners}
|
||||||
|
options={loadOptions}
|
||||||
|
onChange={onChange}
|
||||||
|
header={<FormLabel>{t('Owners')}</FormLabel>}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class DatasourceEditor extends React.PureComponent {
|
class DatasourceEditor extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
datasource: {
|
datasource: {
|
||||||
...props.datasource,
|
...props.datasource,
|
||||||
|
owners: props.datasource.owners.map(owner => ({
|
||||||
|
value: owner.id,
|
||||||
|
label: `${owner.first_name} ${owner.last_name}`,
|
||||||
|
})),
|
||||||
metrics: props.datasource.metrics?.map(metric => {
|
metrics: props.datasource.metrics?.map(metric => {
|
||||||
const {
|
const {
|
||||||
certified_by: certifiedByMetric,
|
certified_by: certifiedByMetric,
|
||||||
@ -717,23 +749,11 @@ class DatasourceEditor extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Field
|
<OwnersSelector
|
||||||
fieldKey="owners"
|
datasource={datasource}
|
||||||
label={t('Owners')}
|
onChange={newOwners => {
|
||||||
description={t('Owners of the dataset')}
|
this.onDatasourceChange({ ...datasource, owners: newOwners });
|
||||||
control={
|
}}
|
||||||
<SelectAsyncControl
|
|
||||||
dataEndpoint="api/v1/dataset/related/owners"
|
|
||||||
multi
|
|
||||||
mutator={data =>
|
|
||||||
data.result.map(pk => ({
|
|
||||||
value: pk.value,
|
|
||||||
label: `${pk.text}`,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
controlProps={{}}
|
|
||||||
/>
|
/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
);
|
);
|
||||||
|
@ -99,7 +99,6 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
|
|||||||
currentDatasource.schema;
|
currentDatasource.schema;
|
||||||
|
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
|
||||||
SupersetClient.post({
|
SupersetClient.post({
|
||||||
endpoint: '/datasource/save/',
|
endpoint: '/datasource/save/',
|
||||||
postPayload: {
|
postPayload: {
|
||||||
@ -119,6 +118,9 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
type: currentDatasource.type || currentDatasource.datasource_type,
|
type: currentDatasource.type || currentDatasource.datasource_type,
|
||||||
|
owners: currentDatasource.owners.map(
|
||||||
|
(o: { label: string; value: number }) => o.value,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -41,6 +41,7 @@ const createProps = () => ({
|
|||||||
name: 'channels',
|
name: 'channels',
|
||||||
type: 'table',
|
type: 'table',
|
||||||
columns: [],
|
columns: [],
|
||||||
|
owners: [{ first_name: 'john', last_name: 'doe', id: 1, username: 'jd' }],
|
||||||
},
|
},
|
||||||
validationErrors: [],
|
validationErrors: [],
|
||||||
name: 'datasource',
|
name: 'datasource',
|
||||||
|
@ -165,7 +165,6 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||||||
endpoint: `/api/v1/dataset/${id}`,
|
endpoint: `/api/v1/dataset/${id}`,
|
||||||
})
|
})
|
||||||
.then(({ json = {} }) => {
|
.then(({ json = {} }) => {
|
||||||
const owners = json.result.owners.map((owner: any) => owner.id);
|
|
||||||
const addCertificationFields = json.result.columns.map(
|
const addCertificationFields = json.result.columns.map(
|
||||||
(column: ColumnObject) => {
|
(column: ColumnObject) => {
|
||||||
const {
|
const {
|
||||||
@ -181,7 +180,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||||||
);
|
);
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
json.result.columns = [...addCertificationFields];
|
json.result.columns = [...addCertificationFields];
|
||||||
setDatasetCurrentlyEditing({ ...json.result, owners });
|
setDatasetCurrentlyEditing(json.result);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
addDangerToast(
|
addDangerToast(
|
||||||
|
@ -118,6 +118,18 @@ class BaseDatasource(
|
|||||||
def kind(self) -> DatasourceKind:
|
def kind(self) -> DatasourceKind:
|
||||||
return DatasourceKind.VIRTUAL if self.sql else DatasourceKind.PHYSICAL
|
return DatasourceKind.VIRTUAL if self.sql else DatasourceKind.PHYSICAL
|
||||||
|
|
||||||
|
@property
|
||||||
|
def owners_data(self) -> List[Dict[str, Any]]:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"first_name": o.first_name,
|
||||||
|
"last_name": o.last_name,
|
||||||
|
"username": o.username,
|
||||||
|
"id": o.id,
|
||||||
|
}
|
||||||
|
for o in self.owners
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_virtual(self) -> bool:
|
def is_virtual(self) -> bool:
|
||||||
return self.kind == DatasourceKind.VIRTUAL
|
return self.kind == DatasourceKind.VIRTUAL
|
||||||
|
@ -856,6 +856,9 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||||||
except (SupersetException, SQLAlchemyError):
|
except (SupersetException, SQLAlchemyError):
|
||||||
datasource_data = dummy_datasource_data
|
datasource_data = dummy_datasource_data
|
||||||
|
|
||||||
|
if datasource:
|
||||||
|
datasource_data["owners"] = datasource.owners_data
|
||||||
|
|
||||||
bootstrap_data = {
|
bootstrap_data = {
|
||||||
"can_add": slice_add_perm,
|
"can_add": slice_add_perm,
|
||||||
"can_download": slice_download_perm,
|
"can_download": slice_download_perm,
|
||||||
|
Loading…
Reference in New Issue
Block a user