Skip to content

Commit

Permalink
Big sig release (#231)
Browse files Browse the repository at this point in the history
* added adhoc filter plugin files

* Made sure that adhoc filter uses the adhoc filter object

* added adhocfiltercontrol to native adhoc filter

* fixed hook that made too many requests

* fixed applied filter label

* removed duplicate files

* Removed uneeded functions

* Removed uneeded functions and variables

* Removed unused props variables

* modifying base image tag

* Removed unused config settings

* removed column for filter config form

* Improved the applied filter(s) modal

* Temp update to build image

* fixed string formatting issue:

* updating superset base image tag

* added setFocused hooks to filter when hovering

* fixed unused declaration error

* updating image

* updating superset-base image tag

* updating image

* Update cccs-build/superset/Dockerfile

Co-authored-by: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com>

* Added ability to certify entities with multiple values (#224)

* Added ability to certify entities with multiple values

* Update description text to reflect new feature

* Add tests for multiple certified by values

* [bigdig] updating base image

Co-authored-by: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com>
Co-authored-by: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com>
Co-authored-by: Reese <10563996+reesercollins@users.noreply.github.com>
  • Loading branch information
4 people committed Nov 28, 2022
1 parent 8d26229 commit d052a5c
Show file tree
Hide file tree
Showing 19 changed files with 852 additions and 258 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ test('renders with certified by', async () => {
expect(await screen.findByRole('tooltip')).toHaveTextContent(certifiedBy);
});

test('renders with multiple certified by values', async () => {
const certifiedBy = ['Trusted Authority', 'Other Authority'];
render(<CertifiedBadge certifiedBy={certifiedBy} />);
userEvent.hover(screen.getByRole('img'));
expect(await screen.findByRole('tooltip')).toHaveTextContent(
certifiedBy.join(', '),
);
});

test('renders with details', async () => {
const details = 'All requirements have been met.';
render(<CertifiedBadge details={details} />);
Expand Down
8 changes: 5 additions & 3 deletions superset-frontend/src/components/CertifiedBadge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
* under the License.
*/
import React from 'react';
import { t, useTheme } from '@superset-ui/core';
import { ensureIsArray, t, useTheme } from '@superset-ui/core';
import Icons, { IconType } from 'src/components/Icons';
import { Tooltip } from 'src/components/Tooltip';

export interface CertifiedBadgeProps {
certifiedBy?: string;
certifiedBy?: string | string[];
details?: string;
size?: IconType['iconSize'];
}
Expand All @@ -41,7 +41,9 @@ function CertifiedBadge({
<>
{certifiedBy && (
<div>
<strong>{t('Certified by %s', certifiedBy)}</strong>
<strong>
{t('Certified by %s', ensureIsArray(certifiedBy).join(', '))}
</strong>
</div>
)}
<div>{details}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ class DatasourceEditor extends React.PureComponent {
description={t(
'Extra data to specify table metadata. Currently supports ' +
'metadata of the format: `{ "certification": { "certified_by": ' +
'"Data Platform Team", "details": "This table is the source of truth." ' +
'["Data Platform Team", "Engineering Team"], "details": "This table is the source of truth." ' +
'}, "warning_markdown": "This is a warning." }`.',
)}
control={
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export const FILTER_SUPPORTED_TYPES = {
GenericDataType.NUMERIC,
GenericDataType.TEMPORAL,
],
filter_adhoc: [
GenericDataType.BOOLEAN,
GenericDataType.STRING,
GenericDataType.NUMERIC,
GenericDataType.TEMPORAL,
],
filter_range: [GenericDataType.NUMERIC],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ enum FILTER_COMPONENT_FILTER_TYPES {
FILTER_TIMEGRAIN = 'filter_timegrain',
FILTER_TIMECOLUMN = 'filter_timecolumn',
FILTER_SELECT = 'filter_select',
FILTER_ADHOC = 'filter_adhoc',
FILTER_RANGE = 'filter_range',
}

Expand Down
254 changes: 254 additions & 0 deletions superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/**
* 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.
*/
/* eslint-disable no-param-reassign */
import {
DataMask,
ensureIsArray,
ExtraFormData,
JsonObject,
JsonResponse,
smartDateDetailedFormatter,
SupersetApiError,
SupersetClient,
t,
} from '@superset-ui/core';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { useImmerReducer } from 'use-immer';
import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl';
import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter';
// eslint-disable-next-line import/no-unresolved
import { addDangerToast } from 'src/components/MessageToasts/actions';
// eslint-disable-next-line import/no-unresolved
import { cacheWrapper } from 'src/utils/cacheWrapper';
// eslint-disable-next-line import/no-unresolved
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
// eslint-disable-next-line import/no-unresolved
import { useChangeEffect } from 'src/hooks/useChangeEffect';
import { PluginFilterAdhocProps } from './types';
import {
StyledFormItem,
FilterPluginStyle,
StatusMessage,
ControlContainer,
} from '../common';
import { getDataRecordFormatter, getAdhocExtraFormData } from '../../utils';

type DataMaskAction =
| { type: 'ownState'; ownState: JsonObject }
| {
type: 'filterState';
__cache: JsonObject;
extraFormData: ExtraFormData;
filterState: {
label?: string;
filters?: AdhocFilter[];
value: AdhocFilter[];
};
};

function reducer(
draft: DataMask & { __cache?: JsonObject },
action: DataMaskAction,
) {
switch (action.type) {
case 'ownState':
draft.ownState = {
...draft.ownState,
...action.ownState,
};
return draft;
case 'filterState':
draft.extraFormData = action.extraFormData;
// eslint-disable-next-line no-underscore-dangle
draft.__cache = action.__cache;
draft.filterState = { ...draft.filterState, ...action.filterState };
return draft;
default:
return draft;
}
}

export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) {
const {
filterState,
formData,
height,
width,
setDataMask,
setFocusedFilter,
unsetFocusedFilter,
appSection,
} = props;
const { enableEmptyFilter, inverseSelection, defaultToFirstItem } = formData;
const datasetId = useMemo(
() => formData.datasource.split('_')[0],
[formData.datasource],
);
const [datasetDetails, setDatasetDetails] = useState<Record<string, any>>();
const [columns, setColumns] = useState();
const [dataMask, dispatchDataMask] = useImmerReducer(reducer, {
extraFormData: {},
filterState,
});
const labelFormatter = useMemo(
() =>
getDataRecordFormatter({
timeFormatter: smartDateDetailedFormatter,
}),
[],
);

const localCache = new Map<string, any>();

const cachedSupersetGet = cacheWrapper(
SupersetClient.get,
localCache,
({ endpoint }) => endpoint || '',
);

useChangeEffect(datasetId, () => {
if (datasetId) {
cachedSupersetGet({
endpoint: `/api/v1/dataset/${datasetId}`,
})
.then((response: JsonResponse) => {
const dataset = response.json?.result;
// modify the response to fit structure expected by AdhocFilterControl
dataset.type = dataset.datasource_type;
dataset.filter_select = true;
setDatasetDetails(dataset);
})
.catch((response: SupersetApiError) => {
addDangerToast(response.message);
});
}
});

useChangeEffect(datasetId, () => {
if (datasetId != null) {
cachedSupersetGet({
endpoint: `/api/v1/dataset/${datasetId}`,
}).then(
({ json: { result } }) => {
setColumns(result.columns);
},
async badResponse => {
const { error, message } = await getClientErrorObject(badResponse);
let errorText = message || error || t('An error has occurred');
if (message === 'Forbidden') {
errorText = t('You do not have permission to edit this dashboard');
}
addDangerToast(errorText);
},
);
}
});

const labelString: (props: AdhocFilter) => string = (props: AdhocFilter) => {
if (ensureIsArray(props.comparator).length >= 2) {
return `${props.subject} ${props.operator} (${props.comparator.join(
', ',
)})`;
}
return `${props.subject} ${props.operator} ${props.comparator}`;
};

const updateDataMask = useCallback(
(adhoc_filters: AdhocFilter[]) => {
const emptyFilter =
enableEmptyFilter && !inverseSelection && !adhoc_filters?.length;

dispatchDataMask({
type: 'filterState',
__cache: filterState,
extraFormData: getAdhocExtraFormData(
adhoc_filters,
emptyFilter,
inverseSelection,
),
filterState: {
...filterState,
label: (adhoc_filters || [])
.map(f =>
f.sqlExpression ? String(f.sqlExpression) : labelString(f),
)
.join(', '),
value: adhoc_filters,
filters: adhoc_filters,
},
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
appSection,
defaultToFirstItem,
dispatchDataMask,
enableEmptyFilter,
inverseSelection,
JSON.stringify(filterState),
labelFormatter,
],
);

useEffect(() => {
updateDataMask(filterState.value);
}, [JSON.stringify(filterState.value)]);

useEffect(() => {
setDataMask(dataMask);
}, [JSON.stringify(dataMask)]);

const formItemExtra = useMemo(() => {
if (filterState.validateMessage) {
return (
<StatusMessage status={filterState.validateStatus}>
{filterState.validateMessage}
</StatusMessage>
);
}
return undefined;
}, [filterState.validateMessage, filterState.validateStatus]);

return (
<FilterPluginStyle height={height} width={width}>
<StyledFormItem
validateStatus={filterState.validateStatus}
extra={formItemExtra}
>
<ControlContainer
onMouseEnter={setFocusedFilter}
onMouseLeave={unsetFocusedFilter}
validateStatus={filterState.validateStatus}
>
<AdhocFilterControl
columns={columns || []}
savedMetrics={[]}
datasource={datasetDetails}
onChange={(filters: AdhocFilter[]) => {
// New Adhoc Filters Selected
updateDataMask(filters);
}}
label={' '}
value={filterState.filters || []}
/>
</ControlContainer>
</StyledFormItem>
</FilterPluginStyle>
);
}
43 changes: 43 additions & 0 deletions superset-frontend/src/filters/components/Adhoc/buildQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* 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 {
buildQueryContext,
QueryObject,
QueryObjectFilterClause,
BuildQuery,
} from '@superset-ui/core';
import { PluginFilterSelectQueryFormData } from './types';

const buildQuery: BuildQuery<PluginFilterSelectQueryFormData> = (
formData: PluginFilterSelectQueryFormData,
) =>
buildQueryContext(formData, baseQueryObject => {
const { filters = [] } = baseQueryObject;
const extraFilters: QueryObjectFilterClause[] = [];
const query: QueryObject[] = [
{
...baseQueryObject,
result_type: 'columns',
filters: filters.concat(extraFilters),
},
];
return query;
});

export default buildQuery;

0 comments on commit d052a5c

Please sign in to comment.