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

Big sig release #231

Merged
merged 34 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
30ecdf3
added adhoc filter plugin files
cccs-RyanK Sep 23, 2022
5b9d83d
Made sure that adhoc filter uses the adhoc filter object
cccs-Dustin Sep 23, 2022
22285cd
added adhocfiltercontrol to native adhoc filter
cccs-RyanK Sep 27, 2022
080d279
fixed hook that made too many requests
cccs-RyanK Sep 27, 2022
99d9f5a
fixed applied filter label
cccs-RyanK Sep 27, 2022
80151b6
removed duplicate files
cccs-RyanK Sep 28, 2022
3a66d93
Removed uneeded functions
cccs-Dustin Sep 28, 2022
1e04870
Merge branch 'Adding-Dashboard-Adhoc-Filters' of github.com:Cybercent…
cccs-Dustin Sep 28, 2022
8e310c6
Removed uneeded functions and variables
cccs-Dustin Sep 28, 2022
ac5e203
Removed unused props variables
cccs-Dustin Sep 28, 2022
be85fab
modifying base image tag
cccs-RyanK Sep 28, 2022
c53c43d
Merge branch 'Adding-Dashboard-Adhoc-Filters' of github.com:Cybercent…
cccs-RyanK Sep 28, 2022
673ffac
Removed unused config settings
cccs-Dustin Sep 28, 2022
7c681b5
Merge branch 'Adding-Dashboard-Adhoc-Filters' of github.com:Cybercent…
cccs-Dustin Sep 28, 2022
c796777
removed column for filter config form
cccs-RyanK Sep 29, 2022
157aaae
Improved the applied filter(s) modal
cccs-Dustin Sep 29, 2022
257eec8
Temp update to build image
cccs-Dustin Sep 29, 2022
14c936a
fixed string formatting issue:
cccs-RyanK Sep 29, 2022
3a6b4bf
updating superset base image tag
cccs-RyanK Sep 29, 2022
94de584
added setFocused hooks to filter when hovering
cccs-RyanK Oct 4, 2022
3fe2450
Merge branch 'cccs-2.0' of github.com:CybercentreCanada/superset into…
cccs-RyanK Oct 7, 2022
58be3bb
fixed unused declaration error
cccs-RyanK Oct 7, 2022
ab53b1e
updating image
cccs-RyanK Oct 7, 2022
ddfc44d
Merge branch 'cccs-2.0' of github.com:CybercentreCanada/superset into…
cccs-RyanK Oct 11, 2022
9d9a0d3
Merge branch 'cccs-2.0' of github.com:CybercentreCanada/superset int…
cccs-RyanK Oct 11, 2022
0b71bbf
updating superset-base image tag
cccs-RyanK Oct 11, 2022
953531f
Merge branch 'cccs-2.0' of github.com:CybercentreCanada/superset into…
cccs-RyanK Nov 21, 2022
fc2207b
updating image
cccs-RyanK Nov 21, 2022
330f3e5
Update cccs-build/superset/Dockerfile
cccs-RyanK Nov 22, 2022
0635660
Merge pull request #218 from CybercentreCanada/Adding-Dashboard-Adhoc…
cccs-RyanK Nov 23, 2022
0d5fee8
Added ability to certify entities with multiple values (#224)
reesercollins Nov 24, 2022
f6cf4b0
[bigdig] updating base image
cccs-RyanS Nov 25, 2022
e29e474
Merge branch 'cccs-2.0-bigdig12' of github.com:CybercentreCanada/supe…
cccs-RyanS Nov 28, 2022
64201a9
Merge branch 'cccs-2.0' into cccs-2.0-bigdig12
cccs-RyanS Nov 28, 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
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;
Loading