From 628972440fde690a04a48b1f81668fd964ca1765 Mon Sep 17 00:00:00 2001 From: cccs-nik <68961854+cccs-nik@users.noreply.github.com> Date: Thu, 2 Jun 2022 11:42:37 -0400 Subject: [PATCH 01/76] CLDN-1440 tagging for 1.5 --- .../src/components/ObjectTags/ObjectTags.css | 211 ++++++++++++++++++ .../src/components/ObjectTags/index.tsx | 124 ++++++++++ .../src/components/TagsList/index.tsx | 55 +++++ .../src/dashboard/components/Header/index.jsx | 40 ++++ .../components/ExploreChartHeader/index.jsx | 71 +++++- superset-frontend/src/tags.ts | 118 ++++++++++ superset-frontend/src/types/Chart.ts | 2 + superset-frontend/src/types/Tag.ts | 24 ++ superset-frontend/src/types/TaggedObject.ts | 25 +++ .../src/views/CRUD/chart/ChartList.tsx | 14 ++ .../views/CRUD/dashboard/DashboardList.tsx | 15 ++ .../CRUD/data/savedquery/SavedQueryList.tsx | 14 ++ .../src/views/CRUD/tags/Tags.tsx | 98 ++++++++ .../src/views/CRUD/tags/TagsTable.tsx | 124 ++++++++++ superset-frontend/src/views/routes.tsx | 7 + superset/charts/api.py | 3 + superset/config.py | 2 +- superset/dashboards/api.py | 3 + superset/initialization/__init__.py | 11 +- superset/models/dashboard.py | 9 +- superset/models/slice.py | 9 +- superset/models/sql_lab.py | 9 +- superset/models/tags.py | 2 +- superset/queries/saved_queries/api.py | 10 +- superset/views/tags.py | 16 +- 25 files changed, 995 insertions(+), 21 deletions(-) create mode 100644 superset-frontend/src/components/ObjectTags/ObjectTags.css create mode 100644 superset-frontend/src/components/ObjectTags/index.tsx create mode 100644 superset-frontend/src/components/TagsList/index.tsx create mode 100644 superset-frontend/src/tags.ts create mode 100644 superset-frontend/src/types/Tag.ts create mode 100644 superset-frontend/src/types/TaggedObject.ts create mode 100644 superset-frontend/src/views/CRUD/tags/Tags.tsx create mode 100644 superset-frontend/src/views/CRUD/tags/TagsTable.tsx diff --git a/superset-frontend/src/components/ObjectTags/ObjectTags.css b/superset-frontend/src/components/ObjectTags/ObjectTags.css new file mode 100644 index 0000000000000..b21bc74f4b091 --- /dev/null +++ b/superset-frontend/src/components/ObjectTags/ObjectTags.css @@ -0,0 +1,211 @@ +/** + * 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. + */ +/** + *
+ *
+ * + *
+ * )} + { isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( + + ) : null + }
} menuDropdownProps={{ diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index fb85882f02337..3b91007647144 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -36,6 +36,24 @@ import PropertiesModal from 'src/explore/components/PropertiesModal'; import { sliceUpdated } from 'src/explore/actions/exploreActions'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu'; +import CertifiedBadge from 'src/components/CertifiedBadge'; +import ExploreActionButtons from '../ExploreActionButtons'; +import RowCountLabel from '../RowCountLabel'; +import ObjectTags from '../../components/ObjectTags'; +import { + addTag, + deleteTag, + fetchSuggestions, + fetchTags, + OBJECT_TYPES, +} from '../../tags'; +import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; + +const CHART_STATUS_MAP = { + failed: 'danger', + loading: 'warning', + success: 'success', +}; const propTypes = { actions: PropTypes.object.isRequired, @@ -60,6 +78,11 @@ const saveButtonStyles = theme => css` } `; +const StyledButtons = styled.span` + display: flex; + align-items: center; +`; + export const ExploreChartHeader = ({ dashboardId, slice, @@ -170,16 +193,44 @@ export const ExploreChartHeader = ({ showTooltip: true, }} titlePanelAdditionalItems={ - sliceFormData ? ( - - ) : null + [ + sliceFormData ? ( + + ) : null, + isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( + + ) : null + ] } rightPanelAdditionalItems={ void, + error: (response: Response) => void, +) { + if (objectType === undefined || objectId === undefined) { + throw new Error('Need to specify objectType and objectId'); + } + SupersetClient.get({ endpoint: `/tagview/tags/${objectType}/${objectId}/` }) + .then(({ json }) => + callback( + json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), + ), + ) + .catch(response => error(response)); +} + +export function fetchSuggestions( + { includeTypes = false }, + callback: (json: JsonObject) => void, + error: (response: Response) => void, +) { + SupersetClient.get({ endpoint: '/tagview/tags/suggestions/' }) + .then(({ json }) => + callback( + json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), + ), + ) + .catch(response => error(response)); +} + +export function deleteTag( + { objectType, objectId }: { objectType: string; objectId: number }, + tag: Tag, + callback: (text: string) => void, + error: (response: Response) => void, +) { + if (objectType === undefined || objectId === undefined) { + throw new Error('Need to specify objectType and objectId'); + } + SupersetClient.delete({ + endpoint: `/tagview/tags/${objectType}/${objectId}/`, + body: JSON.stringify([tag]), + parseMethod: 'text', + }) + .then(({ text }) => callback(text)) + .catch(response => error(response)); +} + +export function addTag( + { + objectType, + objectId, + includeTypes = false, + }: { objectType: string; objectId: number; includeTypes: boolean }, + tag: string, + callback: (text: string) => void, + error: (response: Response) => void, +) { + if (objectType === undefined || objectId === undefined) { + throw new Error('Need to specify objectType and objectId'); + } + if (tag.indexOf(':') !== -1 && !includeTypes) { + return; + } + SupersetClient.post({ + endpoint: `/tagview/tags/${objectType}/${objectId}/`, + body: JSON.stringify([tag]), + parseMethod: 'text', + }) + .then(({ text }) => callback(text)) + .catch(response => error(response)); +} + +export function fetchObjects( + { tags = '', types }: { tags: string; types: string | null }, + callback: (json: JsonObject) => void, + error: (response: Response) => void, +) { + let url = `/tagview/tagged_objects/?tags=${tags}`; + if (types) { + url += `&types=${types}`; + } + SupersetClient.get({ endpoint: url }) + .then(({ json }) => callback(json)) + .catch(response => error(response)); +} diff --git a/superset-frontend/src/types/Chart.ts b/superset-frontend/src/types/Chart.ts index df6460080b34c..ea10c4e23a9fd 100644 --- a/superset-frontend/src/types/Chart.ts +++ b/superset-frontend/src/types/Chart.ts @@ -23,6 +23,7 @@ import { QueryFormData } from '@superset-ui/core'; import Owner from './Owner'; +import Tag from './Tag'; export interface Chart { id: number; @@ -39,6 +40,7 @@ export interface Chart { cache_timeout: number | null; thumbnail_url?: string; owners?: Owner[]; + tags?: Tag[]; datasource_name_text?: string; form_data: { viz_type: string; diff --git a/superset-frontend/src/types/Tag.ts b/superset-frontend/src/types/Tag.ts new file mode 100644 index 0000000000000..da085501d1b0d --- /dev/null +++ b/superset-frontend/src/types/Tag.ts @@ -0,0 +1,24 @@ +/** + * 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. + */ + +export default interface Tag { + id: string | number; + type?: string | number; + name: string; +} diff --git a/superset-frontend/src/types/TaggedObject.ts b/superset-frontend/src/types/TaggedObject.ts new file mode 100644 index 0000000000000..4a4aa4314dbcb --- /dev/null +++ b/superset-frontend/src/types/TaggedObject.ts @@ -0,0 +1,25 @@ +/** + * 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. + */ + +export default interface TaggedObject { + id: string | number; + tag_id: number; + object_id: number; + object_type: number; +} diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 8173b73c0663a..88d979b0a4e56 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -39,6 +39,7 @@ import { } from 'src/views/CRUD/hooks'; import handleResourceExport from 'src/utils/export'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import { TagsList } from 'src/components/TagsList'; import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; import FaveStar from 'src/components/FaveStar'; import { Link, useHistory } from 'react-router-dom'; @@ -55,6 +56,7 @@ import withToasts from 'src/components/MessageToasts/withToasts'; import PropertiesModal from 'src/explore/components/PropertiesModal'; import ImportModelsModal from 'src/components/ImportModal/index'; import Chart from 'src/types/Chart'; +import Tag from 'src/types/Tag'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils'; @@ -366,6 +368,18 @@ function ChartList(props: ChartListProps) { disableSortBy: true, size: 'xl', }, + { + Cell: ({ + row: { + original: { tags = [] }, + }, + }: any) => ( + tag.type === 1)} /> + ), + Header: t('Tags'), + accessor: 'tags', + disableSortBy: true, + }, { Cell: ({ row: { original } }: any) => { const handleDelete = () => diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 7dbb30159d91e..26a6d0797cae1 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -28,6 +28,7 @@ import { } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import { TagsList } from 'src/components/TagsList'; import handleResourceExport from 'src/utils/export'; import Loading from 'src/components/Loading'; import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; @@ -39,6 +40,7 @@ import ListView, { } from 'src/components/ListView'; import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers'; import Owner from 'src/types/Owner'; +import Tag from 'src/types/Tag'; import withToasts from 'src/components/MessageToasts/withToasts'; import FacePile from 'src/components/FacePile'; import Icons from 'src/components/Icons'; @@ -87,6 +89,7 @@ interface Dashboard { url: string; thumbnail_url: string; owners: Owner[]; + tags: Tag[]; created_by: object; } @@ -339,6 +342,18 @@ function DashboardList(props: DashboardListProps) { disableSortBy: true, size: 'xl', }, + { + Cell: ({ + row: { + original: { tags = [] }, + }, + }: any) => ( + tag.type === 1)} /> + ), + Header: t('Tags'), + accessor: 'tags', + disableSortBy: true, + }, { Cell: ({ row: { original } }: any) => { const handleDelete = () => diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx index f3d58fe52cce8..2fc5b5814af19 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx @@ -43,10 +43,12 @@ import ListView, { import Loading from 'src/components/Loading'; import DeleteModal from 'src/components/DeleteModal'; import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar'; +import { TagsList } from 'src/components/TagsList'; import { Tooltip } from 'src/components/Tooltip'; import { commonMenuData } from 'src/views/CRUD/data/common'; import { SavedQueryObject } from 'src/views/CRUD/types'; import copyTextToClipboard from 'src/utils/copy'; +import Tag from 'src/types/Tag'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import ImportModelsModal from 'src/components/ImportModal/index'; import Icons from 'src/components/Icons'; @@ -362,6 +364,18 @@ function SavedQueryList({ accessor: 'changed_on_delta_humanized', size: 'xl', }, + { + Cell: ({ + row: { + original: { tags = [] }, + }, + }: any) => ( + tag.type === 1)} /> + ), + Header: t('Tags'), + accessor: 'tags', + disableSortBy: true, + }, { Cell: ({ row: { original } }: any) => { const handlePreview = () => { diff --git a/superset-frontend/src/views/CRUD/tags/Tags.tsx b/superset-frontend/src/views/CRUD/tags/Tags.tsx new file mode 100644 index 0000000000000..d86020c599824 --- /dev/null +++ b/superset-frontend/src/views/CRUD/tags/Tags.tsx @@ -0,0 +1,98 @@ +/** + * 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, { useEffect, useState } from 'react'; +import { styled } from '@superset-ui/core'; +import Tag from 'src/types/Tag'; +import { StringParam, useQueryParam } from 'use-query-params'; +import withToasts from 'src/components/MessageToasts/withToasts'; +import SelectControl from 'src/explore/components/controls/SelectControl'; +import { fetchSuggestions } from 'src/tags'; +// import { STANDARD_TAGS } from 'src/dashboard/util/constants'; +import TagsTable from './TagsTable'; + +// const OWNED_BY_ME = 'owner:{{ current_user_id() }}'; + +const TagsContainer = styled.div` + background-color: ${({ theme }) => theme.colors.grayscale.light4}; + .select-control { + margin-left: ${({ theme }) => theme.gridUnit * 4}px; + margin-right: ${({ theme }) => theme.gridUnit * 4}px; + margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; + } + .select-control-label { + text-transform: uppercase; + font-size: ${({ theme }) => theme.gridUnit * 3}px; + color: #666666; + margin-bottom: ${({ theme }) => theme.gridUnit * 1}px; + } +`; + +const TagsNav = styled.div` + height: 50px; + background-color: white; + margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; + .navbar-brand { + margin-left: ${({ theme }) => theme.gridUnit * 2}px; + font-weight: ${({ theme }) => theme.typography.weights.bold}; + } +`; + +function Tags() { + const [tagSuggestions, setTagSuggestions] = useState(); + const [tagsQuery, setTagsQuery] = useQueryParam('tags', StringParam); + + useEffect(() => { + fetchSuggestions( + { includeTypes: false }, + (suggestions: Tag[]) => { + const tagSuggestions = [...suggestions.map(tag => tag.name)]; + setTagSuggestions(tagSuggestions); + }, + (error: Response) => { + console.log(error.json()); + }, + ); + }, [tagsQuery]); + + const onTagSearchChange = (tags: Tag[]) => { + const tagSearch = tags.join(','); + setTagsQuery(tagSearch); + }; + + return ( + + + Tags + +
+
Search tags
+ +
+ +
+ ); +} + +export default withToasts(Tags); diff --git a/superset-frontend/src/views/CRUD/tags/TagsTable.tsx b/superset-frontend/src/views/CRUD/tags/TagsTable.tsx new file mode 100644 index 0000000000000..1afb4d29715c9 --- /dev/null +++ b/superset-frontend/src/views/CRUD/tags/TagsTable.tsx @@ -0,0 +1,124 @@ +/** + * 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, { useState, useEffect } from 'react'; +import moment from 'moment'; +import { t, styled } from '@superset-ui/core'; +import TableView, { EmptyWrapperType } from 'src/components/TableView'; +import { fetchObjects } from '../../../tags'; +import Loading from '../../../components/Loading'; + +const TagsTableContainer = styled.div` + text-align: left; + border-radius: ${({ theme }) => theme.gridUnit * 1}px 0; + margin: 0 ${({ theme }) => theme.gridUnit * 4}px; + .table { + table-layout: fixed; + } + .td { + width: 33%; + } +`; + +interface TaggedObject { + id: number; + type: string; + name: string; + url: string; + changed_on: moment.MomentInput; + created_by: number | undefined; + creator: string; +} + +interface TaggedObjects { + dashboard: TaggedObject[]; + chart: TaggedObject[]; + query: TaggedObject[]; +} + +interface TagsTableProps { + search?: string; +} + +export default function TagsTable({ search = '' }: TagsTableProps) { + const [objects, setObjects] = useState({ + dashboard: [], + chart: [], + query: [], + }); + + useEffect(() => { + const fetchResults = (search: string) => { + fetchObjects( + { tags: search, types: null }, + (data: TaggedObject[]) => { + const objects = { dashboard: [], chart: [], query: [] }; + data.forEach(object => { + objects[object.type].push(object); + }); + setObjects(objects); + }, + (error: Response) => { + console.log(error.json()); + }, + ); + }; + fetchResults(search); + }, [search]); + + const renderTable = (type: any) => { + const data = objects[type].map((o: TaggedObject) => ({ + [type]: {o.name}, + // eslint-disable-next-line react/no-danger + creator:
, + modified: moment.utc(o.changed_on).fromNow(), + })); + return ( + + ); + }; + + if (objects) { + return ( + +

{t('Dashboards')}

+ {renderTable('dashboard')} +
+

{t('Charts')}

+ {renderTable('chart')} +
+

{t('Queries')}

+ {renderTable('query')} +
+ ); + } + return ; +} diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx index dbb9ca2fab858..bb0bfd5e9f871 100644 --- a/superset-frontend/src/views/routes.tsx +++ b/superset-frontend/src/views/routes.tsx @@ -102,6 +102,9 @@ const SavedQueryList = lazy( /* webpackChunkName: "SavedQueryList" */ 'src/views/CRUD/data/savedquery/SavedQueryList' ), ); +const Tags = lazy( + () => import(/* webpackChunkName: "Tags" */ 'src/views/CRUD/tags/Tags'), +); type Routes = { path: string; @@ -115,6 +118,10 @@ export const routes: Routes = [ path: '/superset/welcome/', Component: Welcome, }, + { + path: '/superset/tags/', + Component: Tags, + }, { path: '/dashboard/list/', Component: DashboardList, diff --git a/superset/charts/api.py b/superset/charts/api.py index e7e511d4fdbd1..b7a9ae3b98e21 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -163,6 +163,9 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "slice_name", "table.default_endpoint", "table.table_name", + "tags.id", + "tags.name", + "tags.type", "thumbnail_url", "url", "viz_type", diff --git a/superset/config.py b/superset/config.py index f30ce37f575e6..20aef22743461 100644 --- a/superset/config.py +++ b/superset/config.py @@ -388,7 +388,7 @@ def _try_json_readsha(filepath: str, length: int) -> Optional[str]: "DASHBOARD_CACHE": False, "REMOVE_SLICE_LEVEL_LABEL_COLORS": False, "SHARE_QUERIES_VIA_KV_STORE": False, - "TAGGING_SYSTEM": False, + "TAGGING_SYSTEM": True, "SQLLAB_BACKEND_PERSISTENCE": True, "LISTVIEWS_DEFAULT_CARD_VIEW": False, # When True, this flag allows display of HTML tags in Markdown components diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index 9a323923c2890..346ae860220ef 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -179,6 +179,9 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "owners.email", "roles.id", "roles.name", + "tags.id", + "tags.name", + "tags.type", "is_managed_externally", ] list_select_columns = list_columns + ["changed_on", "created_on", "changed_by_fk"] diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 588d9b66bfd11..eaaf2fa868940 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -183,7 +183,7 @@ def init_views(self) -> None: TableSchemaView, TabStateView, ) - from superset.views.tags import TagView + from superset.views.tags import TagModelView, TagView from superset.views.users.api import CurrentUserRestApi # @@ -366,7 +366,14 @@ def init_views(self) -> None: category_icon="fa-table", ) appbuilder.add_separator("Data") - + appbuilder.add_view( + TagModelView, + "Tags", + label=__("Tags"), + icon="", + category="", + category_icon="", + ) appbuilder.add_api(LogRestApi) appbuilder.add_view( LogModelView, diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index 6c733ff4e1af6..0886f65817ad9 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -53,7 +53,7 @@ from superset.models.filter_set import FilterSet from superset.models.helpers import AuditMixinNullable, ImportExportMixin from superset.models.slice import Slice -from superset.models.tags import DashboardUpdater +from superset.models.tags import DashboardUpdater, Tag from superset.models.user_attributes import UserAttribute from superset.tasks.thumbnails import cache_dashboard_thumbnail from superset.utils import core as utils @@ -149,6 +149,13 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin): Slice, secondary=dashboard_slices, backref="dashboards" ) owners = relationship(security_manager.user_model, secondary=dashboard_user) + tags = relationship( + Tag, + secondary="tagged_object", + primaryjoin="and_(Dashboard.id == TaggedObject.object_id)", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " + "TaggedObject.object_type == 'dashboard')", + ) published = Column(Boolean, default=False) is_managed_externally = Column(Boolean, nullable=False, default=False) external_url = Column(Text, nullable=True) diff --git a/superset/models/slice.py b/superset/models/slice.py index de0f3df59684f..cfcc41e306f8b 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -42,7 +42,7 @@ from superset import db, is_feature_enabled, security_manager from superset.legacy import update_time_range from superset.models.helpers import AuditMixinNullable, ImportExportMixin -from superset.models.tags import ChartUpdater +from superset.models.tags import ChartUpdater, Tag from superset.tasks.thumbnails import cache_chart_thumbnail from superset.utils import core as utils from superset.utils.hashing import md5_sha_from_str @@ -98,6 +98,13 @@ class Slice( # pylint: disable=too-many-public-methods security_manager.user_model, foreign_keys=[last_saved_by_fk] ) owners = relationship(security_manager.user_model, secondary=slice_user) + tags = relationship( + Tag, + secondary="tagged_object", + primaryjoin="and_(Slice.id == TaggedObject.object_id)", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " + "TaggedObject.object_type == 'chart')", + ) table = relationship( "SqlaTable", foreign_keys=[datasource_id], diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py index 74c43718ef781..e5b4efc648e91 100644 --- a/superset/models/sql_lab.py +++ b/superset/models/sql_lab.py @@ -45,7 +45,7 @@ ExtraJSONMixin, ImportExportMixin, ) -from superset.models.tags import QueryUpdater +from superset.models.tags import QueryUpdater, Tag from superset.sql_parse import CtasMethod, ParsedQuery, Table from superset.sqllab.limiting_factor import LimitingFactor from superset.utils.core import QueryStatus, user_label @@ -203,6 +203,13 @@ class SavedQuery(Model, AuditMixinNullable, ExtraJSONMixin, ImportExportMixin): ) rows = Column(Integer, nullable=True) last_run = Column(DateTime, nullable=True) + tags = relationship( + Tag, + secondary="tagged_object", + primaryjoin="and_(SavedQuery.id == TaggedObject.object_id)", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " + "TaggedObject.object_type == 'query')", + ) export_parent = "database" export_fields = [ diff --git a/superset/models/tags.py b/superset/models/tags.py index 528206e672f62..61b88b1450d8e 100644 --- a/superset/models/tags.py +++ b/superset/models/tags.py @@ -83,7 +83,7 @@ class TaggedObject(Model, AuditMixinNullable): __tablename__ = "tagged_object" id = Column(Integer, primary_key=True) tag_id = Column(Integer, ForeignKey("tag.id")) - object_id = Column(Integer) + object_id = Column(Integer, ForeignKey("dashboards.id"), ForeignKey("slices.id"), ForeignKey("saved_query.id")) object_type = Column(Enum(ObjectTypes)) tag = relationship("Tag", backref="objects") diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py index a82a3dd8efcf1..c58cca0e06723 100644 --- a/superset/queries/saved_queries/api.py +++ b/superset/queries/saved_queries/api.py @@ -103,15 +103,19 @@ class SavedQueryRestApi(BaseSupersetModelRestApi): "database.id", "db_id", "description", + "extra", "id", "label", + "last_run_delta_humanized", + "rows", "schema", "sql", "sql_tables", - "rows", - "last_run_delta_humanized", - "extra", + "tags.id", + "tags.name", + "tags.type", ] + list_select_columns = list_columns + ["changed_by_fk", "changed_on"] add_columns = ["db_id", "description", "label", "schema", "sql"] edit_columns = add_columns order_columns = [ diff --git a/superset/views/tags.py b/superset/views/tags.py index 8ab2798f5d84c..009e2319320c6 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -22,7 +22,8 @@ from flask import request, Response from flask_appbuilder import expose from flask_appbuilder.hooks import before_request -from flask_appbuilder.security.decorators import has_access_api +from flask_appbuilder.models.sqla.interface import SQLAInterface +from flask_appbuilder.security.decorators import has_access, has_access_api from jinja2.sandbox import SandboxedEnvironment from sqlalchemy import and_, func from werkzeug.exceptions import NotFound @@ -34,6 +35,7 @@ from superset.models.sql_lab import SavedQuery from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes from superset.superset_typing import FlaskResponse +from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success @@ -47,6 +49,18 @@ def process_template(content: str) -> str: } return template.render(context) +class TagModelView(SupersetModelView): + route_base = "/superset/tags" + datamodel = SQLAInterface(Tag) + class_permission_name = "Tags" + + @has_access + @expose("/") + def list(self) -> FlaskResponse: + if not is_feature_enabled("TAGGING_SYSTEM"): + return super().list() + + return super().render_app_template() class TagView(BaseSupersetView): @staticmethod From f5a36d68191629e872a781182bd27fe89563a9c3 Mon Sep 17 00:00:00 2001 From: cccs-nik <68961854+cccs-nik@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:23:34 -0400 Subject: [PATCH 02/76] CLDN-1440 package.json --- superset-frontend/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/superset-frontend/package.json b/superset-frontend/package.json index ab63666827298..73d3f91766011 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -182,6 +182,7 @@ "react-sticky": "^6.0.3", "react-syntax-highlighter": "^15.4.5", "react-table": "^7.6.3", + "react-tag-autocomplete": "^6.3.0", "react-transition-group": "^2.5.3", "react-ultimate-pagination": "^1.2.0", "react-virtualized": "9.19.1", @@ -255,6 +256,7 @@ "@types/react-select": "^3.0.19", "@types/react-sticky": "^6.0.3", "@types/react-table": "^7.0.19", + "@types/react-tag-autocomplete": "^6.1.1", "@types/react-ultimate-pagination": "^1.2.0", "@types/react-virtualized": "^9.21.10", "@types/react-window": "^1.8.2", From fa93533197bc997f5723dfc15fc35f43c2cb21ed Mon Sep 17 00:00:00 2001 From: cccs-nik <68961854+cccs-nik@users.noreply.github.com> Date: Thu, 23 Jun 2022 17:30:47 -0400 Subject: [PATCH 03/76] WIP --- .../src/utils/featureFlags.ts | 1 + .../src/components/ObjectTags/ObjectTags.css | 82 +++---------------- .../src/components/ObjectTags/index.tsx | 5 +- .../src/views/CRUD/chart/ChartList.tsx | 2 + .../views/CRUD/dashboard/DashboardList.tsx | 2 + .../CRUD/data/savedquery/SavedQueryList.tsx | 2 + .../src/views/CRUD/tags/Tags.tsx | 3 - superset-frontend/src/views/routes.tsx | 13 +-- superset/initialization/__init__.py | 3 + 9 files changed, 33 insertions(+), 80 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts index fb994450046b7..a4fa3cc6d1960 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts @@ -56,6 +56,7 @@ export enum FeatureFlag { GENERIC_CHART_AXES = 'GENERIC_CHART_AXES', USE_ANALAGOUS_COLORS = 'USE_ANALAGOUS_COLORS', DASHBOARD_EDIT_CHART_IN_NEW_TAB = 'DASHBOARD_EDIT_CHART_IN_NEW_TAB', + TAGGING_SYSTEM = 'TAGGING_SYSTEM', } export type ScheduleQueriesProps = { JSONSCHEMA: { diff --git a/superset-frontend/src/components/ObjectTags/ObjectTags.css b/superset-frontend/src/components/ObjectTags/ObjectTags.css index b21bc74f4b091..60d2f787f8530 100644 --- a/superset-frontend/src/components/ObjectTags/ObjectTags.css +++ b/superset-frontend/src/components/ObjectTags/ObjectTags.css @@ -16,31 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -/** - *
- *
- * - *
- * } menuDropdownProps={{ diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index f9ed72feb883b..225801ace06c2 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -37,7 +37,6 @@ import { sliceUpdated } from 'src/explore/actions/exploreActions'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu'; import CertifiedBadge from 'src/components/CertifiedBadge'; -import ExploreActionButtons from '../ExploreActionButtons'; import RowCountLabel from '../RowCountLabel'; import ObjectTags from 'src/components/ObjectTags'; import { @@ -79,10 +78,10 @@ const saveButtonStyles = theme => css` } `; -const StyledButtons = styled.span` - display: flex; - align-items: center; -`; +// const StyledButtons = styled.span` +// display: flex; +// align-items: center; +// `; export const ExploreChartHeader = ({ dashboardId, @@ -166,6 +165,32 @@ export const ExploreChartHeader = ({ ownState, ); + const handleFetchTags = () => { + return fetchTags({ + objectType: OBJECT_TYPES.CHART, + objectId: chart.id, + includeTypes: false, + }) + } + + const handleFetchSuggestions = () => { + return fetchSuggestions({includeTypes: false}); + } + + const handleDeleteTag = () => { + return deleteTag({ + objectType: OBJECT_TYPES.CHART, + objectId: chart.id, + }); + } + const handleAddTag = () => { + return addTag({ + objectType: OBJECT_TYPES.CHART, + objectId: chart.id, + includeTypes: false, + }) + } + const oldSliceName = slice?.slice_name; return ( <> @@ -207,21 +232,10 @@ export const ExploreChartHeader = ({ ) : null, isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( Date: Tue, 19 Jul 2022 13:42:14 -0400 Subject: [PATCH 12/76] added select component for adding new tags --- .../src/components/ObjectTags/index.tsx | 207 +++++++++++++++--- superset-frontend/src/components/Tags/Tag.tsx | 75 +++++++ .../{TagsList/index.tsx => Tags/TagsList.tsx} | 65 ++++-- .../Tag.ts => components/Tags/index.tsx} | 9 +- .../src/dashboard/components/Header/index.jsx | 83 +++++-- .../components/ExploreChartHeader/index.jsx | 4 +- superset-frontend/src/tags.ts | 2 +- superset-frontend/src/types/Chart.ts | 2 +- superset-frontend/src/types/TagType.ts | 30 +++ .../src/views/CRUD/chart/ChartList.tsx | 4 +- .../views/CRUD/dashboard/DashboardList.tsx | 4 +- .../views/CRUD/data/dataset/DatasetList.tsx | 4 +- .../CRUD/data/savedquery/SavedQueryList.tsx | 4 +- .../src/views/CRUD/tags/Tags.tsx | 2 +- 14 files changed, 404 insertions(+), 91 deletions(-) create mode 100644 superset-frontend/src/components/Tags/Tag.tsx rename superset-frontend/src/components/{TagsList/index.tsx => Tags/TagsList.tsx} (51%) rename superset-frontend/src/{types/Tag.ts => components/Tags/index.tsx} (87%) create mode 100644 superset-frontend/src/types/TagType.ts diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 75a1057588c71..d8eb79433456e 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -17,19 +17,18 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; -import ReactTags from 'react-tag-autocomplete'; -import { t, styled } from '@superset-ui/core'; -import { TagsList } from 'src/components/TagsList'; -import AntdTag from 'antd/lib/tag'; -import Tag from 'src/types/Tag'; +import React, { useCallback, useEffect, useState } from 'react'; +import { styled, SupersetClient, t } from '@superset-ui/core'; +import Tag from 'src/types/TagType'; import './ObjectTags.css'; - -interface TagComponentProps { - tag: Tag; - onDelete: (event: React.MouseEvent) => void; -} +import { TagsList } from 'src/components/Tags'; +import Icons from '../Icons'; +import AsyncSelect from '../Select/AsyncSelect'; +import rison from 'rison'; +import { cacheWrapper } from 'src/utils/cacheWrapper'; +import { ClientErrorObject, getClientErrorObject } from 'src/utils/getClientErrorObject'; +import { SelectValue } from 'antd/lib/select'; interface ObjectTagsProps { fetchTags: (callback: (tags: Tag[]) => void) => void; @@ -40,16 +39,147 @@ interface ObjectTagsProps { onChange?: (tags: Tag[]) => void; } +interface SelectTagsProps { + onSelectTag: (tag: Tag) => void; +} + const TagsDiv = styled.div` margin-left: ${({ theme }) => theme.gridUnit * 2}px; + max-width: 100%; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + -webkit-flex-wrap: wrap; +`; + +const AddTags = styled.div` + max-width: 100%; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + -webkit-flex-wrap: wrap; `; -const TagComponent = ({ tag, onDelete }: TagComponentProps) => ( - - {tag.name} - +const localCache = new Map(); + +const cachedSupersetGet = cacheWrapper( + SupersetClient.get, + localCache, + ({ endpoint }) => endpoint || '', ); +type SelectTagsValue = { + value: string | number | undefined; + label: string; +}; + +export const tagToSelectOption = ( + item: Tag & { table_name: string }, +): SelectTagsValue => ({ + value: item.id, + label: item.name, +}); + +const SelectTags = ({ + onSelectTag, +}: SelectTagsProps) => { + + const [selectValue, setSelectValue] = useState(); + + const getErrorMessage = useCallback( + ({ error, message }: ClientErrorObject) => { + let errorText = message || error || t('An error has occurred'); + if (message === 'Forbidden') { + errorText = t('You do not have permission to edit this dashboard'); + } + return errorText; + }, + [], + ); + + const loadTags = async ( + search: string, + page: number, + pageSize: number, + ) => { + const searchColumn = 'name'; + const query = rison.encode({ + filters: [{ col: searchColumn, opr: 'ct', value: search }], + page, + page_size: pageSize, + order_column: searchColumn, + order_direction: 'asc', + }); + return cachedSupersetGet({ + endpoint: `/api/v1/tag/?q=${query}`, + // endpoint: `/api/v1/tags/?q=${query}`, + }) + .then(response => { + const data: { + label: string; + value: string | number; + }[] = response.json.result.map(tagToSelectOption); + return { + data, + totalCount: response.json.count, + }; + }) + .catch(async error => { + const errorMessage = getErrorMessage(await getClientErrorObject(error)); + throw new Error(errorMessage); + }); + }; + + const onSelect = (value: { label: string; value: number }) => { + onSelectTag({name: value.label}); + setSelectValue(''); + }; + + const selectProps = { + allowClear: true, + allowNewOptions: true, + ariaLabel: 'Tags', + labelInValue: true, + options: loadTags, + pageSize: 10, + showSearch: true, + }; + + return (); +}; + +const EditButton = ({ + onClick, + visible=true, +} : any ) => { + if(!visible) {return null} + return ( +
+ +
+ ); +} + +const SaveButton = ({ + onClick, +} : any ) => { + return ( +
+ +
+ ); +} + export const ObjectTags = ({ fetchTags, fetchSuggestions, @@ -61,6 +191,8 @@ export const ObjectTags = ({ const [tags, setTags] = useState([]); const [tagSuggestions, setTagSuggestions] = useState([]); const [filteredSuggestions, setFilteredSuggestions] = useState([]); + const [showEditButton, setShowEditButton] = useState(false); + const [editMode, setEditMode] = useState(false); const filterSuggestions = () => { setFilteredSuggestions( @@ -72,10 +204,12 @@ export const ObjectTags = ({ useEffect(() => { fetchTags((tags: Tag[]) => { - setTags(tags); + setTags(tags) }); + + fetchSuggestions((suggestions: Tag[]) => { - setTagSuggestions(suggestions); + suggestions && setTagSuggestions(suggestions); }); }, []); @@ -100,8 +234,21 @@ export const ObjectTags = ({ onChange?.(tags); }; - if (editable) { - return ( + const toggleEditMode = () => { + setEditMode(!editMode); + } + + const handleMouseOver = () => { + setShowEditButton(true); + } + + const handleMouseLeave = () => { + setShowEditButton(false); + } + + return ( + + {/* {false ? ( - ); - } - - return ( - - - + ) : ( */} + + + {editMode ? ( + + + + + ) : ( + + )} + + {/* ) */} + {/* } */} + ); }; diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx new file mode 100644 index 0000000000000..14c576d240da9 --- /dev/null +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -0,0 +1,75 @@ +/** + * 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 { styled, SupersetTheme } from "@superset-ui/core"; +import Tag from "src/types/TagType"; +import AntdTag from 'antd/lib/tag'; + +const customTagStyler = (theme: SupersetTheme) => ` + margin-top: ${theme.gridUnit * 1}px; + margin-bottom: ${theme.gridUnit * 1}px; + font-size: ${theme.typography.sizes.s}px; +`; + +const StyledTag = styled(AntdTag)` + ${({ theme }) => customTagStyler(theme)}} +`; + +const Tag = ({ + name, + id, + index=-1, + onDelete=null, + editable=false, + onClick=null +} : Tag) => { + + const handleClose = () => { + return (index >= 0) ? ( + onDelete(index) + ) : ( + null + ) + + return onDelete(index); + } + return ( +
+ {editable ? ( + + {name} + + ) : ( + + {id ? ( + {name} + ):( + name + )} + + )} +
+ ); +} + +export default Tag; diff --git a/superset-frontend/src/components/TagsList/index.tsx b/superset-frontend/src/components/Tags/TagsList.tsx similarity index 51% rename from superset-frontend/src/components/TagsList/index.tsx rename to superset-frontend/src/components/Tags/TagsList.tsx index 741a99cddb577..4c13aa0c3375e 100644 --- a/superset-frontend/src/components/TagsList/index.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -17,25 +17,18 @@ * under the License. */ -import React from 'react'; -import { styled, SupersetTheme } from '@superset-ui/core'; -import AntdTag from 'antd/lib/tag'; -import Tag from 'src/types/Tag'; +import React, { useState } from 'react'; +import { styled, SupersetTheme, t } from '@superset-ui/core'; +import Tag from './Tag'; +import TagType from 'src/types/TagType'; +import Icons from '../Icons'; export type TagsListProps = { - tags: Tag[]; + tags: TagType[]; + editable?: boolean; + onDelete: (index: number) => void; }; -const customTagStyler = (theme: SupersetTheme) => ` - margin-top: ${theme.gridUnit * 1}px; - margin-bottom: ${theme.gridUnit * 1}px; - font-size: ${theme.typography.sizes.s}px; -`; - -const StyledTag = styled(AntdTag)` - ${({ theme }) => customTagStyler(theme)} -`; - const TagsDiv = styled.div` max-width: 100%; display: -webkit-flex; @@ -44,12 +37,36 @@ const TagsDiv = styled.div` -webkit-flex-wrap: wrap; `; -export const TagsList = ({ tags }: TagsListProps) => ( - - {tags.map((tag: Tag) => ( - - {tag.name} - - ))} - -); +const EditButton = ({ + onClick, +} : any ) => { + return ( +
+ +
+ ); +} +const TagsList = ({ + tags, + editable=false, + onDelete=(index) => null, +}: TagsListProps) => { + + const handleDelete = (index: number) => { + onDelete(index); + } + return ( + + {tags.map((tag: TagType, index) => ( + + ))} + + ); +}; + +export default TagsList; diff --git a/superset-frontend/src/types/Tag.ts b/superset-frontend/src/components/Tags/index.tsx similarity index 87% rename from superset-frontend/src/types/Tag.ts rename to superset-frontend/src/components/Tags/index.tsx index ab797b3a24fce..d9178e7a26f23 100644 --- a/superset-frontend/src/types/Tag.ts +++ b/superset-frontend/src/components/Tags/index.tsx @@ -17,10 +17,5 @@ * under the License. */ -export interface Tag { - id: string | number; - type?: string | number; - name: string; -} - -export default Tag; +export { default as TagsList } from './TagsList'; +export { default as Tag } from './Tag'; diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index 05a1f41a238e7..9d4ca2729c7a5 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -198,23 +198,27 @@ class Header extends React.PureComponent { this.showPropertiesModal = this.showPropertiesModal.bind(this); this.hidePropertiesModal = this.hidePropertiesModal.bind(this); this.setIsDropdownVisible = this.setIsDropdownVisible.bind(this); - this.fetchTags = fetchTags.bind(this, { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: props.dashboardInfo.id, - includeTypes: false, - }); - this.fetchSuggestions = fetchSuggestions.bind(this, { - includeTypes: false, - }); - this.deleteTag = deleteTag.bind(this, { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: props.dashboardInfo.id, - }); - this.addTag = addTag.bind(this, { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: props.dashboardInfo.id, - includeTypes: false, - }); + // this.fetchTags = fetchTags.bind(this, { + // objectType: OBJECT_TYPES.DASHBOARD, + // objectId: props.dashboardInfo.id, + // includeTypes: false, + // }); + // this.fetchSuggestions = fetchSuggestions.bind(this, { + // includeTypes: false, + // }); + // this.deleteTag = deleteTag.bind(this, { + // objectType: OBJECT_TYPES.DASHBOARD, + // objectId: props.dashboardInfo.id, + // }); + // this.addTag = addTag.bind(this, { + // objectType: OBJECT_TYPES.DASHBOARD, + // objectId: props.dashboardInfo.id, + // includeTypes: false, + // }); + this.handleFetchTags = this.handleFetchTags.bind(this); + this.handleFetchSuggestions = this.handleFetchSuggestions.bind(this); + this.handleDeleteTag = this.handleDeleteTag.bind(this); + this.handleAddTag = this.handleAddTag.bind(this); } componentDidMount() { @@ -252,6 +256,43 @@ class Header extends React.PureComponent { clearTimeout(this.ctrlZTimeout); } + handleFetchTags(callback) { + if (!this.props.isLoading) { + fetchTags({ + objectType: OBJECT_TYPES.DASHBOARD, + objectId: this.props.dashboardInfo.id, + includeTypes: false, + }, callback); + } + } + + handleFetchSuggestions(callback){ + if (!this.props.isLoading) { + fetchSuggestions({ + includeTypes: false, + }, callback); + } + } + + handleDeleteTag(tag, callback){ + if (!this.props.isLoading) { + deleteTag({ + objectType: OBJECT_TYPES.DASHBOARD, + objectId: this.props.dashboardInfo.id, + }, tag, callback); + } + } + + handleAddTag(tag, callback){ + if (!this.props.isLoading) { + addTag({ + objectType: OBJECT_TYPES.DASHBOARD, + objectId: this.props.dashboardInfo.id, + includeTypes: false, + }, tag, callback); + } + } + handleChangeText(nextText) { const { updateDashboardTitle, onChange } = this.props; if (nextText && this.props.dashboardTitle !== nextText) { @@ -547,10 +588,10 @@ class Header extends React.PureComponent { />, isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( ) : null diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index 225801ace06c2..2709bd0fa25fe 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -183,12 +183,12 @@ export const ExploreChartHeader = ({ objectId: chart.id, }); } - const handleAddTag = () => { + const handleAddTag = (tag, callback) => { return addTag({ objectType: OBJECT_TYPES.CHART, objectId: chart.id, includeTypes: false, - }) + }, tag, callback); } const oldSliceName = slice?.slice_name; diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index 37906ab0307a8..c3c8f2cd33fcf 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -17,7 +17,7 @@ * under the License. */ import { JsonObject, SupersetClient } from '@superset-ui/core'; -import Tag from 'src/types/Tag'; +import Tag from 'src/types/TagType'; export const OBJECT_TYPES = Object.freeze({ DASHBOARD: 'dashboard', diff --git a/superset-frontend/src/types/Chart.ts b/superset-frontend/src/types/Chart.ts index ea10c4e23a9fd..d18e09b6e1749 100644 --- a/superset-frontend/src/types/Chart.ts +++ b/superset-frontend/src/types/Chart.ts @@ -23,7 +23,7 @@ import { QueryFormData } from '@superset-ui/core'; import Owner from './Owner'; -import Tag from './Tag'; +import Tag from './TagType'; export interface Chart { id: number; diff --git a/superset-frontend/src/types/TagType.ts b/superset-frontend/src/types/TagType.ts new file mode 100644 index 0000000000000..e63f46ad2ef78 --- /dev/null +++ b/superset-frontend/src/types/TagType.ts @@ -0,0 +1,30 @@ +/** + * 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. + */ + +export interface TagType { + id?: string | number; + type?: string | number; + editable?: boolean + onDelete?: any; + onClick?: any; + name: string; + index?: number; +} + +export default TagType; diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 30f1ba8df32e4..dbbe1263ed420 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -39,7 +39,7 @@ import { } from 'src/views/CRUD/hooks'; import handleResourceExport from 'src/utils/export'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; -import { TagsList } from 'src/components/TagsList'; +import { TagsList } from 'src/components/Tags'; import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; import FaveStar from 'src/components/FaveStar'; import { Link, useHistory } from 'react-router-dom'; @@ -56,7 +56,7 @@ import withToasts from 'src/components/MessageToasts/withToasts'; import PropertiesModal from 'src/explore/components/PropertiesModal'; import ImportModelsModal from 'src/components/ImportModal/index'; import Chart from 'src/types/Chart'; -import Tag from 'src/types/Tag'; +import Tag from 'src/types/TagType'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils'; diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 1e6cbc6445f94..7c92154e19d29 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -28,7 +28,7 @@ import { } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; -import { TagsList } from 'src/components/TagsList'; +import { TagsList } from 'src/components/Tags'; import handleResourceExport from 'src/utils/export'; import Loading from 'src/components/Loading'; import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; @@ -40,7 +40,7 @@ import ListView, { } from 'src/components/ListView'; import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers'; import Owner from 'src/types/Owner'; -import Tag from 'src/types/Tag'; +import Tag from 'src/types/TagType'; import withToasts from 'src/components/MessageToasts/withToasts'; import FacePile from 'src/components/FacePile'; import Icons from 'src/components/Icons'; diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index fcda993f6011b..47ebe8a9b6459 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -50,7 +50,7 @@ import SubMenu, { } from 'src/views/components/SubMenu'; import { commonMenuData } from 'src/views/CRUD/data/common'; import Owner from 'src/types/Owner'; -import Tag from 'src/types/Tag'; +import Tag from 'src/types/TagType'; import withToasts from 'src/components/MessageToasts/withToasts'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; @@ -58,7 +58,7 @@ import FacePile from 'src/components/FacePile'; import CertifiedBadge from 'src/components/CertifiedBadge'; import InfoTooltip from 'src/components/InfoTooltip'; import ImportModelsModal from 'src/components/ImportModal/index'; -import { TagsList } from 'src/components/TagsList'; +import { TagsList } from 'src/components/Tags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip'; import { isUserAdmin } from 'src/dashboard/util/permissionUtils'; diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx index 4d85db1bc9da4..32cf0933d4247 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx @@ -43,12 +43,12 @@ import ListView, { import Loading from 'src/components/Loading'; import DeleteModal from 'src/components/DeleteModal'; import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar'; -import { TagsList } from 'src/components/TagsList'; +import { TagsList } from 'src/components/Tags'; import { Tooltip } from 'src/components/Tooltip'; import { commonMenuData } from 'src/views/CRUD/data/common'; import { SavedQueryObject } from 'src/views/CRUD/types'; import copyTextToClipboard from 'src/utils/copy'; -import Tag from 'src/types/Tag'; +import Tag from 'src/types/TagType'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import ImportModelsModal from 'src/components/ImportModal/index'; import Icons from 'src/components/Icons'; diff --git a/superset-frontend/src/views/CRUD/tags/Tags.tsx b/superset-frontend/src/views/CRUD/tags/Tags.tsx index 98c4a21d65258..8ee0af8ffb2d9 100644 --- a/superset-frontend/src/views/CRUD/tags/Tags.tsx +++ b/superset-frontend/src/views/CRUD/tags/Tags.tsx @@ -18,7 +18,7 @@ */ import React, { useEffect, useState } from 'react'; import { styled } from '@superset-ui/core'; -import Tag from 'src/types/Tag'; +import Tag from 'src/types/TagType'; import { StringParam, useQueryParam } from 'use-query-params'; import withToasts from 'src/components/MessageToasts/withToasts'; import SelectControl from 'src/explore/components/controls/SelectControl'; From 97b461390bb5a199a85aec27eb1aff13147cad67 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 20 Jul 2022 15:06:13 -0400 Subject: [PATCH 13/76] changed code for better understandability and function --- .../src/components/ObjectTags/index.tsx | 46 ++++++++----- .../src/dashboard/components/Header/index.jsx | 65 +------------------ .../components/ExploreChartHeader/index.jsx | 42 ++---------- 3 files changed, 35 insertions(+), 118 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index d8eb79433456e..7204608262632 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -29,12 +29,12 @@ import rison from 'rison'; import { cacheWrapper } from 'src/utils/cacheWrapper'; import { ClientErrorObject, getClientErrorObject } from 'src/utils/getClientErrorObject'; import { SelectValue } from 'antd/lib/select'; +import { addTag, deleteTag, fetchSuggestions, fetchTags } from 'src/tags'; interface ObjectTagsProps { - fetchTags: (callback: (tags: Tag[]) => void) => void; - fetchSuggestions: (callback: (suggestions: Tag[]) => void) => void; - deleteTag: (tag: string, callback: () => void) => void; - addTag: (tag: string, callback: () => void) => void; + objectType: string; + objectId: number; + includeTypes: boolean; editable: boolean; onChange?: (tags: Tag[]) => void; } @@ -181,10 +181,9 @@ const SaveButton = ({ } export const ObjectTags = ({ - fetchTags, - fetchSuggestions, - deleteTag, - addTag, + objectType, + objectId, + includeTypes, editable, onChange, }: ObjectTagsProps) => { @@ -203,15 +202,18 @@ export const ObjectTags = ({ }; useEffect(() => { - fetchTags((tags: Tag[]) => { - setTags(tags) - }); + fetchTags( + {objectType, objectId, includeTypes}, + (tags: Tag[]) => setTags(tags), + () => {/*TODO: handle error*/}); - fetchSuggestions((suggestions: Tag[]) => { - suggestions && setTagSuggestions(suggestions); - }); - }, []); + fetchSuggestions( + {includeTypes}, + (suggestions: Tag[]) => suggestions && setTagSuggestions(suggestions), + () => {/* TODO: handle error */} + ); + }, [objectType, objectId, includeTypes]); useEffect(() => { if (tagSuggestions.length !== 0) { @@ -220,8 +222,11 @@ export const ObjectTags = ({ }, [tags, tagSuggestions]); const onDelete = (tagIndex: number) => { - deleteTag(tags[tagIndex].name, () => - setTags(tags.filter((_, i) => i !== tagIndex)), + deleteTag( + {objectType, objectId}, + tags[tagIndex], + () => setTags(tags.filter((_, i) => i !== tagIndex)), + () => {/* TODO: handle error */} ); onChange?.(tags); }; @@ -230,7 +235,12 @@ export const ObjectTags = ({ if (tags.some(t => t.name === tag.name)) { return; } - addTag(tag.name, () => setTags([...tags, tag])); + addTag( + {objectType, objectId, includeTypes}, + tag.name, + () => setTags([...tags, tag]), + () => {/* TODO: handle error */} + ); onChange?.(tags); }; diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index 9d4ca2729c7a5..2b5855e1ebf0d 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -198,27 +198,6 @@ class Header extends React.PureComponent { this.showPropertiesModal = this.showPropertiesModal.bind(this); this.hidePropertiesModal = this.hidePropertiesModal.bind(this); this.setIsDropdownVisible = this.setIsDropdownVisible.bind(this); - // this.fetchTags = fetchTags.bind(this, { - // objectType: OBJECT_TYPES.DASHBOARD, - // objectId: props.dashboardInfo.id, - // includeTypes: false, - // }); - // this.fetchSuggestions = fetchSuggestions.bind(this, { - // includeTypes: false, - // }); - // this.deleteTag = deleteTag.bind(this, { - // objectType: OBJECT_TYPES.DASHBOARD, - // objectId: props.dashboardInfo.id, - // }); - // this.addTag = addTag.bind(this, { - // objectType: OBJECT_TYPES.DASHBOARD, - // objectId: props.dashboardInfo.id, - // includeTypes: false, - // }); - this.handleFetchTags = this.handleFetchTags.bind(this); - this.handleFetchSuggestions = this.handleFetchSuggestions.bind(this); - this.handleDeleteTag = this.handleDeleteTag.bind(this); - this.handleAddTag = this.handleAddTag.bind(this); } componentDidMount() { @@ -256,43 +235,6 @@ class Header extends React.PureComponent { clearTimeout(this.ctrlZTimeout); } - handleFetchTags(callback) { - if (!this.props.isLoading) { - fetchTags({ - objectType: OBJECT_TYPES.DASHBOARD, - objectId: this.props.dashboardInfo.id, - includeTypes: false, - }, callback); - } - } - - handleFetchSuggestions(callback){ - if (!this.props.isLoading) { - fetchSuggestions({ - includeTypes: false, - }, callback); - } - } - - handleDeleteTag(tag, callback){ - if (!this.props.isLoading) { - deleteTag({ - objectType: OBJECT_TYPES.DASHBOARD, - objectId: this.props.dashboardInfo.id, - }, tag, callback); - } - } - - handleAddTag(tag, callback){ - if (!this.props.isLoading) { - addTag({ - objectType: OBJECT_TYPES.DASHBOARD, - objectId: this.props.dashboardInfo.id, - includeTypes: false, - }, tag, callback); - } - } - handleChangeText(nextText) { const { updateDashboardTitle, onChange } = this.props; if (nextText && this.props.dashboardTitle !== nextText) { @@ -588,10 +530,9 @@ class Header extends React.PureComponent { />, isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( ) : null diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index 2709bd0fa25fe..2d4afcc303594 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -39,16 +39,9 @@ import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsM import CertifiedBadge from 'src/components/CertifiedBadge'; import RowCountLabel from '../RowCountLabel'; import ObjectTags from 'src/components/ObjectTags'; -import { - addTag, - deleteTag, - fetchSuggestions, - fetchTags, - OBJECT_TYPES, -} from 'src/tags'; +import {OBJECT_TYPES} from 'src/tags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; - const CHART_STATUS_MAP = { failed: 'danger', loading: 'warning', @@ -165,32 +158,6 @@ export const ExploreChartHeader = ({ ownState, ); - const handleFetchTags = () => { - return fetchTags({ - objectType: OBJECT_TYPES.CHART, - objectId: chart.id, - includeTypes: false, - }) - } - - const handleFetchSuggestions = () => { - return fetchSuggestions({includeTypes: false}); - } - - const handleDeleteTag = () => { - return deleteTag({ - objectType: OBJECT_TYPES.CHART, - objectId: chart.id, - }); - } - const handleAddTag = (tag, callback) => { - return addTag({ - objectType: OBJECT_TYPES.CHART, - objectId: chart.id, - includeTypes: false, - }, tag, callback); - } - const oldSliceName = slice?.slice_name; return ( <> @@ -232,10 +199,9 @@ export const ExploreChartHeader = ({ ) : null, isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( Date: Thu, 21 Jul 2022 08:20:04 -0400 Subject: [PATCH 14/76] aligned edit button vertically --- .../src/components/ObjectTags/index.tsx | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 7204608262632..6a217fa791882 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -144,15 +144,21 @@ const SelectTags = ({ pageSize: 10, showSearch: true, }; - + const styling = { + verticalAlign: '-webkit-baseline-middle' + } return (); }; -const EditButton = ({ + +const EditTagsButton = ({ onClick, visible=true, } : any ) => { if(!visible) {return null} + const styling = { + verticalAlign: '-webkit-baseline-middle' + } return (
- +
); } @@ -202,11 +208,14 @@ export const ObjectTags = ({ }; useEffect(() => { - fetchTags( - {objectType, objectId, includeTypes}, - (tags: Tag[]) => setTags(tags), - () => {/*TODO: handle error*/}); - + try { + fetchTags( + {objectType, objectId, includeTypes}, + (tags: Tag[]) => setTags(tags), + () => {/*TODO: handle error*/}); + } catch(error: any) { + console.log(error) + } fetchSuggestions( {includeTypes}, @@ -278,7 +287,7 @@ export const ObjectTags = ({ ) : ( - + )} {/* ) */} From ca2e2121d30a6e8dce0ff8f13bc4177bef964c3e Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 21 Jul 2022 16:06:52 -0400 Subject: [PATCH 15/76] moved tags editing to properties editor in chart explorer --- .../src/components/ObjectTags/index.tsx | 185 ++++++++---------- superset-frontend/src/components/Tags/Tag.tsx | 33 +++- .../src/components/Tags/TagsList.tsx | 38 ++-- .../src/dashboard/components/Header/index.jsx | 1 - .../components/ExploreChartHeader/index.jsx | 7 - .../components/PropertiesModal/index.tsx | 27 +++ superset-frontend/src/tags.ts | 2 +- 7 files changed, 153 insertions(+), 140 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 6a217fa791882..c7642d838ca60 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -18,7 +18,7 @@ */ import React, { useCallback, useEffect, useState } from 'react'; -import { styled, SupersetClient, t } from '@superset-ui/core'; +import { styled, SupersetClient, t, useTheme } from '@superset-ui/core'; import Tag from 'src/types/TagType'; import './ObjectTags.css'; @@ -30,12 +30,14 @@ import { cacheWrapper } from 'src/utils/cacheWrapper'; import { ClientErrorObject, getClientErrorObject } from 'src/utils/getClientErrorObject'; import { SelectValue } from 'antd/lib/select'; import { addTag, deleteTag, fetchSuggestions, fetchTags } from 'src/tags'; +import { StyledModal } from 'src/components/Modal'; interface ObjectTagsProps { objectType: string; objectId: number; includeTypes: boolean; - editable: boolean; + editMode: boolean; + maxTags: number | null; onChange?: (tags: Tag[]) => void; } @@ -43,21 +45,30 @@ interface SelectTagsProps { onSelectTag: (tag: Tag) => void; } -const TagsDiv = styled.div` +const StyledTagsDiv = styled.div` margin-left: ${({ theme }) => theme.gridUnit * 2}px; max-width: 100%; display: -webkit-flex; display: flex; -webkit-flex-direction: row; -webkit-flex-wrap: wrap; -`; + ` -const AddTags = styled.div` +const StyledTagsDropdown = styled.div` + margin-left: ${({ theme }) => theme.gridUnit * 2}px; max-width: 100%; display: -webkit-flex; display: flex; -webkit-flex-direction: row; -webkit-flex-wrap: wrap; + backgroundColor: ${({ theme }) => theme.colors.grayscale.light4}; + ` + +const AddTags = styled.div` + max-width: 100%; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; `; const localCache = new Map(); @@ -80,55 +91,54 @@ export const tagToSelectOption = ( label: item.name, }); -const SelectTags = ({ - onSelectTag, -}: SelectTagsProps) => { - - const [selectValue, setSelectValue] = useState(); - - const getErrorMessage = useCallback( - ({ error, message }: ClientErrorObject) => { +export const loadTags = async ( + search: string, + page: number, + pageSize: number, +) => { + const searchColumn = 'name'; + const query = rison.encode({ + filters: [{ col: searchColumn, opr: 'ct', value: search }], + page, + page_size: pageSize, + order_column: searchColumn, + order_direction: 'asc', + }); + + const getErrorMessage = ({ error, message }: ClientErrorObject) => { let errorText = message || error || t('An error has occurred'); if (message === 'Forbidden') { errorText = t('You do not have permission to edit this dashboard'); } return errorText; - }, - [], - ); - - const loadTags = async ( - search: string, - page: number, - pageSize: number, - ) => { - const searchColumn = 'name'; - const query = rison.encode({ - filters: [{ col: searchColumn, opr: 'ct', value: search }], - page, - page_size: pageSize, - order_column: searchColumn, - order_direction: 'asc', - }); - return cachedSupersetGet({ - endpoint: `/api/v1/tag/?q=${query}`, - // endpoint: `/api/v1/tags/?q=${query}`, + } + + return cachedSupersetGet({ + endpoint: `/api/v1/tag/?q=${query}`, + // endpoint: `/api/v1/tags/?q=${query}`, + }) + .then(response => { + const data: { + label: string; + value: string | number; + }[] = response.json.result.map(tagToSelectOption); + return { + data, + totalCount: response.json.count, + }; }) - .then(response => { - const data: { - label: string; - value: string | number; - }[] = response.json.result.map(tagToSelectOption); - return { - data, - totalCount: response.json.count, - }; - }) - .catch(async error => { - const errorMessage = getErrorMessage(await getClientErrorObject(error)); - throw new Error(errorMessage); - }); - }; + .catch(async error => { + const errorMessage = getErrorMessage(await getClientErrorObject(error)); + throw new Error(errorMessage); + }); +}; + +const SelectTags = ({ + onSelectTag, +}: SelectTagsProps) => { + + const [selectValue, setSelectValue] = useState(); + const onSelect = (value: { label: string; value: number }) => { onSelectTag({name: value.label}); @@ -144,9 +154,6 @@ const SelectTags = ({ pageSize: 10, showSearch: true, }; - const styling = { - verticalAlign: '-webkit-baseline-middle' - } return (); }; @@ -174,6 +181,10 @@ const EditTagsButton = ({ const SaveButton = ({ onClick, } : any ) => { + const styling = { + verticalAlign: '-webkit-baseline-middle', + marginLeft: `inherit` + } return (
- +
); } @@ -190,22 +201,12 @@ export const ObjectTags = ({ objectType, objectId, includeTypes, - editable, + editMode=false, + maxTags=null, onChange, }: ObjectTagsProps) => { const [tags, setTags] = useState([]); - const [tagSuggestions, setTagSuggestions] = useState([]); - const [filteredSuggestions, setFilteredSuggestions] = useState([]); - const [showEditButton, setShowEditButton] = useState(false); - const [editMode, setEditMode] = useState(false); - - const filterSuggestions = () => { - setFilteredSuggestions( - [...tagSuggestions].filter(suggestion => - tags.every(tag => tag.name !== suggestion.name), - ), - ); - }; + const [showTagsDropdown, setShowTagsDropdown] = useState(false); useEffect(() => { try { @@ -216,19 +217,21 @@ export const ObjectTags = ({ } catch(error: any) { console.log(error) } - - fetchSuggestions( - {includeTypes}, - (suggestions: Tag[]) => suggestions && setTagSuggestions(suggestions), - () => {/* TODO: handle error */} - ); }, [objectType, objectId, includeTypes]); - useEffect(() => { - if (tagSuggestions.length !== 0) { - filterSuggestions(); + const theme = useTheme(); + + const TagsDiv = ({ + ...props + }: any ) => { + if(showTagsDropdown){ + return ( + + ); } - }, [tags, tagSuggestions]); + return () + }; const onDelete = (tagIndex: number) => { deleteTag( @@ -253,45 +256,19 @@ export const ObjectTags = ({ onChange?.(tags); }; - const toggleEditMode = () => { - setEditMode(!editMode); - } - const handleMouseOver = () => { - setShowEditButton(true); + setShowTagsDropdown(true); } const handleMouseLeave = () => { - setShowEditButton(false); + setShowTagsDropdown(false); } return ( - {/* {false ? ( - - ) : ( */} - - {editMode ? ( - - - - - ) : ( - - )} + - {/* ) */} - {/* } */} ); }; diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index 14c576d240da9..0123dd4da76a1 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -20,6 +20,9 @@ import { styled, SupersetTheme } from "@superset-ui/core"; import Tag from "src/types/TagType"; import AntdTag from 'antd/lib/tag'; +import { useMemo } from "react"; +import { Tooltip } from 'src/components/Tooltip'; +import React from 'react'; const customTagStyler = (theme: SupersetTheme) => ` margin-top: ${theme.gridUnit * 1}px; @@ -40,35 +43,47 @@ const Tag = ({ onClick=null } : Tag) => { + const isLongTag = useMemo(() => name.length > 20, [name]); + const handleClose = () => { return (index >= 0) ? ( onDelete(index) ) : ( null - ) - - return onDelete(index); + ) } - return ( -
+ + const tagElem =( + <> {editable ? ( - {name} + {isLongTag ? `${name.slice(0, 20)}...` : name} ) : ( {id ? ( - {name} + {isLongTag ? `${name.slice(0, 20)}...` : name} ):( - name + isLongTag ? `${name.slice(0, 20)}...` : name )} )} -
+ + ); + + return ( + isLongTag ? ( + + {tagElem} + + ) : ( + tagElem + ) + ); } diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index 4c13aa0c3375e..9e0852b660b71 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { styled, SupersetTheme, t } from '@superset-ui/core'; import Tag from './Tag'; import TagType from 'src/types/TagType'; @@ -27,6 +27,7 @@ export type TagsListProps = { tags: TagType[]; editable?: boolean; onDelete: (index: number) => void; + maxTags: number | null; }; const TagsDiv = styled.div` @@ -37,34 +38,35 @@ const TagsDiv = styled.div` -webkit-flex-wrap: wrap; `; -const EditButton = ({ - onClick, -} : any ) => { - return ( -
- -
- ); -} const TagsList = ({ tags, editable=false, onDelete=(index) => null, + maxTags=null }: TagsListProps) => { const handleDelete = (index: number) => { onDelete(index); } + + const tagsIsLong: boolean | null = useMemo(() => (maxTags ? (tags.length > maxTags): null), [tags.length, maxTags]); + + const extraTags: number | null = useMemo(() => ((typeof maxTags === "number") ? ((tags.length - maxTags) + 1) : null), [tagsIsLong, tags.length, maxTags]) + return ( - {tags.map((tag: TagType, index) => ( - - ))} + {(tagsIsLong === true && typeof maxTags === "number") ? ( + <> + {tags.slice(0,(maxTags-1)).map((tag: TagType, index) => ( + + ))} + {tags.length > maxTags ? () : (null)} + + ): ( + tags.map((tag: TagType, index) => ( + + )) + )} ); }; diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index 3d4860d01dff7..35992616bc48f 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -534,7 +534,6 @@ class Header extends React.PureComponent { objectType={OBJECT_TYPES.DASHBOARD} objectId={dashboardInfo.id} includeTypes={false} - editable={dashboardInfo.dash_edit_perm} /> ) : null ] diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index 2d4afcc303594..b953722fdb859 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -202,13 +202,6 @@ export const ExploreChartHeader = ({ objectType={OBJECT_TYPES.CHART} objectId={chart.id} includeTypes={false} - editable={ - canOverwrite || - (slice?.owners || []).includes( - user?.userId, - ) || - !!user?.roles?.Admin - } /> ) : null ] diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 3aa0fa60d2f90..fa2256cf0db90 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -27,6 +27,8 @@ import { t, SupersetClient, styled } from '@superset-ui/core'; import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; +import ObjectTags, { loadTags } from 'src/components/ObjectTags'; +import { OBJECT_TYPES } from 'src/tags'; export type PropertiesModalProps = { slice: Slice; @@ -63,6 +65,8 @@ function PropertiesModal({ null, ); + const [selectedTags, setSelectedTags] = useState(null,); + function showError({ error, statusText, message }: any) { let errorText = error || statusText || t('An error has occurred'); if (message === 'Forbidden') { @@ -314,6 +318,29 @@ function PropertiesModal({ )} +

{t('Tags')}

+ + + + {t( + 'A list of tags that have been applied to this chart.', + )} + + + diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index c3c8f2cd33fcf..16d0d1c14a776 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -71,7 +71,7 @@ export function deleteTag( } SupersetClient.delete({ endpoint: `/tagview/tags/${objectType}/${objectId}/`, - body: JSON.stringify([tag]), + body: JSON.stringify([tag.name]), parseMethod: 'text', }) .then(({ text }) => callback(text)) From de54aa0464932fa040fa17cf731931dcda1e1392 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 25 Jul 2022 09:37:40 -0400 Subject: [PATCH 16/76] fixes to tags displaying and updating --- .../src/components/ObjectTags/index.tsx | 140 +----------------- .../src/components/Tags/TagsList.tsx | 40 +++-- .../src/dashboard/components/Header/index.jsx | 29 ++-- .../components/PropertiesModal/index.tsx | 96 ++++++++++++ .../components/ExploreChartHeader/index.jsx | 28 +++- .../components/PropertiesModal/index.tsx | 84 +++++++++-- .../src/views/CRUD/chart/ChartList.tsx | 5 +- .../views/CRUD/dashboard/DashboardList.tsx | 8 +- superset/charts/api.py | 3 + superset/charts/schemas.py | 6 + superset/dashboards/schemas.py | 6 + 11 files changed, 265 insertions(+), 180 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index c7642d838ca60..46ae699a558eb 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -17,34 +17,26 @@ * under the License. */ -import React, { useCallback, useEffect, useState } from 'react'; -import { styled, SupersetClient, t, useTheme } from '@superset-ui/core'; +import React, { useEffect, useState } from 'react'; +import { styled, SupersetClient, t } from '@superset-ui/core'; import Tag from 'src/types/TagType'; import './ObjectTags.css'; import { TagsList } from 'src/components/Tags'; -import Icons from '../Icons'; -import AsyncSelect from '../Select/AsyncSelect'; import rison from 'rison'; import { cacheWrapper } from 'src/utils/cacheWrapper'; import { ClientErrorObject, getClientErrorObject } from 'src/utils/getClientErrorObject'; -import { SelectValue } from 'antd/lib/select'; -import { addTag, deleteTag, fetchSuggestions, fetchTags } from 'src/tags'; -import { StyledModal } from 'src/components/Modal'; +import { deleteTag, fetchTags } from 'src/tags'; interface ObjectTagsProps { objectType: string; objectId: number; includeTypes: boolean; editMode: boolean; - maxTags: number | null; + maxTags: number | undefined; onChange?: (tags: Tag[]) => void; } -interface SelectTagsProps { - onSelectTag: (tag: Tag) => void; -} - const StyledTagsDiv = styled.div` margin-left: ${({ theme }) => theme.gridUnit * 2}px; max-width: 100%; @@ -54,23 +46,6 @@ const StyledTagsDiv = styled.div` -webkit-flex-wrap: wrap; ` -const StyledTagsDropdown = styled.div` - margin-left: ${({ theme }) => theme.gridUnit * 2}px; - max-width: 100%; - display: -webkit-flex; - display: flex; - -webkit-flex-direction: row; - -webkit-flex-wrap: wrap; - backgroundColor: ${({ theme }) => theme.colors.grayscale.light4}; - ` - -const AddTags = styled.div` - max-width: 100%; - display: -webkit-flex; - display: flex; - -webkit-flex-direction: row; -`; - const localCache = new Map(); const cachedSupersetGet = cacheWrapper( @@ -121,7 +96,7 @@ export const loadTags = async ( const data: { label: string; value: string | number; - }[] = response.json.result.map(tagToSelectOption); + }[] = response.json.result.filter((item: Tag & { table_name: string },) => (item.type === 1)).map(tagToSelectOption); return { data, totalCount: response.json.count, @@ -133,80 +108,15 @@ export const loadTags = async ( }); }; -const SelectTags = ({ - onSelectTag, -}: SelectTagsProps) => { - - const [selectValue, setSelectValue] = useState(); - - - const onSelect = (value: { label: string; value: number }) => { - onSelectTag({name: value.label}); - setSelectValue(''); - }; - - const selectProps = { - allowClear: true, - allowNewOptions: true, - ariaLabel: 'Tags', - labelInValue: true, - options: loadTags, - pageSize: 10, - showSearch: true, - }; - return (); -}; - - -const EditTagsButton = ({ - onClick, - visible=true, -} : any ) => { - if(!visible) {return null} - const styling = { - verticalAlign: '-webkit-baseline-middle' - } - return ( -
- -
- ); -} - -const SaveButton = ({ - onClick, -} : any ) => { - const styling = { - verticalAlign: '-webkit-baseline-middle', - marginLeft: `inherit` - } - return ( -
- -
- ); -} - export const ObjectTags = ({ objectType, objectId, includeTypes, editMode=false, - maxTags=null, + maxTags=undefined, onChange, }: ObjectTagsProps) => { const [tags, setTags] = useState([]); - const [showTagsDropdown, setShowTagsDropdown] = useState(false); useEffect(() => { try { @@ -219,19 +129,6 @@ export const ObjectTags = ({ } }, [objectType, objectId, includeTypes]); - const theme = useTheme(); - - const TagsDiv = ({ - ...props - }: any ) => { - if(showTagsDropdown){ - return ( - - ); - } - return () - }; const onDelete = (tagIndex: number) => { deleteTag( @@ -243,32 +140,11 @@ export const ObjectTags = ({ onChange?.(tags); }; - const onAddition = (tag: Tag) => { - if (tags.some(t => t.name === tag.name)) { - return; - } - addTag( - {objectType, objectId, includeTypes}, - tag.name, - () => setTags([...tags, tag]), - () => {/* TODO: handle error */} - ); - onChange?.(tags); - }; - - const handleMouseOver = () => { - setShowTagsDropdown(true); - } - - const handleMouseLeave = () => { - setShowTagsDropdown(false); - } - return ( - + - + ); }; diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index 9e0852b660b71..aebf3a34fd428 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -26,8 +26,13 @@ import Icons from '../Icons'; export type TagsListProps = { tags: TagType[]; editable?: boolean; - onDelete: (index: number) => void; - maxTags: number | null; + /** + * OnDelete: + * Only applies when editable is true + * Callback for when a tag is deleted + */ + onDelete: ((index: number) => void) | undefined; + maxTags: number | undefined; }; const TagsDiv = styled.div` @@ -41,31 +46,40 @@ const TagsDiv = styled.div` const TagsList = ({ tags, editable=false, - onDelete=(index) => null, - maxTags=null + onDelete, + maxTags }: TagsListProps) => { + const [tempMaxTags, setTempMaxTags] = useState(maxTags); + const handleDelete = (index: number) => { - onDelete(index); + onDelete?.(index); } - const tagsIsLong: boolean | null = useMemo(() => (maxTags ? (tags.length > maxTags): null), [tags.length, maxTags]); + const expand = () => setTempMaxTags(undefined); + + const collapse = () => setTempMaxTags(maxTags); - const extraTags: number | null = useMemo(() => ((typeof maxTags === "number") ? ((tags.length - maxTags) + 1) : null), [tagsIsLong, tags.length, maxTags]) + const tagsIsLong: boolean | null = useMemo(() => (tempMaxTags ? (tags.length > tempMaxTags): null), [tags.length, tempMaxTags]); + + const extraTags: number | null = useMemo(() => ((typeof tempMaxTags === "number") ? ((tags.length - tempMaxTags) + 1) : null), [tagsIsLong, tags.length, tempMaxTags]) return ( - {(tagsIsLong === true && typeof maxTags === "number") ? ( + {(tagsIsLong === true && typeof tempMaxTags === "number") ? ( <> - {tags.slice(0,(maxTags-1)).map((tag: TagType, index) => ( + {tags.slice(0,(tempMaxTags-1)).map((tag: TagType, index) => ( ))} - {tags.length > maxTags ? () : (null)} + {tags.length > tempMaxTags ? () : (null)} ): ( - tags.map((tag: TagType, index) => ( - - )) + <> + {tags.map((tag: TagType, index) => ( + + ))} + {maxTags ? (tags.length > maxTags ? () : (null)) : (null)} + )} ); diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index 35992616bc48f..e3bc0c238070f 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -55,13 +55,9 @@ import { options as PeriodicRefreshOptions } from 'src/dashboard/components/Refr import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; import { DashboardEmbedModal } from '../DashboardEmbedControls'; -import { - addTag, - deleteTag, - fetchSuggestions, - fetchTags, - OBJECT_TYPES, -} from '../../../tags'; +import { OBJECT_TYPES } from 'src/tags'; + +const MAX_TAGS = 3; const propTypes = { addSuccessToast: PropTypes.func.isRequired, @@ -522,20 +518,21 @@ class Header extends React.PureComponent { }} titlePanelAdditionalItems={ [ - , - isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( - - ) : null + isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && + ] } rightPanelAdditionalItems={ @@ -683,7 +680,7 @@ class Header extends React.PureComponent { /> } showFaveStar={user?.userId && dashboardInfo?.id} - showTitlePanelItems={!editMode} + showTitlePanelItems /> {this.state.showingPropertiesModal && ( ([]); const [roles, setRoles] = useState([]); const saveLabel = onlyApply ? t('Apply') : t('Save'); + const [tags, setTags] = useState([]); + const [newTags, setNewTags] = useState([]); + const [oldTags, setOldTags] = useState([]); const handleErrorResponse = async (response: Response) => { const { error, statusText, message } = await getClientErrorObject(response); @@ -315,6 +322,32 @@ const PropertiesModal = ({ updateMetadata: false, }); + // update tags + newTags.map((tag: TagType) => { + // add new tags + addTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + includeTypes: false, + }, + tag.name, + () => {}, + () => {} + ); + }); + oldTags.map((tag: TagType) => { + // add new tags + deleteTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + }, + tag, + () => {}, + () => {} + ); + }); const moreOnSubmitProps: { roles?: Roles } = {}; const morePutProps: { roles?: number[] } = {}; if (isFeatureEnabled(FeatureFlag.DASHBOARD_RBAC)) { @@ -496,6 +529,36 @@ const PropertiesModal = ({ } }, [dashboardInfo, dashboardTitle, form]); + useEffect(() => { + try { + fetchTags( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + includeTypes: false}, + (tags: TagType[]) => setTags(tags), + () => {/*TODO: handle error*/}); + } catch(error: any) { + console.log(error) + } + }, [dashboardId]); + + const handleAddTag = (values: { label: string; value: number }[]) => { + values.map((value: { label: string; value: number }) => { + let tag = {name: value.label}; + if (tags.some(t => t.name === tag.name)) { + return; + } + setTags([...tags, tag]); + setNewTags([...newTags, tag]) + }); + } + + const handleDeleteTag = (tagIndex: number) => { + setOldTags([...oldTags, tags[tagIndex]]); + setTags([...tags.slice(0,tagIndex), ...tags.slice(tagIndex+1, tags.length)]); + } + return ( + + +

{t('Tags')}

+ +
+ + + + + +

+ {t( + 'A list of tags that have been applied to this chart.', + )} +

+ + + + +

diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index b953722fdb859..9a2e909ff7f11 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -39,8 +39,10 @@ import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsM import CertifiedBadge from 'src/components/CertifiedBadge'; import RowCountLabel from '../RowCountLabel'; import ObjectTags from 'src/components/ObjectTags'; -import {OBJECT_TYPES} from 'src/tags'; +import {fetchTags, OBJECT_TYPES} from 'src/tags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; +import { TagsList } from 'src/components/Tags'; +import { Tag } from 'src/types/TagType' const CHART_STATUS_MAP = { failed: 'danger', @@ -48,6 +50,8 @@ const CHART_STATUS_MAP = { success: 'success', }; +const MAX_TAGS = 3; + const propTypes = { actions: PropTypes.object.isRequired, canOverwrite: PropTypes.bool.isRequired, @@ -138,7 +142,7 @@ export const ExploreChartHeader = ({ if (dashboardId) { fetchChartDashboardData(); } - }, []); + }, [dashboardId]); const openPropertiesModal = () => { setIsPropertiesModalOpen(true); @@ -159,6 +163,19 @@ export const ExploreChartHeader = ({ ); const oldSliceName = slice?.slice_name; + + const [tags, setTags] = useState([]); + + useEffect(() => { + fetchTags({ + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false + }, + (tags) => setTags(tags), + () => {/** handle error */}) + }, [slice]); + return ( <> ) : null, isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( - (tag.type ? (tag.type === 1 || tag.type === "TagTypes.custom"): (true)))} + maxTags={MAX_TAGS} /> ) : null ] diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index fa2256cf0db90..0b4e74ba1b332 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -27,8 +27,10 @@ import { t, SupersetClient, styled } from '@superset-ui/core'; import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; -import ObjectTags, { loadTags } from 'src/components/ObjectTags'; -import { OBJECT_TYPES } from 'src/tags'; +import { loadTags } from 'src/components/ObjectTags'; +import { addTag, deleteTag, fetchTags, OBJECT_TYPES } from 'src/tags'; +import TagType from 'src/types/TagType'; +import { TagsList } from 'src/components/Tags'; export type PropertiesModalProps = { slice: Slice; @@ -65,7 +67,9 @@ function PropertiesModal({ null, ); - const [selectedTags, setSelectedTags] = useState(null,); + const [tags, setTags] = useState([]); + const [newTags, setNewTags] = useState([]); + const [oldTags, setOldTags] = useState([]); function showError({ error, statusText, message }: any) { let errorText = error || statusText || t('An error has occurred'); @@ -152,6 +156,33 @@ function PropertiesModal({ }[] ).map(o => o.value); } + // update tags + newTags.map((tag: TagType) => { + // add new tags + addTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false, + }, + tag.name, + () => {}, + () => {} + ); + }); + oldTags.map((tag: TagType) => { + // add new tags + deleteTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + }, + tag, + () => {}, + () => {} + ); + }); + try { const res = await SupersetClient.put({ endpoint: `/api/v1/chart/${slice.slice_id}`, @@ -162,6 +193,7 @@ function PropertiesModal({ const updatedChart = { ...payload, ...res.json.result, + tags: tags, id: slice.slice_id, }; onSave(updatedChart); @@ -186,6 +218,36 @@ function PropertiesModal({ setName(slice.slice_name || ''); }, [slice.slice_name]); + useEffect(() => { + try { + fetchTags( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false}, + (tags: TagType[]) => setTags(tags), + () => {/*TODO: handle error*/}); + } catch(error: any) { + console.log(error) + } + }, [slice.slice_id]); + + const handleAddTag = (values: { label: string; value: number }[]) => { + values.map((value: { label: string; value: number }) => { + let tag = {name: value.label}; + if (tags.some(t => t.name === tag.name)) { + return; + } + setTags([...tags, tag]); + setNewTags([...newTags, tag]) + }); + } + + const handleDeleteTag = (tagIndex: number) => { + setOldTags([...oldTags, tags[tagIndex]]); + setTags([...tags.slice(0,tagIndex), ...tags.slice(tagIndex+1, tags.length)]); + } + return ( @@ -334,11 +396,11 @@ function PropertiesModal({ 'A list of tags that have been applied to this chart.', )} - diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index dbbe1263ed420..90745e2785d65 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -375,7 +375,10 @@ function ChartList(props: ChartListProps) { }, }: any) => ( // Only show custom type tags - tag.type === 1)} /> + (tag.type ? (tag.type === 1 || tag.type === "TagTypes.custom"): (true)))} + maxTags={3} + /> ), Header: t('Tags'), accessor: 'tags', diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 7c92154e19d29..33ce2d5f387b6 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -184,6 +184,8 @@ function DashboardList(props: DashboardListProps) { url = '', certified_by = '', certification_details = '', + owners, + tags, } = json.result; return { ...dashboard, @@ -197,6 +199,8 @@ function DashboardList(props: DashboardListProps) { url, certified_by, certification_details, + owners, + tags, }; } return dashboard; @@ -349,7 +353,9 @@ function DashboardList(props: DashboardListProps) { }, }: any) => ( // Only show custom type tags - tag.type === 1)} /> + (tag.type === 'TagTypes.custom' || tag.type === 1))} + maxTags={3}/> ), Header: t('Tags'), accessor: 'tags', diff --git a/superset/charts/api.py b/superset/charts/api.py index c86eeb2312332..31699e6bcb87f 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -123,6 +123,9 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "owners.id", "owners.last_name", "owners.username", + "tags.id", + "tags.name", + "tags.type", "params", "slice_name", "viz_type", diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 2e091cd1b8e2c..f7ac543884b6b 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -148,6 +148,12 @@ } +class TagSchema(Schema): + id = fields.Int() + name = fields.String() + type = fields.String() + + class ChartEntityResponseSchema(Schema): """ Schema for a chart object diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index d91879f0d88b3..ed1ba7d53ea4d 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -145,6 +145,11 @@ class RolesSchema(Schema): id = fields.Int() name = fields.String() +class TagSchema(Schema): + id = fields.Int() + name = fields.String() + type = fields.String() + class DashboardGetResponseSchema(Schema): id = fields.Int() @@ -165,6 +170,7 @@ class DashboardGetResponseSchema(Schema): charts = fields.List(fields.String(description=charts_description)) owners = fields.List(fields.Nested(UserSchema)) roles = fields.List(fields.Nested(RolesSchema)) + tags = fields.Nested(TagSchema, many=True) changed_on_humanized = fields.String(data_key="changed_on_delta_humanized") is_managed_externally = fields.Boolean(allow_none=True, default=False) From 7b8b95d5a7840036440979fa833887e5017fefdd Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:17:22 -0400 Subject: [PATCH 17/76] added some component tests --- .../src/components/ObjectTags/index.tsx | 2 +- .../src/components/Tags/Tag.test.tsx | 35 +++++++++ superset-frontend/src/components/Tags/Tag.tsx | 6 +- .../src/components/Tags/TagsList.test.tsx | 77 +++++++++++++++++++ .../src/components/Tags/TagsList.tsx | 5 +- 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 superset-frontend/src/components/Tags/Tag.test.tsx create mode 100644 superset-frontend/src/components/Tags/TagsList.test.tsx diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 46ae699a558eb..29e6ff6877ca8 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -28,7 +28,7 @@ import { cacheWrapper } from 'src/utils/cacheWrapper'; import { ClientErrorObject, getClientErrorObject } from 'src/utils/getClientErrorObject'; import { deleteTag, fetchTags } from 'src/tags'; -interface ObjectTagsProps { +export interface ObjectTagsProps { objectType: string; objectId: number; includeTypes: boolean; diff --git a/superset-frontend/src/components/Tags/Tag.test.tsx b/superset-frontend/src/components/Tags/Tag.test.tsx new file mode 100644 index 0000000000000..7e02030ee753c --- /dev/null +++ b/superset-frontend/src/components/Tags/Tag.test.tsx @@ -0,0 +1,35 @@ +/** + * 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 { render } from 'spec/helpers/testing-library'; +import Tag from './Tag'; +import TagType from 'src/types/TagType'; + +const mockedProps: TagType = { + name: 'example-tag', + id: 1, + onDelete: null, + editable: false, + onClick: null +}; + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index 0123dd4da76a1..60a6cf2e61126 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -18,7 +18,7 @@ */ import { styled, SupersetTheme } from "@superset-ui/core"; -import Tag from "src/types/TagType"; +import TagType from "src/types/TagType"; import AntdTag from 'antd/lib/tag'; import { useMemo } from "react"; import { Tooltip } from 'src/components/Tooltip'; @@ -41,7 +41,7 @@ const Tag = ({ onDelete=null, editable=false, onClick=null -} : Tag) => { +} : TagType) => { const isLongTag = useMemo(() => name.length > 20, [name]); @@ -64,7 +64,7 @@ const Tag = ({ {isLongTag ? `${name.slice(0, 20)}...` : name} ) : ( - + {id ? ( {isLongTag ? `${name.slice(0, 20)}...` : name} ):( diff --git a/superset-frontend/src/components/Tags/TagsList.test.tsx b/superset-frontend/src/components/Tags/TagsList.test.tsx new file mode 100644 index 0000000000000..3b28fbadc8fd9 --- /dev/null +++ b/superset-frontend/src/components/Tags/TagsList.test.tsx @@ -0,0 +1,77 @@ +/** + * 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 { render, screen } from 'spec/helpers/testing-library'; +import TagsList, { TagsListProps } from './TagsList'; + +const testTags = [ + { + name: 'example-tag1', + id: 1, + }, + { + name: 'example-tag2', + id: 2, + }, + { + name: 'example-tag3', + id: 3, + }, + { + name: 'example-tag4', + id: 4, + }, + { + name: 'example-tag5', + id: 5, + }, +] + +const mockedProps: TagsListProps = { + tags: testTags, + onDelete: undefined, + maxTags: 5, +}; + +const findAllTags = () => + screen.getAllByRole("tag")! as HTMLElement[]; + +test('should render', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + // console.log(screen.getAllByRole("tag")); +}); + +test('should render 5 elements', () => { + render(); + const tagsListItems = findAllTags(); + expect(tagsListItems).toHaveLength(5); + expect(tagsListItems[0]).toHaveTextContent(testTags[0].name); + expect(tagsListItems[1]).toHaveTextContent(testTags[1].name); + expect(tagsListItems[2]).toHaveTextContent(testTags[2].name); + expect(tagsListItems[3]).toHaveTextContent(testTags[3].name); + expect(tagsListItems[4]).toHaveTextContent(testTags[4].name); +}); + +test('should render 3 elements when maxTags is set to 3', () => { + render(); + const tagsListItems = findAllTags(); + expect(tagsListItems).toHaveLength(3); + expect(tagsListItems[2]).toHaveTextContent('+3...'); +}); diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index aebf3a34fd428..65f422124625c 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -18,10 +18,9 @@ */ import React, { useMemo, useState } from 'react'; -import { styled, SupersetTheme, t } from '@superset-ui/core'; +import { styled } from '@superset-ui/core'; import Tag from './Tag'; import TagType from 'src/types/TagType'; -import Icons from '../Icons'; export type TagsListProps = { tags: TagType[]; @@ -65,7 +64,7 @@ const TagsList = ({ const extraTags: number | null = useMemo(() => ((typeof tempMaxTags === "number") ? ((tags.length - tempMaxTags) + 1) : null), [tagsIsLong, tags.length, tempMaxTags]) return ( - + {(tagsIsLong === true && typeof tempMaxTags === "number") ? ( <> {tags.slice(0,(tempMaxTags-1)).map((tag: TagType, index) => ( From 1c88ab540e39dd243576e2b7491253e356896612 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:26:23 -0400 Subject: [PATCH 18/76] removed dataset tagging for PR --- .../src/components/ListView/types.ts | 1 - .../views/CRUD/data/dataset/DatasetList.tsx | 22 --------------- superset/connectors/sqla/models.py | 13 --------- superset/datasets/api.py | 8 ++---- superset/datasets/filters.py | 12 --------- superset/models/tags.py | 10 +------ superset/views/tags.py | 27 ------------------- 7 files changed, 3 insertions(+), 90 deletions(-) diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts index 8b07f13fcd5c2..6cc7f17872634 100644 --- a/superset-frontend/src/components/ListView/types.ts +++ b/superset-frontend/src/components/ListView/types.ts @@ -117,5 +117,4 @@ export enum FilterOperator { dashboardTags = 'dashboard_tags', chartTags = 'chart_tags', savedQueryTags = 'saved_query_tags', - datasetTags = 'dataset_tags', } diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index 47ebe8a9b6459..c5b182871f174 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -50,7 +50,6 @@ import SubMenu, { } from 'src/views/components/SubMenu'; import { commonMenuData } from 'src/views/CRUD/data/common'; import Owner from 'src/types/Owner'; -import Tag from 'src/types/TagType'; import withToasts from 'src/components/MessageToasts/withToasts'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; @@ -58,7 +57,6 @@ import FacePile from 'src/components/FacePile'; import CertifiedBadge from 'src/components/CertifiedBadge'; import InfoTooltip from 'src/components/InfoTooltip'; import ImportModelsModal from 'src/components/ImportModal/index'; -import { TagsList } from 'src/components/Tags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip'; import { isUserAdmin } from 'src/dashboard/util/permissionUtils'; @@ -384,20 +382,6 @@ const DatasetList: FunctionComponent = ({ hidden: true, disableSortBy: true, }, - { - Cell: ({ - row: { - original: { tags = [] }, - }, - }: any) => ( - // Only show custom type tags - tag.type === 1)} /> - ), - Header: t('Tags'), - accessor: 'tags', - disableSortBy: true, - hidden: !isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM), - }, { Cell: ({ row: { original } }: any) => { // Verify owner or isAdmin @@ -553,12 +537,6 @@ const DatasetList: FunctionComponent = ({ { label: t('No'), value: false }, ], }, - { - Header: t('Tags'), - id: 'tags', - input: 'search', - operator: FilterOperator.datasetTags, - }, { Header: t('Search'), id: 'table_name', diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index f98ef51c530ec..68690f1db17a1 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -109,7 +109,6 @@ clone_model, QueryResult, ) -from superset.models.tags import DatasetUpdater, Tag from superset.sql_parse import ( extract_table_references, ParsedQuery, @@ -701,13 +700,6 @@ class SqlaTable(Model, BaseDatasource): # pylint: disable=too-many-public-metho database_id = Column(Integer, ForeignKey("dbs.id"), nullable=False) fetch_values_predicate = Column(Text) owners = relationship(owner_class, secondary=sqlatable_user, backref="tables") - tags = relationship( - Tag, - secondary="tagged_object", - primaryjoin="and_(SqlaTable.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " - "TaggedObject.object_type == 'dataset')", - ) database: Database = relationship( "Database", backref=backref("tables", cascade="all, delete-orphan"), @@ -2549,11 +2541,6 @@ def write_shadow_dataset( sa.event.listen(TableColumn, "after_update", SqlaTable.update_column) sa.event.listen(TableColumn, "after_delete", TableColumn.after_delete) -# events for updating tags -if is_feature_enabled("TAGGING_SYSTEM"): - sa.event.listen(SqlaTable, "after_insert", DatasetUpdater.after_insert) - sa.event.listen(SqlaTable, "after_update", DatasetUpdater.after_update) - sa.event.listen(SqlaTable, "after_delete", DatasetUpdater.after_delete) RLSFilterRoles = Table( "rls_filter_roles", diff --git a/superset/datasets/api.py b/superset/datasets/api.py index 7aef6ee2e5367..f6890655ed321 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -55,7 +55,7 @@ from superset.datasets.commands.samples import SamplesDatasetCommand from superset.datasets.commands.update import UpdateDatasetCommand from superset.datasets.dao import DatasetDAO -from superset.datasets.filters import DatasetCertifiedFilter, DatasetIsNullOrEmptyFilter, DatasetTagFilter +from superset.datasets.filters import DatasetCertifiedFilter, DatasetIsNullOrEmptyFilter from superset.datasets.schemas import ( DatasetPostSchema, DatasetPutSchema, @@ -115,9 +115,6 @@ class DatasetRestApi(BaseSupersetModelRestApi): "owners.username", "owners.first_name", "owners.last_name", - "tags.id", - "tags.name", - "tags.type", "schema", "sql", "table_name", @@ -218,9 +215,8 @@ class DatasetRestApi(BaseSupersetModelRestApi): search_filters = { "sql": [DatasetIsNullOrEmptyFilter], "id": [DatasetCertifiedFilter], - "tags": [DatasetTagFilter], } - search_columns = ["id", "database", "owners", "schema", "sql", "table_name", "tags"] + search_columns = ["id", "database", "owners", "schema", "sql", "table_name"] filter_rel_fields = {"database": [["id", DatabaseFilter, lambda: []]]} allowed_rel_fields = {"database", "owners"} allowed_distinct_fields = {"schema"} diff --git a/superset/datasets/filters.py b/superset/datasets/filters.py index c9e42dde3e6de..e72eb281818ae 100644 --- a/superset/datasets/filters.py +++ b/superset/datasets/filters.py @@ -20,7 +20,6 @@ from superset.connectors.sqla.models import SqlaTable from superset.views.base import BaseFilter -from superset.views.base_api import BaseTagFilter class DatasetIsNullOrEmptyFilter(BaseFilter): # pylint: disable=too-few-public-methods @@ -52,14 +51,3 @@ def apply(self, query: Query, value: bool) -> Query: ) ) return query - -class DatasetTagFilter( # pylint: disable=too-few-public-methods - BaseTagFilter -): - """ - Custom filter for the GET list that filters all dashboards that a user has favored - """ - - arg_name = "dataset_tags" - class_name = "dataset" - model = SqlaTable diff --git a/superset/models/tags.py b/superset/models/tags.py index 0c58b841742cf..43c1571632eff 100644 --- a/superset/models/tags.py +++ b/superset/models/tags.py @@ -65,7 +65,6 @@ class ObjectTypes(enum.Enum): query = 1 chart = 2 dashboard = 3 - dataset = 4 class Tag(Model, AuditMixinNullable): @@ -228,14 +227,7 @@ class QueryUpdater(ObjectUpdater): def get_owners_ids(cls, target: "Query") -> List[int]: return [target.user_id] -class DatasetUpdater(ObjectUpdater): - object_type = "dataset" - - @classmethod - def get_owners_ids(cls, target: "SqlaTable") -> List[int]: - return [owner.id for owner in target.owners] - - + class FavStarUpdater: @classmethod def after_insert( diff --git a/superset/views/tags.py b/superset/views/tags.py index 204d0b64e4a7e..cc7366505e10f 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -253,31 +253,4 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use for obj in saved_queries ) - # datasets - if not types or "dataset" in types: - datasets = ( - db.session.query(SqlaTable) - .join( - TaggedObject, - and_( - TaggedObject.object_id == SqlaTable.id, - TaggedObject.object_type == ObjectTypes.dataset, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.dataset.name, - "name": obj.label, - "url": obj.url(), - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in datasets - ) - return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) From bcfb95a8c2eff659839e155cdd8961fcdc14d31c Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 25 Jul 2022 16:49:22 -0400 Subject: [PATCH 19/76] fixed linting errors --- superset-frontend/package-lock.json | 4395 ++++++++++++++--- .../legacy-preset-chart-deckgl/package.json | 7 +- .../src/components/ObjectTags/ObjectTags.css | 10 +- .../src/components/ObjectTags/index.tsx | 58 +- .../src/components/Tags/Tag.test.tsx | 12 +- superset-frontend/src/components/Tags/Tag.tsx | 102 +- .../src/components/Tags/TagsList.test.tsx | 7 +- .../src/components/Tags/TagsList.tsx | 68 +- .../src/dashboard/components/Header/index.jsx | 20 +- .../components/PropertiesModal/index.tsx | 72 +- .../components/ExploreChartHeader/index.jsx | 76 +- .../components/PropertiesModal/index.tsx | 69 +- superset-frontend/src/types/TagType.ts | 2 +- .../src/views/CRUD/chart/ChartList.tsx | 8 +- .../views/CRUD/dashboard/DashboardList.tsx | 9 +- .../src/views/CRUD/tags/Tags.tsx | 4 +- superset/views/tags.py | 1 - 17 files changed, 3979 insertions(+), 941 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 18f0dc4a32c56..b5f82b2f3dcc5 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -3859,6 +3859,19 @@ "node": ">=0.1.95" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.3.1.tgz", @@ -3943,6 +3956,112 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/@cypress/request": { + "version": "2.88.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", + "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "dev": true, + "peer": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@cypress/request/node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "peer": true + }, + "node_modules/@cypress/request/node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "peer": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "peer": true + }, "node_modules/@data-ui/event-flow": { "version": "0.0.84", "resolved": "https://registry.npmjs.org/@data-ui/event-flow/-/event-flow-0.0.84.tgz", @@ -6372,7 +6491,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -6388,11 +6506,38 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@lerna/add": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-4.0.0.tgz", @@ -11802,7 +11947,7 @@ "version": "6.4.22", "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-6.4.22.tgz", "integrity": "sha512-vvQ0HgkIIVz+cmaCXIRor0UFZbGZqh4aV0ISSof60BjdW5ld+R+XCr/bdTU6Zg8b2fL9CXh7/LE6fImnIMpRIA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.12.10", "@babel/plugin-proposal-class-properties": "^7.12.1", @@ -11880,13 +12025,13 @@ "version": "14.17.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", - "dev": true + "devOptional": true }, "node_modules/@storybook/builder-webpack5/node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -11901,7 +12046,7 @@ "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", - "dev": true, + "devOptional": true, "dependencies": { "icss-utils": "^5.1.0", "loader-utils": "^2.0.0", @@ -11929,7 +12074,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -11944,7 +12089,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -11953,7 +12098,7 @@ "version": "27.2.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.3.tgz", "integrity": "sha512-ZwOvv4GCIPviL+Ie4pVguz4N5w/6IGbTaHBYOl3ZcsZZktaL7d8JOU0rmovoED7AJZKA8fvmLbBg8yg80u/tGA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -11967,7 +12112,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, + "devOptional": true, "dependencies": { "minimist": "^1.2.5" }, @@ -11982,7 +12127,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, + "devOptional": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -11996,7 +12141,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -12008,7 +12153,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -12023,7 +12168,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + "devOptional": true, "dependencies": { "randombytes": "^2.1.0" } @@ -12032,7 +12177,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -12041,7 +12186,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, + "devOptional": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -12061,7 +12206,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -12076,7 +12221,7 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", - "dev": true, + "devOptional": true, "dependencies": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -12093,7 +12238,7 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, + "devOptional": true, "dependencies": { "jest-worker": "^27.0.6", "p-limit": "^3.1.0", @@ -12128,7 +12273,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 8" } @@ -12137,7 +12282,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz", "integrity": "sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==", - "dev": true, + "devOptional": true, "dependencies": { "colorette": "^1.2.2", "mem": "^8.1.1", @@ -12161,13 +12306,13 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz", "integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==", - "dev": true + "devOptional": true }, "node_modules/@storybook/builder-webpack5/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/@storybook/channel-postmessage": { "version": "6.4.22", @@ -13639,7 +13784,7 @@ "version": "6.4.22", "resolved": "https://registry.npmjs.org/@storybook/manager-webpack5/-/manager-webpack5-6.4.22.tgz", "integrity": "sha512-BMkOMselT4jOn7EQGt748FurM5ewtDfZtOQPCVK8MZX+HYE2AgjNOzm562TYODIxk12Fkhgj3EIz7GGMe1U3RA==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/core": "^7.12.10", "@babel/plugin-transform-template-literals": "^7.12.1", @@ -13693,13 +13838,13 @@ "version": "14.17.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", - "dev": true + "devOptional": true }, "node_modules/@storybook/manager-webpack5/node_modules/css-loader": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", - "dev": true, + "devOptional": true, "dependencies": { "icss-utils": "^5.1.0", "loader-utils": "^2.0.0", @@ -13727,7 +13872,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -13743,7 +13888,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, + "devOptional": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -13758,7 +13903,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -13767,7 +13912,7 @@ "version": "27.2.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.3.tgz", "integrity": "sha512-ZwOvv4GCIPviL+Ie4pVguz4N5w/6IGbTaHBYOl3ZcsZZktaL7d8JOU0rmovoED7AJZKA8fvmLbBg8yg80u/tGA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -13781,7 +13926,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, + "devOptional": true, "dependencies": { "minimist": "^1.2.5" }, @@ -13796,7 +13941,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, + "devOptional": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -13810,7 +13955,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -13825,7 +13970,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -13837,7 +13982,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -13852,7 +13997,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -13867,7 +14012,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -13876,7 +14021,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -13885,7 +14030,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -13900,7 +14045,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + "devOptional": true, "dependencies": { "randombytes": "^2.1.0" } @@ -13909,7 +14054,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -13918,7 +14063,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, + "devOptional": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -13938,7 +14083,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -13953,7 +14098,7 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", - "dev": true, + "devOptional": true, "dependencies": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -13970,7 +14115,7 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, + "devOptional": true, "dependencies": { "jest-worker": "^27.0.6", "p-limit": "^3.1.0", @@ -14005,7 +14150,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 8" } @@ -14014,7 +14159,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz", "integrity": "sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==", - "dev": true, + "devOptional": true, "dependencies": { "colorette": "^1.2.2", "mem": "^8.1.1", @@ -14038,13 +14183,13 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz", "integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==", - "dev": true + "devOptional": true }, "node_modules/@storybook/manager-webpack5/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/@storybook/node-logger": { "version": "6.4.22", @@ -14704,6 +14849,19 @@ "node": ">=0.10.0" } }, + "node_modules/@storybook/react/node_modules/type-fest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.17.0.tgz", + "integrity": "sha512-U+g3/JVXnOki1kLSc+xZGPRll3Ah9u2VIG6Sn9iH9YX6UkPERmt6O/0fIyTgsd2/whV0+gAaHAg8fz6sG1QzMA==", + "optional": true, + "peer": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@storybook/react/node_modules/webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -15283,6 +15441,67 @@ "resolved": "plugins/legacy-plugin-chart-sunburst", "link": true }, + "node_modules/@superset-ui/legacy-plugin-chart-time-table": { + "version": "0.18.25", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-time-table/-/legacy-plugin-chart-time-table-0.18.25.tgz", + "integrity": "sha512-rRHtheYnBdumo5lRupXOdetB2/DIw9ayZ7KPWJP0JpdC9qR6i1OLKMvGNO67b+d6JIklNh538eHF3iP8E1Oo+Q==", + "peer": true, + "dependencies": { + "@data-ui/sparkline": "^0.0.84", + "@superset-ui/chart-controls": "0.18.25", + "@superset-ui/core": "0.18.25", + "@types/d3-scale": "^2.0.2", + "d3-scale": "^3.2.1", + "moment": "^2.26.0", + "mustache": "^4.0.1", + "prop-types": "^15.7.2", + "reactable-arc": "^0.15.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.3.1" + } + }, + "node_modules/@superset-ui/legacy-plugin-chart-time-table/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "peer": true, + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/@superset-ui/legacy-plugin-chart-time-table/node_modules/d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "peer": true, + "dependencies": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "node_modules/@superset-ui/legacy-plugin-chart-time-table/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "peer": true, + "dependencies": { + "d3-array": "2" + } + }, + "node_modules/@superset-ui/legacy-plugin-chart-time-table/node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "peer": true, + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/@superset-ui/legacy-plugin-chart-treemap": { "resolved": "plugins/legacy-plugin-chart-treemap", "link": true @@ -15602,7 +15821,6 @@ "version": "7.29.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz", "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -15621,7 +15839,6 @@ "version": "5.11.6", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", @@ -15642,7 +15859,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15655,7 +15871,6 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.0.tgz", "integrity": "sha512-90xKYJzskZ7q/AoSuWraQL4EGZlr75uZvDt3nrO4M+rugN02zjO45tmOBq/JBOgDiMIL1tkhHioKXjJsVaSINA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^7.27.1" @@ -15672,7 +15887,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz", "integrity": "sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "@types/react": ">=16.9.0", @@ -15699,7 +15913,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.0.tgz", "integrity": "sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -15715,7 +15928,6 @@ "version": "12.7.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.7.0.tgz", "integrity": "sha512-KzRM1KNDoW8pJ2HTenrUhTjV6wJMHvWAagDs8DDrYSWz6y4PN+K2jSvlm2bMHWNRk5LTJPo9jqIjNjJ3FlqXNw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -15745,11 +15957,38 @@ "node": ">=10.13.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true, + "peer": true + }, "node_modules/@types/aria-query": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", - "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", - "dev": true + "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==" }, "node_modules/@types/babel__core": { "version": "7.1.9", @@ -15912,7 +16151,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", - "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -15922,7 +16160,6 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", - "dev": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -15931,8 +16168,7 @@ "node_modules/@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, "node_modules/@types/fetch-mock": { "version": "7.3.5", @@ -15999,7 +16235,7 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*" } @@ -16018,7 +16254,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -16037,7 +16272,6 @@ "version": "26.0.23", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", - "dev": true, "dependencies": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" @@ -16088,6 +16322,15 @@ "@types/lodash": "*" } }, + "node_modules/@types/mapbox-gl": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.3.tgz", + "integrity": "sha512-XdveeJptNNZw7ZoeiAJ2/dupNtWaV6qpBG/SOFEpQNQAc+oiO6qUznX85n+W1XbLeD8SVRVfVORKuR+I4CHDZw==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/math-expression-evaluator": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/math-expression-evaluator/-/math-expression-evaluator-1.2.1.tgz", @@ -16204,7 +16447,6 @@ "version": "16.9.8", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", - "dev": true, "dependencies": { "@types/react": "*" } @@ -16246,6 +16488,18 @@ "@types/webpack": "^4" } }, + "node_modules/@types/react-map-gl": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/react-map-gl/-/react-map-gl-6.1.3.tgz", + "integrity": "sha512-22R7vE/XT/S+Nwu7245llMW+vO8ITPQJHaiRCO9RxmUUdV2kio7mQWaL8M5ko3XMt+7Hyz5ComNI4AopXZsxMA==", + "dev": true, + "dependencies": { + "@types/geojson": "*", + "@types/mapbox-gl": "*", + "@types/react": "*", + "@types/viewport-mercator-project": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.10", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.10.tgz", @@ -16328,7 +16582,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz", "integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==", - "dev": true, "dependencies": { "@types/react": "*" } @@ -16409,7 +16662,7 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", - "dev": true + "devOptional": true }, "node_modules/@types/rison": { "version": "0.0.6", @@ -16437,9 +16690,9 @@ } }, "node_modules/@types/sinonjs__fake-timers": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", - "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", "dev": true }, "node_modules/@types/sizzle": { @@ -16468,7 +16721,6 @@ "version": "5.9.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", - "dev": true, "dependencies": { "@types/jest": "*" } @@ -16481,8 +16733,7 @@ "node_modules/@types/tinycolor2": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz", - "integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==", - "dev": true + "integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==" }, "node_modules/@types/uglify-js": { "version": "3.0.4", @@ -16505,6 +16756,15 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" }, + "node_modules/@types/viewport-mercator-project": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@types/viewport-mercator-project/-/viewport-mercator-project-6.1.2.tgz", + "integrity": "sha512-21U4s61ZMUx95F2Uc9DpXBlcSKlrkeChM+hbGWmpz8HtL+zwl/zP5pJarJmxKv/764iR9xf5870jYZ8Mby5xuQ==", + "dev": true, + "dependencies": { + "gl-matrix": "^3.2.0" + } + }, "node_modules/@types/webpack": { "version": "4.41.31", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.31.tgz", @@ -16573,7 +16833,6 @@ "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", - "dev": true, "dependencies": { "@types/yargs-parser": "*" } @@ -16581,8 +16840,7 @@ "node_modules/@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, "node_modules/@types/yauzl": { "version": "2.9.2", @@ -17521,7 +17779,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -17531,14 +17788,12 @@ "node_modules/@webassemblyjs/helper-numbers/node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "node_modules/@webassemblyjs/helper-numbers/node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.9.0", @@ -17779,7 +18034,6 @@ "version": "1.7.6", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", - "dev": true, "peerDependencies": { "acorn": "^8" } @@ -18352,11 +18606,52 @@ "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=" }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "peer": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true, + "peer": true + }, "node_modules/are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -18367,6 +18662,13 @@ "readable-stream": "^2.0.6" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "peer": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -18379,7 +18681,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.10.2", "@babel/runtime-corejs3": "^7.10.2" @@ -18437,8 +18738,7 @@ "node_modules/array-filter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" }, "node_modules/array-flatten": { "version": "2.1.2", @@ -18675,6 +18975,13 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true, + "peer": true + }, "node_modules/async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -19559,7 +19866,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true + "devOptional": true }, "node_modules/batch-processor": { "version": "1.0.0", @@ -19701,6 +20008,13 @@ "node": ">= 6" } }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "peer": true + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -19779,7 +20093,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, + "devOptional": true, "dependencies": { "array-flatten": "^2.1.0", "deep-equal": "^1.0.1", @@ -20156,7 +20470,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true + "devOptional": true }, "node_modules/buffer-xor": { "version": "1.0.3", @@ -20462,6 +20776,58 @@ "node": ">=0.10.0" } }, + "node_modules/cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "peer": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -20689,7 +21055,6 @@ "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "dev": true, "dependencies": { "css-select": "~1.2.0", "dom-serializer": "~0.1.1", @@ -20706,7 +21071,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, "dependencies": { "domelementtype": "^1.3.0", "entities": "^1.1.1" @@ -21448,6 +21812,86 @@ "node": ">=8" } }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "peer": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -21831,6 +22275,16 @@ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -21934,7 +22388,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8" } @@ -22825,6 +23279,13 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "peer": true + }, "node_modules/cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", @@ -22890,7 +23351,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, "dependencies": { "inherits": "^2.0.4", "source-map": "^0.6.1", @@ -23741,7 +24201,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, "dependencies": { "boolbase": "~1.0.0", "css-what": "2.1", @@ -23787,7 +24246,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", - "dev": true, "engines": { "node": "*" } @@ -23795,20 +24253,17 @@ "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", - "dev": true + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" }, "node_modules/css/node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/css/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -23817,7 +24272,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, "dependencies": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0" @@ -23924,6 +24378,359 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, + "node_modules/cypress": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.3.1.tgz", + "integrity": "sha512-As9HrExjAgpgjCnbiQCuPdw5sWKx5HUJcK2EOKziu642akwufr/GUeqL5UnCPYXTyyibvEdWT/pSC2qnGW/e5w==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@cypress/request": "^2.88.10", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^14.14.31", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^5.1.0", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "eventemitter2": "^6.4.3", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.6", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.3.2", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cypress/node_modules/@types/node": { + "version": "14.18.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.22.tgz", + "integrity": "sha512-qzaYbXVzin6EPjghf/hTdIbnVW1ErMx8rPzwRNJhlbyJhu2SyqlvjGOY/tbUt6VFyzg56lROcOeSQRInpt63Yw==", + "dev": true, + "peer": true + }, + "node_modules/cypress/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cypress/node_modules/ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true, + "peer": true + }, + "node_modules/cypress/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cypress/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cypress/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/cypress/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/cypress/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "peer": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cypress/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "peer": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/cypress/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cypress/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + }, + "node_modules/cypress/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cypress/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/cypress/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cypress/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, "node_modules/d3": { "version": "3.5.17", "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", @@ -24391,7 +25198,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, + "devOptional": true, "dependencies": { "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", @@ -24453,7 +25260,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, + "devOptional": true, "dependencies": { "execa": "^5.0.0" }, @@ -24465,7 +25272,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "devOptional": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -24479,7 +25286,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "devOptional": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -24502,7 +25309,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -24514,7 +25321,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10.17.0" } @@ -24523,7 +25330,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" }, @@ -24535,7 +25342,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "devOptional": true, "dependencies": { "path-key": "^3.0.0" }, @@ -24547,7 +25354,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -24556,7 +25363,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -24568,7 +25375,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -24577,7 +25384,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -24588,6 +25395,29 @@ "node": ">= 8" } }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "peer": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-require-extensions/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -24610,7 +25440,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -24806,7 +25636,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, "engines": { "node": ">= 10.14.2" } @@ -24848,8 +25677,7 @@ "node_modules/discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" }, "node_modules/distributions": { "version": "1.1.0", @@ -24873,13 +25701,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true + "devOptional": true }, "node_modules/dns-packet": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dev": true, + "devOptional": true, "dependencies": { "ip": "^1.1.0", "safe-buffer": "^5.0.1" @@ -24889,7 +25717,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, + "devOptional": true, "dependencies": { "buffer-indexof": "^1.0.0" } @@ -24908,8 +25736,7 @@ "node_modules/dom-accessibility-api": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", - "dev": true + "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==" }, "node_modules/dom-align": { "version": "1.12.0", @@ -24936,7 +25763,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, "dependencies": { "domelementtype": "~1.1.1", "entities": "~1.1.1" @@ -24945,8 +25771,7 @@ "node_modules/dom-serializer/node_modules/domelementtype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" }, "node_modules/dom-to-image-more": { "version": "2.10.1", @@ -24970,8 +25795,7 @@ "node_modules/domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "node_modules/domexception": { "version": "4.0.0", @@ -24998,7 +25822,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, "dependencies": { "domelementtype": "1" } @@ -25012,7 +25835,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, "dependencies": { "dom-serializer": "0", "domelementtype": "1" @@ -25470,8 +26292,7 @@ "node_modules/entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "node_modules/env-ci": { "version": "5.4.1", @@ -25639,7 +26460,6 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz", "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==", - "dev": true, "dependencies": { "array.prototype.flat": "^1.2.1", "cheerio": "^1.0.0-rc.2", @@ -25755,8 +26575,7 @@ "node_modules/enzyme/node_modules/object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" }, "node_modules/err-code": { "version": "2.0.3", @@ -25874,8 +26693,7 @@ "node_modules/es-module-lexer": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==", - "dev": true + "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==" }, "node_modules/es-to-primitive": { "version": "1.2.1", @@ -25901,6 +26719,13 @@ "node": ">=0.4.0" } }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "peer": true + }, "node_modules/es6-shim": { "version": "0.35.6", "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", @@ -26648,7 +27473,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -27076,6 +27900,13 @@ "node": ">=6" } }, + "node_modules/eventemitter2": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.6.tgz", + "integrity": "sha512-OHqo4wbHX5VbvlbB6o6eDwhYmiTjrpWACjF8Pmof/GTD6rdBNdZFNck3xlhqOiQFGCOoq3uzHvA0cQpFHIGVAQ==", + "dev": true, + "peer": true + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -27122,6 +27953,29 @@ "node": ">=6" } }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "peer": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -27658,7 +28512,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, + "devOptional": true, "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -27920,7 +28774,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/filter-console/-/filter-console-0.1.1.tgz", "integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==", - "dev": true, "engines": { "node": ">=8" } @@ -28929,6 +29782,16 @@ "node": ">=0.10.0" } }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "peer": true, + "dependencies": { + "async": "^3.2.0" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -29378,6 +30241,32 @@ "node": ">= 0.4" } }, + "node_modules/global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "peer": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -29516,7 +30405,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true + "devOptional": true }, "node_modules/handlebars": { "version": "4.7.7", @@ -29763,6 +30652,36 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "peer": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/hast-to-hyperscript": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", @@ -30076,7 +30995,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -30088,7 +31007,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.1.0.tgz", "integrity": "sha512-iqiG3dTZmy+uUaTmHarTL+3/A2VW9ox/9uasKEZC+R/wAtUrTcRlXPSaPqsnWPfIu8wqn09jQNwMRqzL54jSYA==", - "dev": true, "dependencies": { "array-filter": "^1.0.0" } @@ -30287,7 +31205,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, "dependencies": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", @@ -30301,7 +31218,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -30321,13 +31237,13 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true + "devOptional": true }, "node_modules/http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, + "devOptional": true, "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -30342,13 +31258,13 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", - "dev": true + "devOptional": true }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, + "devOptional": true, "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -30399,7 +31315,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/http-proxy": "^1.17.5", "http-proxy": "^1.18.1", @@ -30415,7 +31331,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -30427,7 +31343,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -30439,7 +31355,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -30448,7 +31364,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -30460,7 +31376,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, + "devOptional": true, "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -30473,7 +31389,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -30575,7 +31491,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, + "devOptional": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -31026,7 +31942,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", - "dev": true, + "devOptional": true, "dependencies": { "default-gateway": "^6.0.0", "ipaddr.js": "^1.9.1", @@ -31044,7 +31960,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.10" } @@ -31105,7 +32021,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 10" } @@ -31381,6 +32297,23 @@ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", "integrity": "sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==" }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "peer": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -31394,7 +32327,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", - "dev": true, + "devOptional": true, "dependencies": { "ip-regex": "^4.0.0" }, @@ -31406,7 +32339,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -31505,7 +32438,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -31514,7 +32447,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -31642,8 +32575,7 @@ "node_modules/is-subset": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" }, "node_modules/is-symbol": { "version": "1.0.4", @@ -31799,9 +32731,22 @@ "dev": true }, "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "peer": true, + "dependencies": { + "append-transform": "^2.0.0" + }, "engines": { "node": ">=8" } @@ -31830,6 +32775,111 @@ "semver": "bin/semver.js" } }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "peer": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "peer": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -32326,7 +33376,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", @@ -33548,7 +34597,6 @@ "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, "engines": { "node": ">= 10.14.2" } @@ -35952,6 +37000,124 @@ "node": ">=6" } }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "peer": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true, + "peer": true + }, + "node_modules/listr2/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "peer": true + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -35971,7 +37137,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, "engines": { "node": ">=6.11.5" } @@ -36059,8 +37224,7 @@ "node_modules/lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" }, "node_modules/lodash.flatten": { "version": "4.4.0", @@ -36071,8 +37235,7 @@ "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" }, "node_modules/lodash.flow": { "version": "3.5.0", @@ -36142,6 +37305,13 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "peer": true + }, "node_modules/lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", @@ -36209,6 +37379,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", @@ -36274,7 +37463,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", - "dev": true, "bin": { "lz-string": "bin/bin.js" } @@ -36307,6 +37495,13 @@ "node": ">=6" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "peer": true + }, "node_modules/make-fetch-happen": { "version": "8.0.14", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz", @@ -36426,7 +37621,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, + "devOptional": true, "dependencies": { "p-defer": "^1.0.0" }, @@ -36470,6 +37665,74 @@ "node": ">=0.10.0" } }, + "node_modules/mapbox-gl": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.2.tgz", + "integrity": "sha512-CPjtWygL+f7naL+sGHoC2JQR0DG7u+9ik6WdkjjVmz2uy0kBC2l+aKfdi3ZzUR7VKSQJ6Mc/CeCN+6iVNah+ww==", + "peer": true, + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.0", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "minimist": "^1.2.5", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/mapbox-gl/node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "peer": true, + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/mapbox-gl/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mapbox-gl/node_modules/supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "peer": true, + "dependencies": { + "kdbush": "^3.0.0" + } + }, "node_modules/markdown-escapes": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", @@ -36699,7 +37962,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", - "dev": true, + "devOptional": true, "dependencies": { "map-age-cleaner": "^0.1.3", "mimic-fn": "^3.1.0" @@ -36753,7 +38016,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -37188,9 +38451,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minimist-options": { "version": "4.1.0", @@ -37496,8 +38759,7 @@ "node_modules/moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", - "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", - "dev": true + "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==" }, "node_modules/morgan": { "version": "1.10.0", @@ -37562,7 +38824,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, + "devOptional": true, "dependencies": { "dns-packet": "^1.3.1", "thunky": "^1.0.2" @@ -37575,7 +38837,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true + "devOptional": true }, "node_modules/multimatch": { "version": "5.0.0", @@ -37720,7 +38982,6 @@ "version": "2.18.0", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.18.0.tgz", "integrity": "sha512-/zQOMCeJcioI0xJtd5RpBiWw2WP7wLe6vq8/3Yu0rEwgus/G/+pViX80oA87JdVgjRt2895mZSv2VfZmy4W1uw==", - "dev": true, "dependencies": { "commander": "^2.19.0", "moo": "^0.4.3", @@ -37896,7 +39157,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 6.0.0" } @@ -38161,6 +39422,19 @@ "node": ">= 8" } }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "peer": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/node-releases": { "version": "1.1.75", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", @@ -38636,7 +39910,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, "dependencies": { "boolbase": "~1.0.0" } @@ -38668,6 +39941,204 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "peer": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "peer": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "peer": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -38729,7 +40200,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", - "dev": true, "engines": { "node": ">= 0.4" } @@ -38855,7 +40325,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true + "devOptional": true }, "node_modules/omit.js": { "version": "2.0.2", @@ -39054,6 +40524,13 @@ "os-tmpdir": "^1.0.0" } }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "peer": true + }, "node_modules/overlayscrollbars": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz", @@ -39082,7 +40559,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true, + "devOptional": true, "engines": { "node": ">=4" } @@ -39227,7 +40704,7 @@ "version": "4.6.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/retry": "^0.12.0", "retry": "^0.13.1" @@ -39271,6 +40748,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pad-component": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pad-component/-/pad-component-0.0.1.tgz", @@ -39437,7 +40930,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -39476,7 +40968,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true + "devOptional": true }, "node_modules/path-dirname": { "version": "1.0.2", @@ -39747,7 +41239,7 @@ "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, + "devOptional": true, "dependencies": { "async": "^2.6.2", "debug": "^3.1.1", @@ -39761,7 +41253,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, + "devOptional": true, "dependencies": { "lodash": "^4.17.14" } @@ -39771,7 +41263,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.1" } @@ -39780,7 +41272,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/posix-character-classes": { "version": "0.1.1", @@ -39794,7 +41286,6 @@ "version": "8.3.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", - "dev": true, "dependencies": { "colorette": "^1.2.2", "nanoid": "^3.1.23", @@ -39921,7 +41412,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, + "devOptional": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -39933,7 +41424,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, + "devOptional": true, "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -39950,7 +41441,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, + "devOptional": true, "dependencies": { "postcss-selector-parser": "^6.0.4" }, @@ -39965,7 +41456,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, + "devOptional": true, "dependencies": { "icss-utils": "^5.0.0" }, @@ -39997,7 +41488,6 @@ "version": "3.1.25", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", - "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -40086,7 +41576,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -40101,7 +41590,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, "engines": { "node": ">=8" } @@ -40109,8 +41597,7 @@ "node_modules/pretty-format/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/pretty-hrtime": { "version": "1.0.3", @@ -40164,6 +41651,19 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "peer": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -40356,6 +41856,13 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "peer": true + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -40703,8 +42210,7 @@ "node_modules/railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" }, "node_modules/ramda": { "version": "0.26.1", @@ -40715,7 +42221,6 @@ "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, "dependencies": { "discontinuous-range": "1.0.0", "ret": "~0.1.10" @@ -42458,7 +43963,7 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.9.0.tgz", "integrity": "sha512-R62stB73qZyhrJo7wmCW9jgl/07ai+YzvouvCXIJLBkRlRqLx4j9RqcLEAfNfU3OxTGucqR2Whmn3/Aad6L3hQ==", - "dev": true, + "devOptional": true, "dependencies": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", @@ -42473,7 +43978,7 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true + "devOptional": true }, "node_modules/react-textarea-autosize": { "version": "8.3.3", @@ -42632,6 +44137,15 @@ "react": "* || ^0.14.0" } }, + "node_modules/reactable-arc": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/reactable-arc/-/reactable-arc-0.15.0.tgz", + "integrity": "sha512-XH1mryI/xvbYb3lCVOU3rx/KRacDE0PDa45KazL/PPTM0AgPZ/awVmCAxRi179BpjbStk7cgCyFjI2oYJ28E8A==", + "peer": true, + "peerDependencies": { + "react": "* || ^0.14.0" + } + }, "node_modules/reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", @@ -42995,7 +44509,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -43264,6 +44777,19 @@ "node": ">= 0.10" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "peer": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/remark-external-links": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", @@ -43894,6 +45420,16 @@ "node": ">= 6" } }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "peer": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, "node_modules/request-promise-core": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", @@ -43967,7 +45503,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "devOptional": true }, "node_modules/reselect": { "version": "4.0.0", @@ -44064,7 +45600,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 4" } @@ -44078,6 +45614,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true, + "peer": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -44110,7 +45653,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "dev": true, "dependencies": { "lodash.flattendeep": "^4.4.0", "nearley": "^2.7.10" @@ -44227,7 +45769,7 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", - "dev": true, + "devOptional": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -44267,13 +45809,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true + "devOptional": true }, "node_modules/selfsigned": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", - "dev": true, + "devOptional": true, "dependencies": { "node-forge": "^0.10.0" } @@ -44392,7 +45934,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, + "devOptional": true, "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -44468,7 +46010,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true + "devOptional": true }, "node_modules/sha.js": { "version": "2.4.11", @@ -44871,7 +46413,7 @@ "version": "0.3.21", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "dev": true, + "devOptional": true, "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^3.4.0", @@ -45039,7 +46581,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -45086,6 +46627,66 @@ "trim": "0.0.1" } }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "peer": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawn-wrap/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/spawn-wrap/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -45118,7 +46719,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -45134,7 +46735,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -45149,7 +46750,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.1" } @@ -45158,13 +46759,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/spdy-transport/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -45179,7 +46780,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.1" } @@ -45188,7 +46789,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/specificity": { "version": "0.4.1", @@ -45481,7 +47082,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.6" } @@ -45746,7 +47347,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz", "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==", - "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.13.0", @@ -46884,6 +48484,13 @@ "node": ">=10" } }, + "node_modules/throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", + "dev": true, + "peer": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -46902,7 +48509,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true + "devOptional": true }, "node_modules/timed-out": { "version": "4.0.1", @@ -47338,6 +48945,60 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -47473,7 +49134,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -47867,6 +49527,16 @@ "node": ">=0.10.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/unzip-response": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", @@ -48122,6 +49792,13 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "peer": true + }, "node_modules/v8-to-istanbul": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", @@ -48455,7 +50132,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, + "devOptional": true, "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -48487,7 +50164,6 @@ "version": "5.52.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz", "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==", - "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.0", "@types/estree": "^0.0.50", @@ -48811,7 +50487,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.2.0.tgz", "integrity": "sha512-iBaDkHBLfW3cEITeJWNkjZBrm+b5A3YLg8XVdNOdjUNABdXJwcsJv4dzKSnVf1q4Ch489+6epWVW6OcOyVfG7w==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-html-community": "^0.0.8", "bonjour": "^3.5.0", @@ -48858,7 +50534,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=12" }, @@ -48870,7 +50546,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, + "devOptional": true, "dependencies": { "globby": "^11.0.1", "graceful-fs": "^4.2.4", @@ -48892,7 +50568,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, + "devOptional": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -48904,7 +50580,7 @@ "version": "8.2.1", "resolved": "https://registry.npmjs.org/open/-/open-8.2.1.tgz", "integrity": "sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ==", - "dev": true, + "devOptional": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -48921,7 +50597,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -48930,7 +50606,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -48945,7 +50621,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.1.0.tgz", "integrity": "sha512-oT660AR1gOnU/NTdUQi3EiGR0iXG7CFxmKsj3ylWCBA2khJ8LFHK+sKv3BZEsC11gl1eChsltRhzUq7nWj7XIQ==", - "dev": true, + "devOptional": true, "dependencies": { "colorette": "^1.2.2", "memfs": "^3.2.2", @@ -48968,7 +50644,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10.0.0" }, @@ -49091,7 +50767,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz", "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==", - "dev": true, "engines": { "node": ">=10.13.0" } @@ -49121,7 +50796,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -49130,26 +50804,22 @@ "node_modules/webpack/node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "node_modules/webpack/node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "node_modules/webpack/node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "node_modules/webpack/node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -49161,7 +50831,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -49170,7 +50839,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -49178,14 +50846,12 @@ "node_modules/webpack/node_modules/@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "node_modules/webpack/node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -49201,7 +50867,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -49214,7 +50879,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -49226,7 +50890,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -49240,7 +50903,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -49250,7 +50912,6 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -49263,7 +50924,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -49272,7 +50932,6 @@ "version": "27.2.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.0.tgz", "integrity": "sha512-laB0ZVIBz+voh/QQy9dmUuuDsadixeerrKqyVpgPz+CCWiOYjOBabUXHIXZhsdvkWbLqSHbgkAHWl5cg24Q6RA==", - "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -49286,7 +50945,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -49301,7 +50959,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, "dependencies": { "randombytes": "^2.1.0" } @@ -49310,7 +50967,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -49319,7 +50975,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -49334,7 +50989,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -49343,7 +50997,6 @@ "version": "5.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", - "dev": true, "dependencies": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -49360,7 +51013,6 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, "dependencies": { "jest-worker": "^27.0.6", "p-limit": "^3.1.0", @@ -49395,7 +51047,6 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, "engines": { "node": ">= 8" } @@ -49404,7 +51055,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", - "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -49417,7 +51067,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, + "devOptional": true, "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -49431,7 +51081,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8.0" } @@ -51175,6 +52825,16 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "dev": true }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -52289,6 +53949,9 @@ "urijs": "^1.19.8", "xss": "^1.0.10" }, + "devDependencies": { + "@types/react-map-gl": "^6.1.3" + }, "peerDependencies": { "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", @@ -53353,7 +55016,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "4.0.0", @@ -55199,6 +56863,16 @@ "minimist": "^1.2.0" } }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, "@ctrl/tinycolor": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.3.1.tgz", @@ -55239,6 +56913,104 @@ } } }, + "@cypress/request": { + "version": "2.88.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", + "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "dev": true, + "peer": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + } + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "peer": true + }, + "jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "peer": true + } + } + }, + "@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "peer": true, + "requires": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "peer": true + } + } + }, "@data-ui/event-flow": { "version": "0.0.84", "resolved": "https://registry.npmjs.org/@data-ui/event-flow/-/event-flow-0.0.84.tgz", @@ -56025,7 +57797,8 @@ "@deck.gl/google-maps": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/@deck.gl/google-maps/-/google-maps-8.5.2.tgz", - "integrity": "sha512-Dk3ozenBWgt9nFSYOT4N82urNW/JhiMszfFq6zLt3jUp0N7EJ9d2XO81hclM59BhjIdGWb6drTe96NvtbabVLQ==" + "integrity": "sha512-Dk3ozenBWgt9nFSYOT4N82urNW/JhiMszfFq6zLt3jUp0N7EJ9d2XO81hclM59BhjIdGWb6drTe96NvtbabVLQ==", + "requires": {} }, "@deck.gl/json": { "version": "8.5.2", @@ -56050,7 +57823,8 @@ "@deck.gl/mapbox": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-8.5.2.tgz", - "integrity": "sha512-nMpzfdPFBVthT+EMgIcKo4YO6bZCqADQtqnxIFtfofZIiKS6R5OSuJ3sXPSNZ9ReCJGzdmndEz7/Qtm9Sia/bA==" + "integrity": "sha512-nMpzfdPFBVthT+EMgIcKo4YO6bZCqADQtqnxIFtfofZIiKS6R5OSuJ3sXPSNZ9ReCJGzdmndEz7/Qtm9Sia/bA==", + "requires": {} }, "@deck.gl/mesh-layers": { "version": "8.5.2", @@ -56496,7 +58270,8 @@ "@encodable/registry": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@encodable/registry/-/registry-1.0.3.tgz", - "integrity": "sha512-YH2nSBZJKgbH/9MkQXzAEE9UwTaVcWiKgVFyEU/gvrfmNWqecYaHMTyObo+ADSTGF4kk0cZZkr7VqZgIQbvrUw==" + "integrity": "sha512-YH2nSBZJKgbH/9MkQXzAEE9UwTaVcWiKgVFyEU/gvrfmNWqecYaHMTyObo+ADSTGF4kk0cZZkr7VqZgIQbvrUw==", + "requires": {} }, "@eslint/eslintrc": { "version": "0.4.3", @@ -57242,7 +59017,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -57255,13 +59029,37 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, "requires": { "@types/istanbul-lib-report": "*" } } } }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@lerna/add": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-4.0.0.tgz", @@ -59721,7 +61519,8 @@ "@mapbox/mapbox-gl-supported": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", - "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==" + "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==", + "requires": {} }, "@mapbox/martini": { "version": "0.2.0", @@ -60037,7 +61836,8 @@ "version": "1.6.22", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", - "dev": true + "dev": true, + "requires": {} }, "@mdx-js/util": { "version": "1.6.22", @@ -60345,7 +62145,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true + "dev": true, + "requires": {} }, "@octokit/plugin-rest-endpoint-methods": { "version": "5.13.0", @@ -60454,7 +62255,8 @@ "@react-icons/all-files": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@react-icons/all-files/-/all-files-4.1.0.tgz", - "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==" + "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==", + "requires": {} }, "@samverschueren/stream-to-observable": { "version": "0.3.1", @@ -61356,7 +63158,8 @@ "webpack-filter-warnings-plugin": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", - "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==" + "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", + "requires": {} }, "webpack-sources": { "version": "1.4.3", @@ -61373,7 +63176,7 @@ "version": "6.4.22", "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-6.4.22.tgz", "integrity": "sha512-vvQ0HgkIIVz+cmaCXIRor0UFZbGZqh4aV0ISSof60BjdW5ld+R+XCr/bdTU6Zg8b2fL9CXh7/LE6fImnIMpRIA==", - "dev": true, + "devOptional": true, "requires": { "@babel/core": "^7.12.10", "@babel/plugin-proposal-class-properties": "^7.12.1", @@ -61438,13 +63241,13 @@ "version": "14.17.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", - "dev": true + "devOptional": true }, "babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, + "devOptional": true, "requires": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -61455,7 +63258,7 @@ "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", - "dev": true, + "devOptional": true, "requires": { "icss-utils": "^5.1.0", "loader-utils": "^2.0.0", @@ -61473,7 +63276,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -61484,13 +63287,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "devOptional": true }, "jest-worker": { "version": "27.2.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.3.tgz", "integrity": "sha512-ZwOvv4GCIPviL+Ie4pVguz4N5w/6IGbTaHBYOl3ZcsZZktaL7d8JOU0rmovoED7AJZKA8fvmLbBg8yg80u/tGA==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -61501,7 +63304,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, + "devOptional": true, "requires": { "minimist": "^1.2.5" } @@ -61510,7 +63313,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, + "devOptional": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -61521,7 +63324,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -61530,7 +63333,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "requires": { "yocto-queue": "^0.1.0" } @@ -61539,7 +63342,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + "devOptional": true, "requires": { "randombytes": "^2.1.0" } @@ -61548,13 +63351,13 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "devOptional": true }, "style-loader": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, + "devOptional": true, "requires": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -61564,7 +63367,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "requires": { "has-flag": "^4.0.0" } @@ -61573,7 +63376,7 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", - "dev": true, + "devOptional": true, "requires": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -61584,7 +63387,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true + "devOptional": true } } }, @@ -61592,7 +63395,7 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, + "devOptional": true, "requires": { "jest-worker": "^27.0.6", "p-limit": "^3.1.0", @@ -61606,7 +63409,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz", "integrity": "sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==", - "dev": true, + "devOptional": true, "requires": { "colorette": "^1.2.2", "mem": "^8.1.1", @@ -61620,13 +63423,13 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz", "integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==", - "dev": true + "devOptional": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -62265,7 +64068,8 @@ "ws": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", - "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==" + "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", + "requires": {} } } }, @@ -62663,7 +64467,7 @@ "version": "6.4.22", "resolved": "https://registry.npmjs.org/@storybook/manager-webpack5/-/manager-webpack5-6.4.22.tgz", "integrity": "sha512-BMkOMselT4jOn7EQGt748FurM5ewtDfZtOQPCVK8MZX+HYE2AgjNOzm562TYODIxk12Fkhgj3EIz7GGMe1U3RA==", - "dev": true, + "devOptional": true, "requires": { "@babel/core": "^7.12.10", "@babel/plugin-transform-template-literals": "^7.12.1", @@ -62704,13 +64508,13 @@ "version": "14.17.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", - "dev": true + "devOptional": true }, "css-loader": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", - "dev": true, + "devOptional": true, "requires": { "icss-utils": "^5.1.0", "loader-utils": "^2.0.0", @@ -62728,7 +64532,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -62738,7 +64542,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, + "devOptional": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -62750,13 +64554,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "devOptional": true }, "jest-worker": { "version": "27.2.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.3.tgz", "integrity": "sha512-ZwOvv4GCIPviL+Ie4pVguz4N5w/6IGbTaHBYOl3ZcsZZktaL7d8JOU0rmovoED7AJZKA8fvmLbBg8yg80u/tGA==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -62767,7 +64571,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, + "devOptional": true, "requires": { "minimist": "^1.2.5" } @@ -62776,7 +64580,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, + "devOptional": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -62787,7 +64591,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "requires": { "p-locate": "^5.0.0" } @@ -62796,7 +64600,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -62805,7 +64609,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "requires": { "yocto-queue": "^0.1.0" } @@ -62814,7 +64618,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "requires": { "p-limit": "^3.0.2" } @@ -62823,19 +64627,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "devOptional": true }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "devOptional": true }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -62844,7 +64648,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + "devOptional": true, "requires": { "randombytes": "^2.1.0" } @@ -62853,13 +64657,13 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "devOptional": true }, "style-loader": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, + "devOptional": true, "requires": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -62869,7 +64673,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "requires": { "has-flag": "^4.0.0" } @@ -62878,7 +64682,7 @@ "version": "5.9.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", - "dev": true, + "devOptional": true, "requires": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -62889,7 +64693,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true + "devOptional": true } } }, @@ -62897,7 +64701,7 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, + "devOptional": true, "requires": { "jest-worker": "^27.0.6", "p-limit": "^3.1.0", @@ -62911,7 +64715,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz", "integrity": "sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==", - "dev": true, + "devOptional": true, "requires": { "colorette": "^1.2.2", "mem": "^8.1.1", @@ -62925,13 +64729,13 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz", "integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==", - "dev": true + "devOptional": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -63227,6 +65031,13 @@ } } }, + "type-fest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.17.0.tgz", + "integrity": "sha512-U+g3/JVXnOki1kLSc+xZGPRll3Ah9u2VIG6Sn9iH9YX6UkPERmt6O/0fIyTgsd2/whV0+gAaHAg8fz6sG1QzMA==", + "optional": true, + "peer": true + }, "webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -63857,7 +65668,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-mock-console/-/jest-mock-console-1.2.3.tgz", "integrity": "sha512-q4jfuHW3V3tYzwtKTF6nxjRNriUC2/D2SVfxW88lNeG1qO1mVarBUqgOAvZjTEmxuTsjzGlHQsDIgvlOZaLccg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -64267,77 +66079,133 @@ } } }, - "@superset-ui/legacy-plugin-chart-map-box": { - "version": "file:plugins/legacy-plugin-chart-map-box", - "requires": { - "prop-types": "^15.6.2", - "react-map-gl": "^4.0.10", - "supercluster": "^4.1.1", - "viewport-mercator-project": "^6.1.1" - } - }, - "@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "file:plugins/legacy-plugin-chart-paired-t-test", - "requires": { - "distributions": "^1.0.0", - "prop-types": "^15.6.2", - "reactable": "^1.1.0" - } - }, - "@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "file:plugins/legacy-plugin-chart-parallel-coordinates", - "requires": { - "d3": "^3.5.17", - "prop-types": "^15.7.2" - } - }, - "@superset-ui/legacy-plugin-chart-partition": { - "version": "file:plugins/legacy-plugin-chart-partition", - "requires": { - "d3": "^3.5.17", - "d3-hierarchy": "^1.1.8", - "prop-types": "^15.6.2" - } - }, - "@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "file:plugins/legacy-plugin-chart-pivot-table", - "requires": { - "d3": "^3.5.17", - "datatables.net-bs": "^1.11.3", - "prop-types": "^15.6.2" - } - }, - "@superset-ui/legacy-plugin-chart-rose": { - "version": "file:plugins/legacy-plugin-chart-rose", - "requires": { - "d3": "^3.5.17", - "nvd3-fork": "^2.0.5", - "prop-types": "^15.6.2" - } - }, - "@superset-ui/legacy-plugin-chart-sankey": { - "version": "file:plugins/legacy-plugin-chart-sankey", - "requires": { - "d3": "^3.5.17", - "d3-sankey": "^0.4.2", - "prop-types": "^15.6.2" - } - }, - "@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "file:plugins/legacy-plugin-chart-sankey-loop", - "requires": { - "d3-sankey-diagram": "^0.7.3", - "d3-selection": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "@superset-ui/legacy-plugin-chart-sunburst": { - "version": "file:plugins/legacy-plugin-chart-sunburst", - "requires": { - "d3": "^3.5.17", - "prop-types": "^15.6.2" - } - }, + "@superset-ui/legacy-plugin-chart-map-box": { + "version": "file:plugins/legacy-plugin-chart-map-box", + "requires": { + "prop-types": "^15.6.2", + "react-map-gl": "^4.0.10", + "supercluster": "^4.1.1", + "viewport-mercator-project": "^6.1.1" + } + }, + "@superset-ui/legacy-plugin-chart-paired-t-test": { + "version": "file:plugins/legacy-plugin-chart-paired-t-test", + "requires": { + "distributions": "^1.0.0", + "prop-types": "^15.6.2", + "reactable": "^1.1.0" + } + }, + "@superset-ui/legacy-plugin-chart-parallel-coordinates": { + "version": "file:plugins/legacy-plugin-chart-parallel-coordinates", + "requires": { + "d3": "^3.5.17", + "prop-types": "^15.7.2" + } + }, + "@superset-ui/legacy-plugin-chart-partition": { + "version": "file:plugins/legacy-plugin-chart-partition", + "requires": { + "d3": "^3.5.17", + "d3-hierarchy": "^1.1.8", + "prop-types": "^15.6.2" + } + }, + "@superset-ui/legacy-plugin-chart-pivot-table": { + "version": "file:plugins/legacy-plugin-chart-pivot-table", + "requires": { + "d3": "^3.5.17", + "datatables.net-bs": "^1.11.3", + "prop-types": "^15.6.2" + } + }, + "@superset-ui/legacy-plugin-chart-rose": { + "version": "file:plugins/legacy-plugin-chart-rose", + "requires": { + "d3": "^3.5.17", + "nvd3-fork": "^2.0.5", + "prop-types": "^15.6.2" + } + }, + "@superset-ui/legacy-plugin-chart-sankey": { + "version": "file:plugins/legacy-plugin-chart-sankey", + "requires": { + "d3": "^3.5.17", + "d3-sankey": "^0.4.2", + "prop-types": "^15.6.2" + } + }, + "@superset-ui/legacy-plugin-chart-sankey-loop": { + "version": "file:plugins/legacy-plugin-chart-sankey-loop", + "requires": { + "d3-sankey-diagram": "^0.7.3", + "d3-selection": "^1.4.0", + "prop-types": "^15.6.2" + } + }, + "@superset-ui/legacy-plugin-chart-sunburst": { + "version": "file:plugins/legacy-plugin-chart-sunburst", + "requires": { + "d3": "^3.5.17", + "prop-types": "^15.6.2" + } + }, + "@superset-ui/legacy-plugin-chart-time-table": { + "version": "0.18.25", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-time-table/-/legacy-plugin-chart-time-table-0.18.25.tgz", + "integrity": "sha512-rRHtheYnBdumo5lRupXOdetB2/DIw9ayZ7KPWJP0JpdC9qR6i1OLKMvGNO67b+d6JIklNh538eHF3iP8E1Oo+Q==", + "peer": true, + "requires": { + "@data-ui/sparkline": "^0.0.84", + "@superset-ui/chart-controls": "0.18.25", + "@superset-ui/core": "0.18.25", + "@types/d3-scale": "^2.0.2", + "d3-scale": "^3.2.1", + "moment": "^2.26.0", + "mustache": "^4.0.1", + "prop-types": "^15.7.2", + "reactable-arc": "^0.15.0" + }, + "dependencies": { + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "peer": true, + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "peer": true, + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "peer": true, + "requires": { + "d3-array": "2" + } + }, + "mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "peer": true + } + } + }, "@superset-ui/legacy-plugin-chart-treemap": { "version": "file:plugins/legacy-plugin-chart-treemap", "requires": { @@ -64372,6 +66240,7 @@ "@mapbox/geojson-extent": "^1.0.1", "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", + "@types/react-map-gl": "*", "bootstrap-slider": "^10.0.0", "d3-array": "^1.2.4", "d3-color": "^1.4.1", @@ -64467,7 +66336,8 @@ "just-handlebars-helpers": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/just-handlebars-helpers/-/just-handlebars-helpers-1.0.19.tgz", - "integrity": "sha512-E+0eUn5xKfBAoU6mF3QbGZ939PZDw7RYI6AMTpRQtesRH2lZXjXaOqHzJ2nbHnDVmxNQM453sXFnMpd/uaLkKg==" + "integrity": "sha512-E+0eUn5xKfBAoU6mF3QbGZ939PZDw7RYI6AMTpRQtesRH2lZXjXaOqHzJ2nbHnDVmxNQM453sXFnMpd/uaLkKg==", + "requires": {} } } }, @@ -64821,7 +66691,6 @@ "version": "7.29.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz", "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==", - "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -64837,7 +66706,6 @@ "version": "5.11.6", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", - "dev": true, "requires": { "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", @@ -64853,7 +66721,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -64865,7 +66732,6 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.0.tgz", "integrity": "sha512-90xKYJzskZ7q/AoSuWraQL4EGZlr75uZvDt3nrO4M+rugN02zjO45tmOBq/JBOgDiMIL1tkhHioKXjJsVaSINA==", - "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^7.27.1" @@ -64875,7 +66741,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz", "integrity": "sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg==", - "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@types/react": ">=16.9.0", @@ -64889,7 +66754,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.0.tgz", "integrity": "sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==", - "dev": true, "requires": { "@babel/runtime": "^7.12.5" } @@ -64900,7 +66764,6 @@ "version": "12.7.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.7.0.tgz", "integrity": "sha512-KzRM1KNDoW8pJ2HTenrUhTjV6wJMHvWAagDs8DDrYSWz6y4PN+K2jSvlm2bMHWNRk5LTJPo9jqIjNjJ3FlqXNw==", - "dev": true, "requires": { "@babel/runtime": "^7.12.5" } @@ -64917,11 +66780,38 @@ "integrity": "sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "peer": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "peer": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "peer": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true, + "peer": true + }, "@types/aria-query": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", - "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", - "dev": true + "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==" }, "@types/babel__core": { "version": "7.1.9", @@ -65084,7 +66974,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", - "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -65094,7 +66983,6 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", - "dev": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -65103,8 +66991,7 @@ "@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, "@types/fetch-mock": { "version": "7.3.5", @@ -65171,7 +67058,7 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*" } @@ -65190,7 +67077,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } @@ -65209,7 +67095,6 @@ "version": "26.0.23", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", - "dev": true, "requires": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" @@ -65260,6 +67145,15 @@ "@types/lodash": "*" } }, + "@types/mapbox-gl": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.3.tgz", + "integrity": "sha512-XdveeJptNNZw7ZoeiAJ2/dupNtWaV6qpBG/SOFEpQNQAc+oiO6qUznX85n+W1XbLeD8SVRVfVORKuR+I4CHDZw==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/math-expression-evaluator": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/math-expression-evaluator/-/math-expression-evaluator-1.2.1.tgz", @@ -65375,7 +67269,6 @@ "version": "16.9.8", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", - "dev": true, "requires": { "@types/react": "*" } @@ -65417,6 +67310,18 @@ "@types/webpack": "^4" } }, + "@types/react-map-gl": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/react-map-gl/-/react-map-gl-6.1.3.tgz", + "integrity": "sha512-22R7vE/XT/S+Nwu7245llMW+vO8ITPQJHaiRCO9RxmUUdV2kio7mQWaL8M5ko3XMt+7Hyz5ComNI4AopXZsxMA==", + "dev": true, + "requires": { + "@types/geojson": "*", + "@types/mapbox-gl": "*", + "@types/react": "*", + "@types/viewport-mercator-project": "*" + } + }, "@types/react-redux": { "version": "7.1.10", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.10.tgz", @@ -65499,7 +67404,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz", "integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==", - "dev": true, "requires": { "@types/react": "*" } @@ -65582,7 +67486,7 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", - "dev": true + "devOptional": true }, "@types/rison": { "version": "0.0.6", @@ -65610,9 +67514,9 @@ } }, "@types/sinonjs__fake-timers": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", - "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", "dev": true }, "@types/sizzle": { @@ -65641,7 +67545,6 @@ "version": "5.9.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", - "dev": true, "requires": { "@types/jest": "*" } @@ -65654,8 +67557,7 @@ "@types/tinycolor2": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz", - "integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==", - "dev": true + "integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==" }, "@types/uglify-js": { "version": "3.0.4", @@ -65677,6 +67579,15 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" }, + "@types/viewport-mercator-project": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@types/viewport-mercator-project/-/viewport-mercator-project-6.1.2.tgz", + "integrity": "sha512-21U4s61ZMUx95F2Uc9DpXBlcSKlrkeChM+hbGWmpz8HtL+zwl/zP5pJarJmxKv/764iR9xf5870jYZ8Mby5xuQ==", + "dev": true, + "requires": { + "gl-matrix": "^3.2.0" + } + }, "@types/webpack": { "version": "4.41.31", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.31.tgz", @@ -65737,7 +67648,6 @@ "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", - "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -65745,8 +67655,7 @@ "@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, "@types/yauzl": { "version": "2.9.2", @@ -66122,7 +68031,8 @@ "@vx/clip-path": { "version": "0.0.140", "resolved": "https://registry.npmjs.org/@vx/clip-path/-/clip-path-0.0.140.tgz", - "integrity": "sha1-smI9AE3Vw8imr+jQYN5Z31FHLZQ=" + "integrity": "sha1-smI9AE3Vw8imr+jQYN5Z31FHLZQ=", + "requires": {} }, "@vx/curve": { "version": "0.0.165", @@ -66316,7 +68226,8 @@ "@vx/clip-path": { "version": "0.0.165", "resolved": "https://registry.npmjs.org/@vx/clip-path/-/clip-path-0.0.165.tgz", - "integrity": "sha512-mBCbgguLMVyGvar5FbxqyyY4NQFlnXoSLF0TrhgWYkF/FCXdE1CzBC+Y4iXIJOY0ZTtluqL9XrNdIDpx49AmuA==" + "integrity": "sha512-mBCbgguLMVyGvar5FbxqyyY4NQFlnXoSLF0TrhgWYkF/FCXdE1CzBC+Y4iXIJOY0ZTtluqL9XrNdIDpx49AmuA==", + "requires": {} }, "@vx/group": { "version": "0.0.170", @@ -66428,7 +68339,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -66438,14 +68348,12 @@ "@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" } } }, @@ -66564,7 +68472,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.4.tgz", "integrity": "sha512-cs3XLy+UcxiP6bj0A6u7MLLuwdXJ1c3Dtc0RkKg+wiI1g/Ti1om8+/2hc2A2B60NbBNAbMgyBMHvyymWm/j4wQ==", - "dev": true + "dev": true, + "requires": {} }, "@webpack-cli/info": { "version": "1.3.0", @@ -66579,7 +68488,8 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.2.tgz", "integrity": "sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw==", - "dev": true + "dev": true, + "requires": {} }, "@xtuc/ieee754": { "version": "1.2.0", @@ -66657,13 +68567,14 @@ "version": "1.7.6", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz", "integrity": "sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==", - "dev": true + "requires": {} }, "acorn-jsx": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -66809,7 +68720,8 @@ "ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "requires": {} }, "ajv-formats": { "version": "2.1.1", @@ -66843,7 +68755,8 @@ "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} }, "alphanum-sort": { "version": "1.0.2", @@ -67110,11 +69023,35 @@ "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=" }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "peer": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "peer": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true, + "peer": true + }, "are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -67125,6 +69062,13 @@ "readable-stream": "^2.0.6" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "peer": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -67137,7 +69081,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, "requires": { "@babel/runtime": "^7.10.2", "@babel/runtime-corejs3": "^7.10.2" @@ -67177,8 +69120,7 @@ "array-filter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" }, "array-flatten": { "version": "2.1.2", @@ -67370,6 +69312,13 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true, + "peer": true + }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -67700,7 +69649,8 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/babel-plugin-jsx-remove-data-test-id/-/babel-plugin-jsx-remove-data-test-id-2.1.3.tgz", "integrity": "sha512-FTpcmzr3avLVStllCT4BceTTZNEb+1mJVtLpsicvXDqjojEkyrga1GGOxWj768Ra3tev6KWgNOhZ/Lrucb+MuQ==", - "dev": true + "dev": true, + "requires": {} }, "babel-plugin-lodash": { "version": "3.3.4", @@ -67757,7 +69707,8 @@ "babel-plugin-named-asset-import": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", - "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==" + "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==", + "requires": {} }, "babel-plugin-polyfill-corejs2": { "version": "0.2.2", @@ -68045,7 +69996,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true + "devOptional": true }, "batch-processor": { "version": "1.0.0", @@ -68151,6 +70102,13 @@ } } }, + "blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "peer": true + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -68216,7 +70174,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, + "devOptional": true, "requires": { "array-flatten": "^2.1.0", "deep-equal": "^1.0.1", @@ -68519,7 +70477,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true + "devOptional": true }, "buffer-xor": { "version": "1.0.3", @@ -68748,6 +70706,45 @@ "unset-value": "^1.0.0" } }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true, + "peer": true + }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "peer": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true + } + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -68924,7 +70921,6 @@ "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "dev": true, "requires": { "css-select": "~1.2.0", "dom-serializer": "~0.1.1", @@ -68938,7 +70934,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, "requires": { "domelementtype": "^1.3.0", "entities": "^1.1.1" @@ -69490,6 +71485,67 @@ } } }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "peer": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "peer": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -69795,6 +71851,13 @@ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" }, + "common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "peer": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -69888,7 +71951,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true + "devOptional": true }, "console-browserify": { "version": "1.2.0", @@ -70597,6 +72660,13 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "peer": true + }, "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", @@ -70646,7 +72716,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, "requires": { "inherits": "^2.0.4", "source-map": "^0.6.1", @@ -70656,20 +72725,17 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, "requires": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0" @@ -70955,25 +73021,29 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-duplicates": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-empty": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-overridden": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", - "dev": true + "dev": true, + "requires": {} }, "postcss-merge-longhand": { "version": "5.0.2", @@ -71046,7 +73116,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-normalize-display-values": { "version": "5.0.1", @@ -71232,7 +73303,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, "requires": { "boolbase": "~1.0.0", "css-what": "2.1", @@ -71273,14 +73343,12 @@ "css-what": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", - "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", - "dev": true + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==" }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", - "dev": true + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" }, "csscolorparser": { "version": "1.0.3", @@ -71301,7 +73369,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", - "dev": true + "dev": true, + "requires": {} }, "csso": { "version": "4.2.0", @@ -71363,6 +73432,266 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, + "cypress": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.3.1.tgz", + "integrity": "sha512-As9HrExjAgpgjCnbiQCuPdw5sWKx5HUJcK2EOKziu642akwufr/GUeqL5UnCPYXTyyibvEdWT/pSC2qnGW/e5w==", + "dev": true, + "peer": true, + "requires": { + "@cypress/request": "^2.88.10", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^14.14.31", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^5.1.0", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "eventemitter2": "^6.4.3", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.6", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.3.2", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "@types/node": { + "version": "14.18.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.22.tgz", + "integrity": "sha512-qzaYbXVzin6EPjghf/hTdIbnVW1ErMx8rPzwRNJhlbyJhu2SyqlvjGOY/tbUt6VFyzg56lROcOeSQRInpt63Yw==", + "dev": true, + "peer": true + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "peer": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true, + "peer": true + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "peer": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "peer": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "peer": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "peer": true, + "requires": { + "pump": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true + }, + "is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "peer": true, + "requires": { + "ci-info": "^3.2.0" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "peer": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "peer": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + } + } + }, "d3": { "version": "3.5.17", "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", @@ -71524,7 +73853,8 @@ "d3-svg-legend": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/d3-svg-legend/-/d3-svg-legend-1.13.0.tgz", - "integrity": "sha1-YhdHjJrdnWLLMzYX4ZYTEaQaTbM=" + "integrity": "sha1-YhdHjJrdnWLLMzYX4ZYTEaQaTbM=", + "requires": {} }, "d3-time": { "version": "1.0.10", @@ -71771,7 +74101,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, + "devOptional": true, "requires": { "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", @@ -71826,7 +74156,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, + "devOptional": true, "requires": { "execa": "^5.0.0" }, @@ -71835,7 +74165,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "devOptional": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -71846,7 +74176,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "devOptional": true, "requires": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -71863,25 +74193,25 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true + "devOptional": true }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true + "devOptional": true }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true + "devOptional": true }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "devOptional": true, "requires": { "path-key": "^3.0.0" } @@ -71890,13 +74220,13 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "devOptional": true }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "requires": { "shebang-regex": "^3.0.0" } @@ -71905,19 +74235,38 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "devOptional": true }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "requires": { "isexe": "^2.0.0" } } } }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "peer": true, + "requires": { + "strip-bom": "^4.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "peer": true + } + } + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -71939,7 +74288,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true + "devOptional": true }, "define-properties": { "version": "1.1.3", @@ -72089,8 +74438,7 @@ "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" }, "diffie-hellman": { "version": "5.0.3", @@ -72127,8 +74475,7 @@ "discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" }, "distributions": { "version": "1.1.0", @@ -72152,13 +74499,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true + "devOptional": true }, "dns-packet": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dev": true, + "devOptional": true, "requires": { "ip": "^1.1.0", "safe-buffer": "^5.0.1" @@ -72168,7 +74515,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, + "devOptional": true, "requires": { "buffer-indexof": "^1.0.0" } @@ -72184,8 +74531,7 @@ "dom-accessibility-api": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", - "dev": true + "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==" }, "dom-align": { "version": "1.12.0", @@ -72212,7 +74558,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, "requires": { "domelementtype": "~1.1.1", "entities": "~1.1.1" @@ -72221,8 +74566,7 @@ "domelementtype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } }, @@ -72244,8 +74588,7 @@ "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "domexception": { "version": "4.0.0", @@ -72268,7 +74611,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, "requires": { "domelementtype": "1" } @@ -72282,7 +74624,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" @@ -72691,8 +75032,7 @@ "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "env-ci": { "version": "5.4.1", @@ -72808,7 +75148,6 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz", "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==", - "dev": true, "requires": { "array.prototype.flat": "^1.2.1", "cheerio": "^1.0.0-rc.2", @@ -72836,8 +75175,7 @@ "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" } } }, @@ -73012,8 +75350,7 @@ "es-module-lexer": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==", - "dev": true + "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==" }, "es-to-primitive": { "version": "1.2.1", @@ -73030,6 +75367,13 @@ "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==" }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "peer": true + }, "es6-shim": { "version": "0.35.6", "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", @@ -73349,7 +75693,8 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", - "dev": true + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -73650,7 +75995,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-testing-library": { "version": "3.10.1", @@ -73738,13 +76084,13 @@ "version": "file:tools/eslint-plugin-theme-colors" }, "eslint-plugin-translation-vars": { - "version": "file:tools/eslint-plugin-translation-vars" + "version": "file:tools/eslint-plugin-translation-vars", + "requires": {} }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -73872,6 +76218,13 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true }, + "eventemitter2": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.6.tgz", + "integrity": "sha512-OHqo4wbHX5VbvlbB6o6eDwhYmiTjrpWACjF8Pmof/GTD6rdBNdZFNck3xlhqOiQFGCOoq3uzHvA0cQpFHIGVAQ==", + "dev": true, + "peer": true + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -73912,6 +76265,25 @@ "strip-eof": "^1.0.0" } }, + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "peer": true, + "requires": { + "pify": "^2.2.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "peer": true + } + } + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -74343,7 +76715,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, + "devOptional": true, "requires": { "websocket-driver": ">=0.5.1" } @@ -74551,8 +76923,7 @@ "filter-console": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/filter-console/-/filter-console-0.1.1.tgz", - "integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==", - "dev": true + "integrity": "sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==" }, "filter-obj": { "version": "1.1.0", @@ -75319,6 +77690,16 @@ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, + "getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "peer": true, + "requires": { + "async": "^3.2.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -75664,6 +78045,25 @@ "is-symbol": "^1.0.1" } }, + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "peer": true, + "requires": { + "ini": "2.0.0" + }, + "dependencies": { + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "peer": true + } + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -75771,7 +78171,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true + "devOptional": true }, "handlebars": { "version": "4.7.7", @@ -75946,6 +78346,26 @@ "minimalistic-assert": "^1.0.1" } }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "peer": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "peer": true + } + } + }, "hast-to-hyperscript": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", @@ -76172,7 +78592,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -76184,7 +78604,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.1.0.tgz", "integrity": "sha512-iqiG3dTZmy+uUaTmHarTL+3/A2VW9ox/9uasKEZC+R/wAtUrTcRlXPSaPqsnWPfIu8wqn09jQNwMRqzL54jSYA==", - "dev": true, "requires": { "array-filter": "^1.0.0" } @@ -76344,7 +78763,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, "requires": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", @@ -76358,7 +78776,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -76377,13 +78794,13 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true + "devOptional": true }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, + "devOptional": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -76395,13 +78812,13 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", - "dev": true + "devOptional": true }, "http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, + "devOptional": true, "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -76440,7 +78857,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", - "dev": true, + "devOptional": true, "requires": { "@types/http-proxy": "^1.17.5", "http-proxy": "^1.18.1", @@ -76453,7 +78870,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "requires": { "fill-range": "^7.0.1" } @@ -76462,7 +78879,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -76471,19 +78888,19 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "devOptional": true }, "is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true + "devOptional": true }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, + "devOptional": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -76493,7 +78910,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "requires": { "is-number": "^7.0.0" } @@ -76575,7 +78992,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "devOptional": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -76907,7 +79325,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", - "dev": true, + "devOptional": true, "requires": { "default-gateway": "^6.0.0", "ipaddr.js": "^1.9.1", @@ -76919,7 +79337,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true + "devOptional": true } } }, @@ -76970,7 +79388,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "dev": true + "devOptional": true }, "is-absolute-url": { "version": "3.0.3", @@ -77168,6 +79586,17 @@ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", "integrity": "sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==" }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "peer": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -77178,7 +79607,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", - "dev": true, + "devOptional": true, "requires": { "ip-regex": "^4.0.0" }, @@ -77187,7 +79616,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true + "devOptional": true } } }, @@ -77257,13 +79686,13 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true + "devOptional": true }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "devOptional": true }, "is-plain-obj": { "version": "1.1.0", @@ -77355,8 +79784,7 @@ "is-subset": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" }, "is-symbol": { "version": "1.0.4", @@ -77473,9 +79901,19 @@ "dev": true }, "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==" + }, + "istanbul-lib-hook": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==" + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "peer": true, + "requires": { + "append-transform": "^2.0.0" + } }, "istanbul-lib-instrument": { "version": "4.0.3", @@ -77497,6 +79935,86 @@ } } }, + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "peer": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "peer": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "peer": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -77937,7 +80455,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", @@ -78903,8 +81420,7 @@ "jest-get-type": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" }, "jest-haste-map": { "version": "26.6.2", @@ -79198,7 +81714,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -79633,7 +82150,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.2.0.tgz", "integrity": "sha512-lc3wwXOEyNa4ZpcgJtUG3mmKMAq5FAsKYiZph0p/+PAJrAPuX4JCIfJMdJ/urRsLBG51fwm/wlVPNbR6s2nzNw==", - "dev": true + "dev": true, + "requires": {} }, "jest-worker": { "version": "26.6.2", @@ -79922,7 +82440,8 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "4.0.0", @@ -80662,6 +83181,97 @@ } } }, + "listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "peer": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true, + "peer": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "peer": true + }, + "rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "peer": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -80677,8 +83287,7 @@ "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" }, "loader-utils": { "version": "1.4.0", @@ -80757,8 +83366,7 @@ "lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" }, "lodash.flatten": { "version": "4.4.0", @@ -80769,8 +83377,7 @@ "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" }, "lodash.flow": { "version": "3.5.0", @@ -80840,6 +83447,13 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "peer": true + }, "lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", @@ -80901,6 +83515,19 @@ "is-unicode-supported": "^0.1.0" } }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "peer": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + } + }, "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", @@ -80954,8 +83581,7 @@ "lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", - "dev": true + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" }, "magic-string": { "version": "0.22.5", @@ -80981,6 +83607,13 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "peer": true + }, "make-fetch-happen": { "version": "8.0.14", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz", @@ -81081,7 +83714,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, + "devOptional": true, "requires": { "p-defer": "^1.0.0" } @@ -81110,6 +83743,64 @@ "object-visit": "^1.0.0" } }, + "mapbox-gl": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.2.tgz", + "integrity": "sha512-CPjtWygL+f7naL+sGHoC2JQR0DG7u+9ik6WdkjjVmz2uy0kBC2l+aKfdi3ZzUR7VKSQJ6Mc/CeCN+6iVNah+ww==", + "peer": true, + "requires": { + "@mapbox/geojson-rewind": "^0.5.0", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "minimist": "^1.2.5", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "dependencies": { + "@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "peer": true, + "requires": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true + }, + "supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "peer": true, + "requires": { + "kdbush": "^3.0.0" + } + } + } + }, "markdown-escapes": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", @@ -81118,7 +83809,8 @@ "markdown-to-jsx": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.3.tgz", - "integrity": "sha512-jtQ6VyT7rMT5tPV0g2EJakEnXLiPksnvlYtwQsVVZ611JsWGN8bQ1tVSDX4s6JllfEH6wmsYxNjTUAMrPmNA8w==" + "integrity": "sha512-jtQ6VyT7rMT5tPV0g2EJakEnXLiPksnvlYtwQsVVZ611JsWGN8bQ1tVSDX4s6JllfEH6wmsYxNjTUAMrPmNA8w==", + "requires": {} }, "match-sorter": { "version": "6.3.0", @@ -81289,7 +83981,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", - "dev": true, + "devOptional": true, "requires": { "map-age-cleaner": "^0.1.3", "mimic-fn": "^3.1.0" @@ -81299,7 +83991,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", - "dev": true + "devOptional": true } } }, @@ -81660,9 +84352,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minimist-options": { "version": "4.1.0", @@ -81907,8 +84599,7 @@ "moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", - "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", - "dev": true + "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==" }, "morgan": { "version": "1.10.0", @@ -81968,7 +84659,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, + "devOptional": true, "requires": { "dns-packet": "^1.3.1", "thunky": "^1.0.2" @@ -81978,7 +84669,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true + "devOptional": true }, "multimatch": { "version": "5.0.0", @@ -82100,7 +84791,6 @@ "version": "2.18.0", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.18.0.tgz", "integrity": "sha512-/zQOMCeJcioI0xJtd5RpBiWw2WP7wLe6vq8/3Yu0rEwgus/G/+pViX80oA87JdVgjRt2895mZSv2VfZmy4W1uw==", - "dev": true, "requires": { "commander": "^2.19.0", "moo": "^0.4.3", @@ -82258,7 +84948,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true + "devOptional": true }, "node-gyp": { "version": "5.1.1", @@ -82470,6 +85160,16 @@ } } }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "peer": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, "node-releases": { "version": "1.1.75", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", @@ -82849,7 +85549,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, "requires": { "boolbase": "~1.0.0" } @@ -82867,7 +85566,8 @@ "nvd3-fork": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/nvd3-fork/-/nvd3-fork-2.0.5.tgz", - "integrity": "sha512-Sq3q2rvR/9FJ35LVmqdQJAnfmD15BaIHSBg5wZZL/WLcq/nthff8ukabwFdbW0zeE1c/yPq+DKl6MxnUTR45DA==" + "integrity": "sha512-Sq3q2rvR/9FJ35LVmqdQJAnfmD15BaIHSBg5wZZL/WLcq/nthff8ukabwFdbW0zeE1c/yPq+DKl6MxnUTR45DA==", + "requires": {} }, "nwsapi": { "version": "2.2.0", @@ -82875,6 +85575,155 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "peer": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "peer": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "peer": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "peer": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true + } + } + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -82922,8 +85771,7 @@ "object-is": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", - "dev": true + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" }, "object-keys": { "version": "1.1.1", @@ -83013,7 +85861,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true + "devOptional": true }, "omit.js": { "version": "2.0.2", @@ -83170,6 +86018,13 @@ "os-tmpdir": "^1.0.0" } }, + "ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "peer": true + }, "overlayscrollbars": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz", @@ -83194,7 +86049,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true + "devOptional": true }, "p-each-series": { "version": "2.2.0", @@ -83287,7 +86142,7 @@ "version": "4.6.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", - "dev": true, + "devOptional": true, "requires": { "@types/retry": "^0.12.0", "retry": "^0.13.1" @@ -83316,6 +86171,19 @@ "p-reduce": "^2.0.0" } }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "peer": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "pad-component": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pad-component/-/pad-component-0.0.1.tgz", @@ -83462,7 +86330,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, "requires": { "@types/node": "*" } @@ -83497,7 +86364,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true + "devOptional": true }, "path-dirname": { "version": "1.0.2", @@ -83701,7 +86568,7 @@ "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, + "devOptional": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", @@ -83712,7 +86579,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, + "devOptional": true, "requires": { "lodash": "^4.17.14" } @@ -83721,7 +86588,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, + "devOptional": true, "requires": { "ms": "^2.1.1" } @@ -83730,7 +86597,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true } } }, @@ -83743,7 +86610,6 @@ "version": "8.3.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", - "dev": true, "requires": { "colorette": "^1.2.2", "nanoid": "^3.1.23", @@ -83753,8 +86619,7 @@ "nanoid": { "version": "3.1.25", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", - "dev": true + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==" } } }, @@ -83836,13 +86701,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "devOptional": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, + "devOptional": true, "requires": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -83853,7 +86719,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, + "devOptional": true, "requires": { "postcss-selector-parser": "^6.0.4" } @@ -83862,7 +86728,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, + "devOptional": true, "requires": { "icss-utils": "^5.0.0" } @@ -83938,7 +86804,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, "requires": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -83949,14 +86814,12 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" } } }, @@ -83997,6 +86860,16 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "peer": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -84158,6 +87031,13 @@ } } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "peer": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -84344,7 +87224,8 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -84424,8 +87305,7 @@ "railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" }, "ramda": { "version": "0.26.1", @@ -84436,7 +87316,6 @@ "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, "requires": { "discontinuous-range": "1.0.0", "ret": "~0.1.10" @@ -85147,7 +88026,8 @@ "react-colorful": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.5.0.tgz", - "integrity": "sha512-BuzrlrM0ylg7coPkXOrRqlf2BgHLw5L44sybbr9Lg4xy7w9e5N7fGYbojOO0s8J0nvrM3PERN2rVFkvSa24lnQ==" + "integrity": "sha512-BuzrlrM0ylg7coPkXOrRqlf2BgHLw5L44sybbr9Lg4xy7w9e5N7fGYbojOO0s8J0nvrM3PERN2rVFkvSa24lnQ==", + "requires": {} }, "react-datetime": { "version": "3.0.4", @@ -85196,7 +88076,8 @@ "react-docgen-typescript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.1.0.tgz", - "integrity": "sha512-7kpzLsYzVxff//HUVz1sPWLCdoSNvHD3M8b/iQLdF8fgf7zp26eVysRrAUSxiAT4yQv2zl09zHjJEYSYNxQ8Jw==" + "integrity": "sha512-7kpzLsYzVxff//HUVz1sPWLCdoSNvHD3M8b/iQLdF8fgf7zp26eVysRrAUSxiAT4yQv2zl09zHjJEYSYNxQ8Jw==", + "requires": {} }, "react-dom": { "version": "16.14.0", @@ -85254,7 +88135,8 @@ "react-error-boundary": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-1.2.5.tgz", - "integrity": "sha512-5CPSeLJA2igJNppAgFRwnTL9aK3ojenk65enNzhVyoxYNbHpIJXnChUO7+4vPhkncRA9wvQMXq6Azp2XeXd+iQ==" + "integrity": "sha512-5CPSeLJA2igJNppAgFRwnTL9aK3ojenk65enNzhVyoxYNbHpIJXnChUO7+4vPhkncRA9wvQMXq6Azp2XeXd+iQ==", + "requires": {} }, "react-fast-compare": { "version": "3.2.0", @@ -85347,7 +88229,8 @@ "react-js-cron": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/react-js-cron/-/react-js-cron-1.2.0.tgz", - "integrity": "sha512-mWxTmXkqP58ughdziS3qjEUVl1O03XEo8WDvr45/kTQfbd0C6ITniAsG5wZzwmTOgmrOKQheHog7L0TP683WUA==" + "integrity": "sha512-mWxTmXkqP58ughdziS3qjEUVl1O03XEo8WDvr45/kTQfbd0C6ITniAsG5wZzwmTOgmrOKQheHog7L0TP683WUA==", + "requires": {} }, "react-json-tree": { "version": "0.11.2", @@ -85407,7 +88290,8 @@ "react-lines-ellipsis": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/react-lines-ellipsis/-/react-lines-ellipsis-0.15.0.tgz", - "integrity": "sha512-8kWpEmu7ijmB6Gz5t+eSjNux2SpVXZBsmfeFE8LjMS7tU3H8ai475CyNc0dH0RDTwt4Esr7c06Xq4SB7Gpl9yQ==" + "integrity": "sha512-8kWpEmu7ijmB6Gz5t+eSjNux2SpVXZBsmfeFE8LjMS7tU3H8ai475CyNc0dH0RDTwt4Esr7c06Xq4SB7Gpl9yQ==", + "requires": {} }, "react-loadable": { "version": "5.5.0", @@ -85581,7 +88465,8 @@ "react-reverse-portal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.0.1.tgz", - "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==" + "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==", + "requires": {} }, "react-router": { "version": "5.1.2", @@ -85779,18 +88664,20 @@ "react-table": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.3.tgz", - "integrity": "sha512-hfPF13zDLxPMpLKzIKCE8RZud9T/XrRTsaCIf8zXpWZIZ2juCl7qrGpo3AQw9eAetXV5DP7s2GDm+hht7qq5Dw==" + "integrity": "sha512-hfPF13zDLxPMpLKzIKCE8RZud9T/XrRTsaCIf8zXpWZIZ2juCl7qrGpo3AQw9eAetXV5DP7s2GDm+hht7qq5Dw==", + "requires": {} }, "react-tag-autocomplete": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-tag-autocomplete/-/react-tag-autocomplete-6.3.0.tgz", - "integrity": "sha512-MUBVUFh5eOqshUm5NM20qp7zXk8TzSiKO4GoktlFzBLIOLs356npaMKtL02bm0nFV2f1zInUrXn1fq6+i5YX0w==" + "integrity": "sha512-MUBVUFh5eOqshUm5NM20qp7zXk8TzSiKO4GoktlFzBLIOLs356npaMKtL02bm0nFV2f1zInUrXn1fq6+i5YX0w==", + "requires": {} }, "react-test-renderer": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.9.0.tgz", "integrity": "sha512-R62stB73qZyhrJo7wmCW9jgl/07ai+YzvouvCXIJLBkRlRqLx4j9RqcLEAfNfU3OxTGucqR2Whmn3/Aad6L3hQ==", - "dev": true, + "devOptional": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", @@ -85802,7 +88689,7 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true + "devOptional": true } } }, @@ -85852,7 +88739,8 @@ "react-virtualized-auto-sizer": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.6.tgz", - "integrity": "sha512-7tQ0BmZqfVF6YYEWcIGuoR3OdYe8I/ZFbNclFlGOC3pMqunkYF/oL30NCjSGl9sMEb17AnzixDz98Kqc3N76HQ==" + "integrity": "sha512-7tQ0BmZqfVF6YYEWcIGuoR3OdYe8I/ZFbNclFlGOC3pMqunkYF/oL30NCjSGl9sMEb17AnzixDz98Kqc3N76HQ==", + "requires": {} }, "react-virtualized-select": { "version": "3.1.3", @@ -85916,7 +88804,15 @@ "reactable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reactable/-/reactable-1.1.0.tgz", - "integrity": "sha512-SnvZ3CXyFFxGotw9cqNiVUGb2oW16UlIypGQZRJGgPiJuFqW22jO7A+Y/Tvv8no8F/bZoLdZ+QJP7eZfcc9kCw==" + "integrity": "sha512-SnvZ3CXyFFxGotw9cqNiVUGb2oW16UlIypGQZRJGgPiJuFqW22jO7A+Y/Tvv8no8F/bZoLdZ+QJP7eZfcc9kCw==", + "requires": {} + }, + "reactable-arc": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/reactable-arc/-/reactable-arc-0.15.0.tgz", + "integrity": "sha512-XH1mryI/xvbYb3lCVOU3rx/KRacDE0PDa45KazL/PPTM0AgPZ/awVmCAxRi179BpjbStk7cgCyFjI2oYJ28E8A==", + "peer": true, + "requires": {} }, "reactcss": { "version": "1.2.3", @@ -86211,7 +89107,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, "requires": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -86433,6 +89328,16 @@ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "peer": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "remark-external-links": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", @@ -86886,6 +89791,16 @@ "uuid": "^3.3.2" } }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "peer": true, + "requires": { + "throttleit": "^1.0.0" + } + }, "request-promise-core": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", @@ -86937,7 +89852,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "devOptional": true }, "reselect": { "version": "4.0.0", @@ -87017,13 +89932,20 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true + "devOptional": true }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true, + "peer": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -87050,7 +89972,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "dev": true, "requires": { "lodash.flattendeep": "^4.4.0", "nearley": "^2.7.10" @@ -87148,7 +90069,7 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", - "dev": true, + "devOptional": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -87181,13 +90102,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true + "devOptional": true }, "selfsigned": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", - "dev": true, + "devOptional": true, "requires": { "node-forge": "^0.10.0" } @@ -87262,7 +90183,8 @@ "serialize-query-params": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/serialize-query-params/-/serialize-query-params-1.2.4.tgz", - "integrity": "sha512-m4hGkOY5y+ksPDSEkw12cNxt3HRUJv5G6oF9/4yq+GCw4LznudxC73qnz++VTHqXa0j1x1/iaBIpoiMBxr6w2w==" + "integrity": "sha512-m4hGkOY5y+ksPDSEkw12cNxt3HRUJv5G6oF9/4yq+GCw4LznudxC73qnz++VTHqXa0j1x1/iaBIpoiMBxr6w2w==", + "requires": {} }, "serve-favicon": { "version": "2.5.0", @@ -87292,7 +90214,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, + "devOptional": true, "requires": { "accepts": "~1.3.4", "batch": "0.6.1", @@ -87355,7 +90277,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true + "devOptional": true }, "sha.js": { "version": "2.4.11", @@ -87675,7 +90597,7 @@ "version": "0.3.21", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "dev": true, + "devOptional": true, "requires": { "faye-websocket": "^0.11.3", "uuid": "^3.4.0", @@ -87806,8 +90728,7 @@ "source-map-js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", - "dev": true + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==" }, "source-map-resolve": { "version": "0.5.2", @@ -87850,6 +90771,50 @@ "trim": "0.0.1" } }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "peer": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -87882,7 +90847,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, + "devOptional": true, "requires": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -87895,7 +90860,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, + "devOptional": true, "requires": { "ms": "^2.1.1" } @@ -87904,7 +90869,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true } } }, @@ -87912,7 +90877,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, + "devOptional": true, "requires": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -87926,7 +90891,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, + "devOptional": true, "requires": { "ms": "^2.1.1" } @@ -87935,13 +90900,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -88178,7 +91143,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true + "devOptional": true }, "stealthy-require": { "version": "1.1.1", @@ -88219,7 +91184,8 @@ "storybook-pretty-props": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/storybook-pretty-props/-/storybook-pretty-props-1.2.1.tgz", - "integrity": "sha512-3dUtu0UbBA6idA3Qo0i+CYGGz8GiqlXzhgCJdT065jnuJ3y9intKxZpv05ZbnQXCPnsPVSDos+hgOZ444hf6xA==" + "integrity": "sha512-3dUtu0UbBA6idA3Qo0i+CYGGz8GiqlXzhgCJdT065jnuJ3y9intKxZpv05ZbnQXCPnsPVSDos+hgOZ444hf6xA==", + "requires": {} }, "stream-browserify": { "version": "2.0.2", @@ -88378,7 +91344,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz", "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.13.0", @@ -88502,7 +91467,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.2.1.tgz", "integrity": "sha512-1k9ZosJCRFaRbY6hH49JFlRB0fVSbmnyq1iTPjNxUmGVjBNEmwrrHPenhlp+Lgo51BojHSf6pl2FcqYaN3PfVg==", - "dev": true + "dev": true, + "requires": {} }, "style-to-object": { "version": "0.3.0", @@ -89234,6 +92200,13 @@ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==" }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", + "dev": true, + "peer": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -89252,7 +92225,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true + "devOptional": true }, "timed-out": { "version": "4.0.1", @@ -89587,6 +92560,37 @@ } } }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "peer": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "peer": true + } + } + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -89689,8 +92693,7 @@ "typescript": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", - "dev": true + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==" }, "typical": { "version": "4.0.0", @@ -89984,6 +92987,13 @@ } } }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "peer": true + }, "unzip-response": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", @@ -90090,12 +93100,14 @@ "use-immer": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.6.0.tgz", - "integrity": "sha512-dFGRfvWCqPDTOt/S431ETYTg6+uxbpb7A1pptufwXVzGJY3RlXr38+3wyLNpc6SbbmAKjWl6+EP6uW74fkEsXQ==" + "integrity": "sha512-dFGRfvWCqPDTOt/S431ETYTg6+uxbpb7A1pptufwXVzGJY3RlXr38+3wyLNpc6SbbmAKjWl6+EP6uW74fkEsXQ==", + "requires": {} }, "use-isomorphic-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", - "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" + "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==", + "requires": {} }, "use-latest": { "version": "1.2.0", @@ -90170,6 +93182,13 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "peer": true + }, "v8-to-istanbul": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", @@ -90456,7 +93475,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, + "devOptional": true, "requires": { "minimalistic-assert": "^1.0.0" } @@ -90484,7 +93503,6 @@ "version": "5.52.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.52.1.tgz", "integrity": "sha512-wkGb0hLfrS7ML3n2xIKfUIwHbjB6gxwQHyLmVHoAqEQBw+nWo+G6LoHL098FEXqahqximsntjBLuewStrnJk0g==", - "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", "@types/estree": "^0.0.50", @@ -90516,7 +93534,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -90525,26 +93542,22 @@ "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -90556,7 +93569,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -90565,7 +93577,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -90573,14 +93584,12 @@ "@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -90596,7 +93605,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -90609,7 +93617,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -90621,7 +93628,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -90635,7 +93641,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -90645,7 +93650,6 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", - "dev": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -90654,14 +93658,12 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "jest-worker": { "version": "27.2.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.0.tgz", "integrity": "sha512-laB0ZVIBz+voh/QQy9dmUuuDsadixeerrKqyVpgPz+CCWiOYjOBabUXHIXZhsdvkWbLqSHbgkAHWl5cg24Q6RA==", - "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -90672,7 +93674,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -90681,7 +93682,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -90689,14 +93689,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -90704,14 +93702,12 @@ "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "terser": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.8.0.tgz", "integrity": "sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==", - "dev": true, "requires": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -90721,8 +93717,7 @@ "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, @@ -90730,7 +93725,6 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, "requires": { "jest-worker": "^27.0.6", "p-limit": "^3.1.0", @@ -90744,7 +93738,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", - "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -90938,7 +93931,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.2.0.tgz", "integrity": "sha512-iBaDkHBLfW3cEITeJWNkjZBrm+b5A3YLg8XVdNOdjUNABdXJwcsJv4dzKSnVf1q4Ch489+6epWVW6OcOyVfG7w==", - "dev": true, + "devOptional": true, "requires": { "ansi-html-community": "^0.0.8", "bonjour": "^3.5.0", @@ -90971,13 +93964,13 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true + "devOptional": true }, "del": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, + "devOptional": true, "requires": { "globby": "^11.0.1", "graceful-fs": "^4.2.4", @@ -90993,7 +93986,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, + "devOptional": true, "requires": { "is-docker": "^2.0.0" } @@ -91002,7 +93995,7 @@ "version": "8.2.1", "resolved": "https://registry.npmjs.org/open/-/open-8.2.1.tgz", "integrity": "sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ==", - "dev": true, + "devOptional": true, "requires": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -91013,13 +94006,13 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "devOptional": true }, "strip-ansi": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, + "devOptional": true, "requires": { "ansi-regex": "^6.0.1" } @@ -91028,7 +94021,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.1.0.tgz", "integrity": "sha512-oT660AR1gOnU/NTdUQi3EiGR0iXG7CFxmKsj3ylWCBA2khJ8LFHK+sKv3BZEsC11gl1eChsltRhzUq7nWj7XIQ==", - "dev": true, + "devOptional": true, "requires": { "colorette": "^1.2.2", "memfs": "^3.2.2", @@ -91041,7 +94034,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "devOptional": true, + "requires": {} } } }, @@ -91127,8 +94121,7 @@ "webpack-sources": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==", - "dev": true + "integrity": "sha512-fahN08Et7P9trej8xz/Z7eRu8ltyiygEo/hnRi9KqBUs80KeDcnf96ZJo++ewWd84fEf3xSX9bp4ZS9hbw0OBw==" }, "webpack-virtual-modules": { "version": "0.2.2", @@ -91157,7 +94150,7 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, + "devOptional": true, "requires": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -91168,7 +94161,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true + "devOptional": true }, "wgs84": { "version": "0.0.0", @@ -91511,7 +94504,8 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true + "dev": true, + "requires": {} }, "x-is-string": { "version": "0.1.0", @@ -92511,6 +95505,13 @@ } } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "peer": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json index 7b11af60566e5..087560d58f0f6 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json @@ -44,12 +44,15 @@ "peerDependencies": { "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", + "mapbox-gl": "*", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-map-gl": "^4.0.10", - "mapbox-gl": "*" + "react-map-gl": "^4.0.10" }, "publishConfig": { "access": "public" + }, + "devDependencies": { + "@types/react-map-gl": "^6.1.3" } } diff --git a/superset-frontend/src/components/ObjectTags/ObjectTags.css b/superset-frontend/src/components/ObjectTags/ObjectTags.css index 60d2f787f8530..069f247179532 100644 --- a/superset-frontend/src/components/ObjectTags/ObjectTags.css +++ b/superset-frontend/src/components/ObjectTags/ObjectTags.css @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - .ant-tag { +.ant-tag { color: black; } @@ -47,7 +47,7 @@ padding: 6px 8px; border: 0px solid #f5f5f5; border-radius: 2px; - background: #F1F1F1; + background: #f1f1f1; /* match the font styles */ font-size: inherit; @@ -74,12 +74,10 @@ } @media screen and (min-width: 30em) { - .react-tags__search { /* this will become the offsetParent for suggestions */ position: relative; } - } .react-tags__search input { @@ -110,11 +108,9 @@ } @media screen and (min-width: 30em) { - .react-tags__suggestions { width: 240px; } - } .react-tags__suggestions ul { @@ -122,7 +118,7 @@ padding: 0; list-style: none; background: white; - border: 1px solid #D1D1D1; + border: 1px solid #d1d1d1; border-radius: 2px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 29e6ff6877ca8..035281da6adff 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -25,7 +25,10 @@ import './ObjectTags.css'; import { TagsList } from 'src/components/Tags'; import rison from 'rison'; import { cacheWrapper } from 'src/utils/cacheWrapper'; -import { ClientErrorObject, getClientErrorObject } from 'src/utils/getClientErrorObject'; +import { + ClientErrorObject, + getClientErrorObject, +} from 'src/utils/getClientErrorObject'; import { deleteTag, fetchTags } from 'src/tags'; export interface ObjectTagsProps { @@ -44,7 +47,7 @@ const StyledTagsDiv = styled.div` display: flex; -webkit-flex-direction: row; -webkit-flex-wrap: wrap; - ` +`; const localCache = new Map(); @@ -79,15 +82,15 @@ export const loadTags = async ( order_column: searchColumn, order_direction: 'asc', }); - + const getErrorMessage = ({ error, message }: ClientErrorObject) => { - let errorText = message || error || t('An error has occurred'); - if (message === 'Forbidden') { - errorText = t('You do not have permission to edit this dashboard'); - } - return errorText; - } - + let errorText = message || error || t('An error has occurred'); + if (message === 'Forbidden') { + errorText = t('You do not have permission to edit this dashboard'); + } + return errorText; + }; + return cachedSupersetGet({ endpoint: `/api/v1/tag/?q=${query}`, // endpoint: `/api/v1/tags/?q=${query}`, @@ -96,7 +99,9 @@ export const loadTags = async ( const data: { label: string; value: string | number; - }[] = response.json.result.filter((item: Tag & { table_name: string },) => (item.type === 1)).map(tagToSelectOption); + }[] = response.json.result + .filter((item: Tag & { table_name: string }) => item.type === 1) + .map(tagToSelectOption); return { data, totalCount: response.json.count, @@ -112,8 +117,8 @@ export const ObjectTags = ({ objectType, objectId, includeTypes, - editMode=false, - maxTags=undefined, + editMode = false, + maxTags = undefined, onChange, }: ObjectTagsProps) => { const [tags, setTags] = useState([]); @@ -121,21 +126,25 @@ export const ObjectTags = ({ useEffect(() => { try { fetchTags( - {objectType, objectId, includeTypes}, + { objectType, objectId, includeTypes }, (tags: Tag[]) => setTags(tags), - () => {/*TODO: handle error*/}); - } catch(error: any) { - console.log(error) + () => { + /* TODO: handle error */ + }, + ); + } catch (error: any) { + console.log(error); } }, [objectType, objectId, includeTypes]); - const onDelete = (tagIndex: number) => { deleteTag( - {objectType, objectId}, - tags[tagIndex], + { objectType, objectId }, + tags[tagIndex], () => setTags(tags.filter((_, i) => i !== tagIndex)), - () => {/* TODO: handle error */} + () => { + /* TODO: handle error */ + }, ); onChange?.(tags); }; @@ -143,7 +152,12 @@ export const ObjectTags = ({ return ( - + ); diff --git a/superset-frontend/src/components/Tags/Tag.test.tsx b/superset-frontend/src/components/Tags/Tag.test.tsx index 7e02030ee753c..b797665b13c4f 100644 --- a/superset-frontend/src/components/Tags/Tag.test.tsx +++ b/superset-frontend/src/components/Tags/Tag.test.tsx @@ -18,15 +18,15 @@ */ import React from 'react'; import { render } from 'spec/helpers/testing-library'; -import Tag from './Tag'; import TagType from 'src/types/TagType'; +import Tag from './Tag'; const mockedProps: TagType = { - name: 'example-tag', - id: 1, - onDelete: null, - editable: false, - onClick: null + name: 'example-tag', + id: 1, + onDelete: null, + editable: false, + onClick: null, }; test('should render', () => { diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index 60a6cf2e61126..e2907e491f103 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -17,13 +17,12 @@ * under the License. */ -import { styled, SupersetTheme } from "@superset-ui/core"; -import TagType from "src/types/TagType"; +import { styled, SupersetTheme } from '@superset-ui/core'; +import TagType from 'src/types/TagType'; import AntdTag from 'antd/lib/tag'; -import { useMemo } from "react"; +import { useMemo, React } from 'react'; import { Tooltip } from 'src/components/Tooltip'; -import React from 'react'; - + const customTagStyler = (theme: SupersetTheme) => ` margin-top: ${theme.gridUnit * 1}px; margin-bottom: ${theme.gridUnit * 1}px; @@ -34,57 +33,52 @@ const StyledTag = styled(AntdTag)` ${({ theme }) => customTagStyler(theme)}} `; -const Tag = ({ - name, - id, - index=-1, - onDelete=null, - editable=false, - onClick=null -} : TagType) => { - - const isLongTag = useMemo(() => name.length > 20, [name]); +const Tag = ({ + name, + id, + index = -1, + onDelete = null, + editable = false, + onClick = null, +}: TagType) => { + const isLongTag = useMemo(() => name.length > 20, [name]); - const handleClose = () => { - return (index >= 0) ? ( - onDelete(index) - ) : ( - null - ) - } + const handleClose = () => (index >= 0 ? onDelete(index) : null); - const tagElem =( - <> - {editable ? ( - - {isLongTag ? `${name.slice(0, 20)}...` : name} - - ) : ( - - {id ? ( - {isLongTag ? `${name.slice(0, 20)}...` : name} - ):( - isLongTag ? `${name.slice(0, 20)}...` : name - )} - - )} - - ); + const tagElem = ( + <> + {editable ? ( + + {isLongTag ? `${name.slice(0, 20)}...` : name} + + ) : ( + + {id ? ( + + {isLongTag ? `${name.slice(0, 20)}...` : name} + + ) : isLongTag ? ( + `${name.slice(0, 20)}...` + ) : ( + name + )} + + )} + + ); - return ( - isLongTag ? ( - - {tagElem} - - ) : ( - tagElem - ) - - ); -} + return isLongTag ? ( + + {tagElem} + + ) : ( + tagElem + ); +}; export default Tag; diff --git a/superset-frontend/src/components/Tags/TagsList.test.tsx b/superset-frontend/src/components/Tags/TagsList.test.tsx index 3b28fbadc8fd9..d9817e6458134 100644 --- a/superset-frontend/src/components/Tags/TagsList.test.tsx +++ b/superset-frontend/src/components/Tags/TagsList.test.tsx @@ -41,7 +41,7 @@ const testTags = [ name: 'example-tag5', id: 5, }, -] +]; const mockedProps: TagsListProps = { tags: testTags, @@ -49,8 +49,7 @@ const mockedProps: TagsListProps = { maxTags: 5, }; -const findAllTags = () => - screen.getAllByRole("tag")! as HTMLElement[]; +const findAllTags = () => screen.getAllByRole('link')! as HTMLElement[]; test('should render', () => { const { container } = render(); @@ -70,7 +69,7 @@ test('should render 5 elements', () => { }); test('should render 3 elements when maxTags is set to 3', () => { - render(); + render(); const tagsListItems = findAllTags(); expect(tagsListItems).toHaveLength(3); expect(tagsListItems[2]).toHaveTextContent('+3...'); diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index 65f422124625c..4748f55cda4ff 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -19,14 +19,14 @@ import React, { useMemo, useState } from 'react'; import { styled } from '@superset-ui/core'; -import Tag from './Tag'; import TagType from 'src/types/TagType'; +import Tag from './Tag'; export type TagsListProps = { tags: TagType[]; editable?: boolean; /** - * OnDelete: + * OnDelete: * Only applies when editable is true * Callback for when a tag is deleted */ @@ -42,42 +42,66 @@ const TagsDiv = styled.div` -webkit-flex-wrap: wrap; `; -const TagsList = ({ - tags, - editable=false, - onDelete, - maxTags +const TagsList = ({ + tags, + editable = false, + onDelete, + maxTags, }: TagsListProps) => { - const [tempMaxTags, setTempMaxTags] = useState(maxTags); const handleDelete = (index: number) => { onDelete?.(index); - } + }; const expand = () => setTempMaxTags(undefined); const collapse = () => setTempMaxTags(maxTags); - const tagsIsLong: boolean | null = useMemo(() => (tempMaxTags ? (tags.length > tempMaxTags): null), [tags.length, tempMaxTags]); + const tagsIsLong: boolean | null = useMemo( + () => (tempMaxTags ? tags.length > tempMaxTags : null), + [tags.length, tempMaxTags], + ); - const extraTags: number | null = useMemo(() => ((typeof tempMaxTags === "number") ? ((tags.length - tempMaxTags) + 1) : null), [tagsIsLong, tags.length, tempMaxTags]) + const extraTags: number | null = useMemo( + () => + typeof tempMaxTags === 'number' ? tags.length - tempMaxTags + 1 : null, + [tagsIsLong, tags.length, tempMaxTags], + ); return ( - - {(tagsIsLong === true && typeof tempMaxTags === "number") ? ( + + {tagsIsLong === true && typeof tempMaxTags === 'number' ? ( <> - {tags.slice(0,(tempMaxTags-1)).map((tag: TagType, index) => ( - - ))} - {tags.length > tempMaxTags ? () : (null)} + {tags.slice(0, tempMaxTags - 1).map((tag: TagType, index) => ( + + ))} + {tags.length > tempMaxTags ? ( + + ) : null} - ): ( + ) : ( <> - {tags.map((tag: TagType, index) => ( - - ))} - {maxTags ? (tags.length > maxTags ? () : (null)) : (null)} + {tags.map((tag: TagType, index) => ( + + ))} + {maxTags ? ( + tags.length > maxTags ? ( + + ) : null + ) : null} )} diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index e3bc0c238070f..6a1d62bc6b62b 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -33,8 +33,6 @@ import Button from 'src/components/Button'; import { AntdButton } from 'src/components/'; import { findPermission } from 'src/utils/findPermission'; import { Tooltip } from 'src/components/Tooltip'; -import EditableTitle from 'src/components/EditableTitle'; -import FaveStar from 'src/components/FaveStar'; import ObjectTags from 'src/components/ObjectTags'; import { safeStringify } from 'src/utils/safeStringify'; import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown'; @@ -54,8 +52,8 @@ import setPeriodicRunner, { import { options as PeriodicRefreshOptions } from 'src/dashboard/components/RefreshIntervalModal'; import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; -import { DashboardEmbedModal } from '../DashboardEmbedControls'; import { OBJECT_TYPES } from 'src/tags'; +import { DashboardEmbedModal } from '../DashboardEmbedControls'; const MAX_TAGS = 3; @@ -516,25 +514,26 @@ class Header extends React.PureComponent { isStarred: this.props.isStarred, showTooltip: true, }} - titlePanelAdditionalItems={ - [ - !editMode && , - isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && + /> + ), + isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && ( - ] - } + ), + ]} rightPanelAdditionalItems={
{userCanSaveAs && ( @@ -632,7 +631,6 @@ class Header extends React.PureComponent { )}
)} -

} menuDropdownProps={{ diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index e9b543fda432e..3e5ff76d77dd6 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -323,31 +323,29 @@ const PropertiesModal = ({ }); // update tags - newTags.map((tag: TagType) => { - // add new tags + newTags.map((tag: TagType) => addTag( { objectType: OBJECT_TYPES.DASHBOARD, objectId: dashboardId, includeTypes: false, - }, + }, tag.name, () => {}, - () => {} - ); - }); - oldTags.map((tag: TagType) => { - // add new tags + () => {}, + ), + ); + oldTags.map((tag: TagType) => deleteTag( { objectType: OBJECT_TYPES.DASHBOARD, objectId: dashboardId, - }, + }, tag, () => {}, - () => {} - ); - }); + () => {}, + ), + ); const moreOnSubmitProps: { roles?: Roles } = {}; const morePutProps: { roles?: number[] } = {}; if (isFeatureEnabled(FeatureFlag.DASHBOARD_RBAC)) { @@ -533,31 +531,39 @@ const PropertiesModal = ({ try { fetchTags( { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: dashboardId, - includeTypes: false}, + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + includeTypes: false, + }, (tags: TagType[]) => setTags(tags), - () => {/*TODO: handle error*/}); - } catch(error: any) { - console.log(error) + () => { + /* TODO: handle error */ + }, + ); + } catch (error: any) { + console.log(error); } }, [dashboardId]); const handleAddTag = (values: { label: string; value: number }[]) => { values.map((value: { label: string; value: number }) => { - let tag = {name: value.label}; + const tag = { name: value.label }; if (tags.some(t => t.name === tag.name)) { return; } setTags([...tags, tag]); - setNewTags([...newTags, tag]) + setNewTags([...newTags, tag]); + return; }); - } + }; const handleDeleteTag = (tagIndex: number) => { setOldTags([...oldTags, tags[tagIndex]]); - setTags([...tags.slice(0,tagIndex), ...tags.slice(tagIndex+1, tags.length)]); - } + setTags([ + ...tags.slice(0, tagIndex), + ...tags.slice(tagIndex + 1, tags.length), + ]); + }; return ( - -

{t('Tags')}

- + +

{t('Tags')}

+

- {t( - 'A list of tags that have been applied to this chart.', - )} + {t('A list of tags that have been applied to this chart.')}

diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index 9a2e909ff7f11..b25c70dcdaf89 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -35,20 +35,10 @@ import Icons from 'src/components/Icons'; import PropertiesModal from 'src/explore/components/PropertiesModal'; import { sliceUpdated } from 'src/explore/actions/exploreActions'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; -import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu'; -import CertifiedBadge from 'src/components/CertifiedBadge'; -import RowCountLabel from '../RowCountLabel'; -import ObjectTags from 'src/components/ObjectTags'; -import {fetchTags, OBJECT_TYPES} from 'src/tags'; +import { fetchTags, OBJECT_TYPES } from 'src/tags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { TagsList } from 'src/components/Tags'; -import { Tag } from 'src/types/TagType' - -const CHART_STATUS_MAP = { - failed: 'danger', - loading: 'warning', - success: 'success', -}; +import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu'; const MAX_TAGS = 3; @@ -165,15 +155,19 @@ export const ExploreChartHeader = ({ const oldSliceName = slice?.slice_name; const [tags, setTags] = useState([]); - + useEffect(() => { - fetchTags({ - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - includeTypes: false - }, - (tags) => setTags(tags), - () => {/** handle error */}) + fetchTags( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false, + }, + tags => setTags(tags), + () => { + /** handle error */ + }, + ); }, [slice]); return ( @@ -202,26 +196,28 @@ export const ExploreChartHeader = ({ isStarred, showTooltip: true, }} - titlePanelAdditionalItems={ - [ - sliceFormData ? ( - - ) : null, - isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( - (tag.type ? (tag.type === 1 || tag.type === "TagTypes.custom"): (true)))} - maxTags={MAX_TAGS} - /> - ) : null - ] - } + titlePanelAdditionalItems={[ + sliceFormData ? ( + + ) : null, + isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( + + tag.type + ? tag.type === 1 || tag.type === 'TagTypes.custom' + : true, + )} + maxTags={MAX_TAGS} + /> + ) : null, + ]} rightPanelAdditionalItems={ o.value); } // update tags - newTags.map((tag: TagType) => { - // add new tags + newTags.map((tag: TagType) => addTag( { objectType: OBJECT_TYPES.CHART, objectId: slice.slice_id, includeTypes: false, - }, + }, tag.name, () => {}, - () => {} - ); - }); - oldTags.map((tag: TagType) => { - // add new tags + () => {}, + ), + ); + oldTags.map((tag: TagType) => deleteTag( { objectType: OBJECT_TYPES.CHART, objectId: slice.slice_id, - }, + }, tag, () => {}, - () => {} - ); - }); - + () => {}, + ), + ); + try { const res = await SupersetClient.put({ endpoint: `/api/v1/chart/${slice.slice_id}`, @@ -193,7 +191,7 @@ function PropertiesModal({ const updatedChart = { ...payload, ...res.json.result, - tags: tags, + tags, id: slice.slice_id, }; onSave(updatedChart); @@ -222,31 +220,38 @@ function PropertiesModal({ try { fetchTags( { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - includeTypes: false}, + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false, + }, (tags: TagType[]) => setTags(tags), - () => {/*TODO: handle error*/}); - } catch(error: any) { - console.log(error) + () => { + /* TODO: handle error */ + }, + ); + } catch (error: any) { + console.log(error); } }, [slice.slice_id]); const handleAddTag = (values: { label: string; value: number }[]) => { values.map((value: { label: string; value: number }) => { - let tag = {name: value.label}; + const tag = { name: value.label }; if (tags.some(t => t.name === tag.name)) { return; } setTags([...tags, tag]); - setNewTags([...newTags, tag]) + return setNewTags([...newTags, tag]); }); - } + }; const handleDeleteTag = (tagIndex: number) => { setOldTags([...oldTags, tags[tagIndex]]); - setTags([...tags.slice(0,tagIndex), ...tags.slice(tagIndex+1, tags.length)]); - } + setTags([ + ...tags.slice(0, tagIndex), + ...tags.slice(tagIndex + 1, tags.length), + ]); + }; return ( {t('Tags')} - {t( - 'A list of tags that have been applied to this chart.', - )} + {t('A list of tags that have been applied to this chart.')} diff --git a/superset-frontend/src/types/TagType.ts b/superset-frontend/src/types/TagType.ts index e63f46ad2ef78..ae7fd8c523a9b 100644 --- a/superset-frontend/src/types/TagType.ts +++ b/superset-frontend/src/types/TagType.ts @@ -20,7 +20,7 @@ export interface TagType { id?: string | number; type?: string | number; - editable?: boolean + editable?: boolean; onDelete?: any; onClick?: any; name: string; diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 90745e2785d65..aad4cdb8065f0 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -375,8 +375,12 @@ function ChartList(props: ChartListProps) { }, }: any) => ( // Only show custom type tags - (tag.type ? (tag.type === 1 || tag.type === "TagTypes.custom"): (true)))} + + tag.type + ? tag.type === 1 || tag.type === 'TagTypes.custom' + : true, + )} maxTags={3} /> ), diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 33ce2d5f387b6..82dff379fd76f 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -353,9 +353,12 @@ function DashboardList(props: DashboardListProps) { }, }: any) => ( // Only show custom type tags - (tag.type === 'TagTypes.custom' || tag.type === 1))} - maxTags={3}/> + tag.type === 'TagTypes.custom' || tag.type === 1, + )} + maxTags={3} + /> ), Header: t('Tags'), accessor: 'tags', diff --git a/superset-frontend/src/views/CRUD/tags/Tags.tsx b/superset-frontend/src/views/CRUD/tags/Tags.tsx index 8ee0af8ffb2d9..b5fba5a1b2b78 100644 --- a/superset-frontend/src/views/CRUD/tags/Tags.tsx +++ b/superset-frontend/src/views/CRUD/tags/Tags.tsx @@ -35,14 +35,14 @@ const TagsContainer = styled.div` .select-control-label { text-transform: uppercase; font-size: ${({ theme }) => theme.gridUnit * 3}px; - color: #666666; + color: ${({ theme }) => theme.colors.grayscale.base}; margin-bottom: ${({ theme }) => theme.gridUnit * 1}px; } `; const TagsNav = styled.div` height: 50px; - background-color: white; + background-color: ${({ theme }) => theme.colors.grayscale.light5}; margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; .navbar-brand { margin-left: ${({ theme }) => theme.gridUnit * 2}px; diff --git a/superset/views/tags.py b/superset/views/tags.py index cc7366505e10f..009e2319320c6 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -29,7 +29,6 @@ from werkzeug.exceptions import NotFound from superset import db, is_feature_enabled, utils -from superset.connectors.sqla.models import SqlaTable from superset.jinja_context import ExtraCache from superset.models.dashboard import Dashboard from superset.models.slice import Slice From 22d5b8da751eb8811ac06fecbf9eaf4e7be1115b Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 25 Jul 2022 16:50:51 -0400 Subject: [PATCH 20/76] fixed linting errors --- .../src/dashboard/components/PropertiesModal/index.tsx | 3 +-- .../src/explore/components/PropertiesModal/index.tsx | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 8f6b1eb451bb1..ceb9384d06c88 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -559,8 +559,7 @@ const PropertiesModal = ({ return; } setTags([...tags, tag]); - setNewTags([...newTags, tag]); - return; + return setNewTags([...newTags, tag]); }); }; diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 16d07ae041cd4..ccdcdc6208f75 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -227,6 +227,7 @@ function PropertiesModal({ (tags: TagType[]) => setTags(tags), () => { /* TODO: handle error */ + return }, ); } catch (error: any) { From b38dd2bb724cc8a51d075b693fcedf7912fe52a0 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 26 Jul 2022 16:28:45 -0400 Subject: [PATCH 21/76] fixed package json and added feature flags --- superset-frontend/package.json | 2 - .../legacy-preset-chart-deckgl/package.json | 8 +- superset-frontend/src/components/Tags/Tag.tsx | 6 +- .../components/PropertiesModal/index.tsx | 54 +++++----- .../components/ExploreChartHeader/index.jsx | 1 + .../components/PropertiesModal/index.tsx | 101 +++++++++--------- superset-frontend/src/types/TagType.ts | 2 +- 7 files changed, 91 insertions(+), 83 deletions(-) diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 6bcdba2e5c70e..36d092c953acd 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -183,7 +183,6 @@ "react-sticky": "^6.0.3", "react-syntax-highlighter": "^15.4.5", "react-table": "^7.6.3", - "react-tag-autocomplete": "^6.3.0", "react-transition-group": "^2.5.3", "react-ultimate-pagination": "^1.2.0", "react-virtualized": "9.19.1", @@ -257,7 +256,6 @@ "@types/react-select": "^3.0.19", "@types/react-sticky": "^6.0.3", "@types/react-table": "^7.0.19", - "@types/react-tag-autocomplete": "^6.1.1", "@types/react-ultimate-pagination": "^1.2.0", "@types/react-virtualized": "^9.21.10", "@types/react-window": "^1.8.2", diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json index 087560d58f0f6..7278bb705cbf9 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json @@ -44,15 +44,13 @@ "peerDependencies": { "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", - "mapbox-gl": "*", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-map-gl": "^4.0.10" + "react-map-gl": "^4.0.10", + "mapbox-gl": "*" + }, "publishConfig": { "access": "public" - }, - "devDependencies": { - "@types/react-map-gl": "^6.1.3" } } diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index e2907e491f103..0dbd62edbdda2 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -20,7 +20,7 @@ import { styled, SupersetTheme } from '@superset-ui/core'; import TagType from 'src/types/TagType'; import AntdTag from 'antd/lib/tag'; -import { useMemo, React } from 'react'; +import React, { useMemo } from 'react'; import { Tooltip } from 'src/components/Tooltip'; const customTagStyler = (theme: SupersetTheme) => ` @@ -36,14 +36,14 @@ const StyledTag = styled(AntdTag)` const Tag = ({ name, id, - index = -1, + index = undefined, onDelete = null, editable = false, onClick = null, }: TagType) => { const isLongTag = useMemo(() => name.length > 20, [name]); - const handleClose = () => (index >= 0 ? onDelete(index) : null); + const handleClose = () => (index ? onDelete(index) : null); const tagElem = ( <> diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index ceb9384d06c88..0e6304e0168c1 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -329,30 +329,33 @@ const PropertiesModal = ({ updateMetadata: false, }); - // update tags - newTags.map((tag: TagType) => - addTag( - { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: dashboardId, - includeTypes: false, - }, - tag.name, - () => {}, - () => {}, - ), - ); - oldTags.map((tag: TagType) => - deleteTag( - { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: dashboardId, - }, - tag, - () => {}, - () => {}, - ), - ); + if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { + // update tags + newTags.map((tag: TagType) => + addTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + includeTypes: false, + }, + tag.name, + () => {}, + () => {}, + ), + ); + oldTags.map((tag: TagType) => + deleteTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + }, + tag, + () => {}, + () => {}, + ), + ); + } + const moreOnSubmitProps: { roles?: Roles } = {}; const morePutProps: { roles?: number[] } = {}; if (isFeatureEnabled(FeatureFlag.DASHBOARD_RBAC)) { @@ -535,6 +538,7 @@ const PropertiesModal = ({ }, [dashboardInfo, dashboardTitle, form]); useEffect(() => { + if(!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; try { fetchTags( { @@ -673,6 +677,7 @@ const PropertiesModal = ({

{t('Tags')}

+ {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? @@ -699,6 +704,7 @@ const PropertiesModal = ({ /> + : null}

diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index b25c70dcdaf89..6e68dd3d4a9f6 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -157,6 +157,7 @@ export const ExploreChartHeader = ({ const [tags, setTags] = useState([]); useEffect(() => { + if(!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; fetchTags( { objectType: OBJECT_TYPES.CHART, diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index ccdcdc6208f75..1cf35b055bc89 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -23,7 +23,7 @@ import Button from 'src/components/Button'; import { AsyncSelect, Row, Col, AntdForm } from 'src/components'; import { SelectValue } from 'antd/lib/select'; import rison from 'rison'; -import { t, SupersetClient, styled } from '@superset-ui/core'; +import { t, SupersetClient, styled, isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; @@ -156,31 +156,33 @@ function PropertiesModal({ }[] ).map(o => o.value); } - // update tags - newTags.map((tag: TagType) => - addTag( - { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - includeTypes: false, - }, - tag.name, - () => {}, - () => {}, - ), - ); - oldTags.map((tag: TagType) => - deleteTag( - { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - }, - tag, - () => {}, - () => {}, - ), - ); - + if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)){ + // update tags + newTags.map((tag: TagType) => + addTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false, + }, + tag.name, + () => {}, + () => {}, + ), + ); + oldTags.map((tag: TagType) => + deleteTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + }, + tag, + () => {}, + () => {}, + ), + ); + } + try { const res = await SupersetClient.put({ endpoint: `/api/v1/chart/${slice.slice_id}`, @@ -217,6 +219,7 @@ function PropertiesModal({ }, [slice.slice_name]); useEffect(() => { + if(!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; try { fetchTags( { @@ -227,7 +230,7 @@ function PropertiesModal({ (tags: TagType[]) => setTags(tags), () => { /* TODO: handle error */ - return + return; }, ); } catch (error: any) { @@ -386,27 +389,29 @@ function PropertiesModal({ )} -

{t('Tags')}

- - - - {t('A list of tags that have been applied to this chart.')} - - - + {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) &&

{t('Tags')}

} + {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && + + + + {t('A list of tags that have been applied to this chart.')} + + + + }
diff --git a/superset-frontend/src/types/TagType.ts b/superset-frontend/src/types/TagType.ts index ae7fd8c523a9b..2be7d96b7c6fa 100644 --- a/superset-frontend/src/types/TagType.ts +++ b/superset-frontend/src/types/TagType.ts @@ -24,7 +24,7 @@ export interface TagType { onDelete?: any; onClick?: any; name: string; - index?: number; + index?: number | undefined; } export default TagType; From 65c927b7c50e7a50580a3987ad1317d6b4b45781 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 27 Jul 2022 14:24:19 -0400 Subject: [PATCH 22/76] fixed feature flag --- superset/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/config.py b/superset/config.py index 20aef22743461..f30ce37f575e6 100644 --- a/superset/config.py +++ b/superset/config.py @@ -388,7 +388,7 @@ def _try_json_readsha(filepath: str, length: int) -> Optional[str]: "DASHBOARD_CACHE": False, "REMOVE_SLICE_LEVEL_LABEL_COLORS": False, "SHARE_QUERIES_VIA_KV_STORE": False, - "TAGGING_SYSTEM": True, + "TAGGING_SYSTEM": False, "SQLLAB_BACKEND_PERSISTENCE": True, "LISTVIEWS_DEFAULT_CARD_VIEW": False, # When True, this flag allows display of HTML tags in Markdown components From a335a1c37b8078e6efd18deb38107887770499e7 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:29:40 -0400 Subject: [PATCH 23/76] hide tags search filter when feature flag disabled --- .../src/views/CRUD/chart/ChartList.tsx | 187 +++++++++--------- .../views/CRUD/dashboard/DashboardList.tsx | 26 +-- 2 files changed, 111 insertions(+), 102 deletions(-) diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index aad4cdb8065f0..49b58d41f614c 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -507,109 +507,114 @@ function ChartList(props: ChartListProps) { ); const filters: Filters = useMemo( - () => [ - { - Header: t('Owner'), - id: 'owners', - input: 'select', - operator: FilterOperator.relationManyMany, - unfilteredLabel: t('All'), - fetchSelects: createFetchRelated( - 'chart', - 'owners', - createErrorHandler(errMsg => - addDangerToast( - t( - 'An error occurred while fetching chart owners values: %s', - errMsg, + () => { + const filters_list = [ + { + Header: t('Owner'), + id: 'owners', + input: 'select', + operator: FilterOperator.relationManyMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'chart', + 'owners', + createErrorHandler(errMsg => + addDangerToast( + t( + 'An error occurred while fetching chart owners values: %s', + errMsg, + ), ), ), + props.user, ), - props.user, - ), - paginate: true, - }, - { - Header: t('Created by'), - id: 'created_by', - input: 'select', - operator: FilterOperator.relationOneMany, - unfilteredLabel: t('All'), - fetchSelects: createFetchRelated( - 'chart', - 'created_by', - createErrorHandler(errMsg => - addDangerToast( - t( - 'An error occurred while fetching chart created by values: %s', - errMsg, + paginate: true, + }, + { + Header: t('Created by'), + id: 'created_by', + input: 'select', + operator: FilterOperator.relationOneMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'chart', + 'created_by', + createErrorHandler(errMsg => + addDangerToast( + t( + 'An error occurred while fetching chart created by values: %s', + errMsg, + ), ), ), + props.user, ), - props.user, - ), - paginate: true, - }, - { - Header: t('Chart type'), - id: 'viz_type', - input: 'select', - operator: FilterOperator.equals, - unfilteredLabel: t('All'), - selects: registry - .keys() - .filter(k => nativeFilterGate(registry.get(k)?.behaviors || [])) - .map(k => ({ label: registry.get(k)?.name || k, value: k })) - .sort((a, b) => { - if (!a.label || !b.label) { - return 0; - } + paginate: true, + }, + { + Header: t('Chart type'), + id: 'viz_type', + input: 'select', + operator: FilterOperator.equals, + unfilteredLabel: t('All'), + selects: registry + .keys() + .filter(k => nativeFilterGate(registry.get(k)?.behaviors || [])) + .map(k => ({ label: registry.get(k)?.name || k, value: k })) + .sort((a, b) => { + if (!a.label || !b.label) { + return 0; + } - if (a.label > b.label) { - return 1; - } - if (a.label < b.label) { - return -1; - } + if (a.label > b.label) { + return 1; + } + if (a.label < b.label) { + return -1; + } - return 0; - }), - }, - { - Header: t('Dataset'), - id: 'datasource_id', - input: 'select', - operator: FilterOperator.equals, - unfilteredLabel: t('All'), - fetchSelects: createFetchDatasets, - paginate: true, - }, - ...(userId ? [favoritesFilter] : []), - { - Header: t('Certified'), - id: 'id', - urlDisplay: 'certified', - input: 'select', - operator: FilterOperator.chartIsCertified, - unfilteredLabel: t('Any'), - selects: [ - { label: t('Yes'), value: true }, - { label: t('No'), value: false }, - ], - }, - { - Header: t('Tags'), - id: 'tags', - input: 'search', - operator: FilterOperator.chartTags, - }, - { + return 0; + }), + }, + { + Header: t('Dataset'), + id: 'datasource_id', + input: 'select', + operator: FilterOperator.equals, + unfilteredLabel: t('All'), + fetchSelects: createFetchDatasets, + paginate: true, + }, + ...(userId ? [favoritesFilter] : []), + { + Header: t('Certified'), + id: 'id', + urlDisplay: 'certified', + input: 'select', + operator: FilterOperator.chartIsCertified, + unfilteredLabel: t('Any'), + selects: [ + { label: t('Yes'), value: true }, + { label: t('No'), value: false }, + ], + }, + ] as Filters; + if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { + filters_list.push({ + Header: t('Tags'), + id: 'tags', + input: 'search', + operator: FilterOperator.chartTags + }); + } + filters_list.push({ Header: t('Search'), id: 'slice_name', input: 'search', operator: FilterOperator.chartAllText, - }, - ], + }); + return filters_list; + }, [addDangerToast, favoritesFilter, props.user], ); diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 82dff379fd76f..fa489cbce6664 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -478,8 +478,8 @@ function DashboardList(props: DashboardListProps) { [], ); - const filters: Filters = useMemo( - () => [ + const filters: Filters = useMemo(() => { + const filters_list = [ { Header: t('Owner'), id: 'owners', @@ -546,19 +546,23 @@ function DashboardList(props: DashboardListProps) { { label: t('No'), value: false }, ], }, - { + ] as Filters; + if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)){ + filters_list.push({ Header: t('Tags'), id: 'tags', input: 'search', operator: FilterOperator.dashboardTags, - }, - { - Header: t('Search'), - id: 'dashboard_title', - input: 'search', - operator: FilterOperator.titleOrSlug, - }, - ], + }) + } + filters_list.push({ + Header: t('Search'), + id: 'dashboard_title', + input: 'search', + operator: FilterOperator.titleOrSlug, + }); + return filters_list; + }, [addDangerToast, favoritesFilter, props.user], ); From 885c219d0a61a70a5e16185b0085acc3eb97791c Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:49:39 -0400 Subject: [PATCH 24/76] added allow new tags in dashboard properties modal --- .../src/dashboard/components/PropertiesModal/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 79c6dda57cd33..744dd774188f8 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -431,6 +431,7 @@ const PropertiesModal = ({ Date: Wed, 27 Jul 2022 15:50:19 -0400 Subject: [PATCH 25/76] added allow new tags in dashboard properties modal --- .../src/dashboard/components/PropertiesModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 744dd774188f8..68e4ee422a754 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -431,7 +431,6 @@ const PropertiesModal = ({ Date: Fri, 29 Jul 2022 14:14:40 -0400 Subject: [PATCH 26/76] lint fixes --- .../components/PropertiesModal/index.tsx | 62 +++--- .../components/ExploreChartHeader/index.jsx | 2 +- .../components/PropertiesModal/index.tsx | 25 ++- .../src/views/CRUD/chart/ChartList.tsx | 197 +++++++++--------- .../views/CRUD/dashboard/DashboardList.tsx | 8 +- superset/config.py | 2 +- superset/tags/api.py | 11 +- 7 files changed, 159 insertions(+), 148 deletions(-) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 68e4ee422a754..dd5b9ff8e0cfd 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -347,7 +347,7 @@ const PropertiesModal = ({ updateMetadata: false, }); - if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { + if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { // update tags newTags.map((tag: TagType) => addTag( @@ -373,7 +373,7 @@ const PropertiesModal = ({ ), ); } - + const moreOnSubmitProps: { roles?: Roles } = {}; const morePutProps: { roles?: number[] } = {}; if (isFeatureEnabled(FeatureFlag.DASHBOARD_RBAC)) { @@ -557,7 +557,7 @@ const PropertiesModal = ({ }, [dashboardInfo, dashboardTitle, form]); useEffect(() => { - if(!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; + if (!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; try { fetchTags( { @@ -582,7 +582,7 @@ const PropertiesModal = ({ return; } setTags([...tags, tag]); - return setNewTags([...newTags, tag]); + setNewTags([...newTags, tag]); }); }; @@ -696,34 +696,34 @@ const PropertiesModal = ({

{t('Tags')}

- {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? - - - - + + + + +

+ {t('A list of tags that have been applied to this chart.')} +

+ + + -
-

- {t('A list of tags that have been applied to this chart.')} -

- - - - -
- : null} + + + ) : null}

diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index 6e68dd3d4a9f6..e96f5d3ec6c03 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -157,7 +157,7 @@ export const ExploreChartHeader = ({ const [tags, setTags] = useState([]); useEffect(() => { - if(!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; + if (!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; fetchTags( { objectType: OBJECT_TYPES.CHART, diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 1cf35b055bc89..e2cd7229f5161 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -23,7 +23,13 @@ import Button from 'src/components/Button'; import { AsyncSelect, Row, Col, AntdForm } from 'src/components'; import { SelectValue } from 'antd/lib/select'; import rison from 'rison'; -import { t, SupersetClient, styled, isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; +import { + t, + SupersetClient, + styled, + isFeatureEnabled, + FeatureFlag, +} from '@superset-ui/core'; import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; @@ -156,7 +162,7 @@ function PropertiesModal({ }[] ).map(o => o.value); } - if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)){ + if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { // update tags newTags.map((tag: TagType) => addTag( @@ -182,7 +188,7 @@ function PropertiesModal({ ), ); } - + try { const res = await SupersetClient.put({ endpoint: `/api/v1/chart/${slice.slice_id}`, @@ -219,7 +225,7 @@ function PropertiesModal({ }, [slice.slice_name]); useEffect(() => { - if(!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; + if (!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; try { fetchTags( { @@ -230,7 +236,6 @@ function PropertiesModal({ (tags: TagType[]) => setTags(tags), () => { /* TODO: handle error */ - return; }, ); } catch (error: any) { @@ -245,7 +250,7 @@ function PropertiesModal({ return; } setTags([...tags, tag]); - return setNewTags([...newTags, tag]); + setNewTags([...newTags, tag]); }); }; @@ -389,8 +394,10 @@ function PropertiesModal({ )} - {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) &&

{t('Tags')}

} - {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && + {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && ( +

{t('Tags')}

+ )} + {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && ( - } + )}
diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 49b58d41f614c..8249d9465c6f7 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -506,117 +506,114 @@ function ChartList(props: ChartListProps) { [], ); - const filters: Filters = useMemo( - () => { - const filters_list = [ - { - Header: t('Owner'), - id: 'owners', - input: 'select', - operator: FilterOperator.relationManyMany, - unfilteredLabel: t('All'), - fetchSelects: createFetchRelated( - 'chart', - 'owners', - createErrorHandler(errMsg => - addDangerToast( - t( - 'An error occurred while fetching chart owners values: %s', - errMsg, - ), + const filters: Filters = useMemo(() => { + const filters_list = [ + { + Header: t('Owner'), + id: 'owners', + input: 'select', + operator: FilterOperator.relationManyMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'chart', + 'owners', + createErrorHandler(errMsg => + addDangerToast( + t( + 'An error occurred while fetching chart owners values: %s', + errMsg, ), ), - props.user, ), - paginate: true, - }, - { - Header: t('Created by'), - id: 'created_by', - input: 'select', - operator: FilterOperator.relationOneMany, - unfilteredLabel: t('All'), - fetchSelects: createFetchRelated( - 'chart', - 'created_by', - createErrorHandler(errMsg => - addDangerToast( - t( - 'An error occurred while fetching chart created by values: %s', - errMsg, - ), + props.user, + ), + paginate: true, + }, + { + Header: t('Created by'), + id: 'created_by', + input: 'select', + operator: FilterOperator.relationOneMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'chart', + 'created_by', + createErrorHandler(errMsg => + addDangerToast( + t( + 'An error occurred while fetching chart created by values: %s', + errMsg, ), ), - props.user, ), - paginate: true, - }, - { - Header: t('Chart type'), - id: 'viz_type', - input: 'select', - operator: FilterOperator.equals, - unfilteredLabel: t('All'), - selects: registry - .keys() - .filter(k => nativeFilterGate(registry.get(k)?.behaviors || [])) - .map(k => ({ label: registry.get(k)?.name || k, value: k })) - .sort((a, b) => { - if (!a.label || !b.label) { - return 0; - } + props.user, + ), + paginate: true, + }, + { + Header: t('Chart type'), + id: 'viz_type', + input: 'select', + operator: FilterOperator.equals, + unfilteredLabel: t('All'), + selects: registry + .keys() + .filter(k => nativeFilterGate(registry.get(k)?.behaviors || [])) + .map(k => ({ label: registry.get(k)?.name || k, value: k })) + .sort((a, b) => { + if (!a.label || !b.label) { + return 0; + } - if (a.label > b.label) { - return 1; - } - if (a.label < b.label) { - return -1; - } + if (a.label > b.label) { + return 1; + } + if (a.label < b.label) { + return -1; + } - return 0; - }), - }, - { - Header: t('Dataset'), - id: 'datasource_id', - input: 'select', - operator: FilterOperator.equals, - unfilteredLabel: t('All'), - fetchSelects: createFetchDatasets, - paginate: true, - }, - ...(userId ? [favoritesFilter] : []), - { - Header: t('Certified'), - id: 'id', - urlDisplay: 'certified', - input: 'select', - operator: FilterOperator.chartIsCertified, - unfilteredLabel: t('Any'), - selects: [ - { label: t('Yes'), value: true }, - { label: t('No'), value: false }, - ], - }, - ] as Filters; - if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { - filters_list.push({ - Header: t('Tags'), - id: 'tags', - input: 'search', - operator: FilterOperator.chartTags - }); - } + return 0; + }), + }, + { + Header: t('Dataset'), + id: 'datasource_id', + input: 'select', + operator: FilterOperator.equals, + unfilteredLabel: t('All'), + fetchSelects: createFetchDatasets, + paginate: true, + }, + ...(userId ? [favoritesFilter] : []), + { + Header: t('Certified'), + id: 'id', + urlDisplay: 'certified', + input: 'select', + operator: FilterOperator.chartIsCertified, + unfilteredLabel: t('Any'), + selects: [ + { label: t('Yes'), value: true }, + { label: t('No'), value: false }, + ], + }, + ] as Filters; + if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { filters_list.push({ - Header: t('Search'), - id: 'slice_name', + Header: t('Tags'), + id: 'tags', input: 'search', - operator: FilterOperator.chartAllText, + operator: FilterOperator.chartTags, }); - return filters_list; - }, - [addDangerToast, favoritesFilter, props.user], - ); + } + filters_list.push({ + Header: t('Search'), + id: 'slice_name', + input: 'search', + operator: FilterOperator.chartAllText, + }); + return filters_list; + }, [addDangerToast, favoritesFilter, props.user]); const sortTypes = [ { diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index fa489cbce6664..e943ff8a1abeb 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -547,13 +547,13 @@ function DashboardList(props: DashboardListProps) { ], }, ] as Filters; - if(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)){ + if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { filters_list.push({ Header: t('Tags'), id: 'tags', input: 'search', operator: FilterOperator.dashboardTags, - }) + }); } filters_list.push({ Header: t('Search'), @@ -562,9 +562,7 @@ function DashboardList(props: DashboardListProps) { operator: FilterOperator.titleOrSlug, }); return filters_list; - }, - [addDangerToast, favoritesFilter, props.user], - ); + }, [addDangerToast, favoritesFilter, props.user]); const sortTypes = [ { diff --git a/superset/config.py b/superset/config.py index 6167bd4dae597..2fbd4f49a347a 100644 --- a/superset/config.py +++ b/superset/config.py @@ -388,7 +388,7 @@ def _try_json_readsha(filepath: str, length: int) -> Optional[str]: "DASHBOARD_CACHE": False, "REMOVE_SLICE_LEVEL_LABEL_COLORS": False, "SHARE_QUERIES_VIA_KV_STORE": False, - "TAGGING_SYSTEM": False, + "TAGGING_SYSTEM": True, "SQLLAB_BACKEND_PERSISTENCE": True, "LISTVIEWS_DEFAULT_CARD_VIEW": False, # When True, this flag allows display of HTML tags in Markdown components diff --git a/superset/tags/api.py b/superset/tags/api.py index 44fd00b41790d..ab3d3beb1ddde 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -81,7 +81,7 @@ def __repr__(self) -> str: self.appbuilder.app.config["VERSION_SHA"], ) - @expose("///", methods=["POST"]) + @expose("///", methods=["POST"]) @protect() @safe @statsd_metrics @@ -102,6 +102,15 @@ def post(self, object_type: ObjectTypes, object_id: int) -> Response: application/json: schema: $ref: '#/components/schemas/{{self.__class__.__name__}}.post' + parameters: + - in: path + schema: + type: integer + name: object_type + - in: path + schema: + type: integer + name: object_id responses: 201: description: Tag added From 5a97eb62a1e04df8b7adc559e749244e3f79c058 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:25:42 -0400 Subject: [PATCH 27/76] added tags api integration tests --- tests/integration_tests/tags/__init__.py | 16 +++ tests/integration_tests/tags/api_tests.py | 126 ++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 tests/integration_tests/tags/__init__.py create mode 100644 tests/integration_tests/tags/api_tests.py diff --git a/tests/integration_tests/tags/__init__.py b/tests/integration_tests/tags/__init__.py new file mode 100644 index 0000000000000..13a83393a9124 --- /dev/null +++ b/tests/integration_tests/tags/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py new file mode 100644 index 0000000000000..d3ccdc3302519 --- /dev/null +++ b/tests/integration_tests/tags/api_tests.py @@ -0,0 +1,126 @@ +# 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. +# isort:skip_file +"""Unit tests for Superset""" +from datetime import datetime, timedelta +import json +import random +import string + +import pytest +import prison +from sqlalchemy.sql import func + +import tests.integration_tests.test_app +from superset import db, security_manager +from superset.common.db_query_status import QueryStatus +from superset.models.core import Database +from superset.utils.database import get_example_database, get_main_database +from superset.models.sql_lab import Tag + +from tests.integration_tests.base_tests import SupersetTestCase + +TAGS_FIXTURE_COUNT = 10 + +class TestTagApi(SupersetTestCase): + def insert_tag( + self, + name: str, + tag_type: str, + ) -> Tag: + tag = Tag( + name=name, + type=tag_type, + ) + db.session.add(tag) + db.session.commit() + return tag + + @pytest.fixture() + def create_tags(self): + with self.create_app().app_context(): + tags = [] + for cx in range(TAGS_FIXTURE_COUNT): + tags.append( + self.insert_tag( + name=f"example_tag_{cx}", + tag_type='custom', + ) + ) + + yield tags + + # rollback changes + for tag in tags: + db.session.delete(tag) + db.session.commit() + + def test_get_tag(self): + """ + Query API: Test get query + """ + tag = self.insert_tag( + name="test get tag", + tag_type='custom', + ) + self.login(username="admin") + uri = f"api/v1/tag/{tag.id}" + rv = self.client.get(uri) + self.assertEqual(rv.status_code, 200) + expected_result = { + "id": tag.id, + "name": "test get tag", + "type": 'custom' + } + data = json.loads(rv.data.decode("utf-8")) + for key, value in data["result"].items(): + self.assertEqual(value, expected_result[key]) + # rollback changes + db.session.delete(tag) + db.session.commit() + + def test_get_tag_not_found(self): + """ + Query API: Test get query not found + """ + tag = self.insert_tag(name="test tag", tag_type='custom') + max_id = db.session.query(func.max(Tag.id)).scalar() + self.login(username="admin") + uri = f"api/v1/tag/{max_id + 1}" + rv = self.client.get(uri) + self.assertEqual(rv.status_code, 404) + # cleanup + db.session.delete(tag) + db.session.commit() + + @pytest.mark.usefixtures("create_tags") + def test_get_list_tag(self): + """ + Query API: Test get list query + """ + self.login(username="admin") + uri = "api/v1/tag/" + rv = self.client.get(uri) + self.assertEqual(rv.status_code, 200) + data = json.loads(rv.data.decode("utf-8")) + assert data["count"] == TAGS_FIXTURE_COUNT + # check expected columns + assert sorted(list(data["result"][0].keys())) == [ + "id", + "name", + "type", + ] From aebbf166df32518c6ae60879fb87367e75774a64 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 9 Aug 2022 08:27:25 -0400 Subject: [PATCH 28/76] removed tags from headers for dashboards and charts --- .../src/dashboard/components/Header/index.jsx | 12 ------- .../components/ExploreChartHeader/index.jsx | 31 ------------------- 2 files changed, 43 deletions(-) diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index 6a1d62bc6b62b..21089d5a231d7 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -33,7 +33,6 @@ import Button from 'src/components/Button'; import { AntdButton } from 'src/components/'; import { findPermission } from 'src/utils/findPermission'; import { Tooltip } from 'src/components/Tooltip'; -import ObjectTags from 'src/components/ObjectTags'; import { safeStringify } from 'src/utils/safeStringify'; import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown'; import PublishedStatus from 'src/dashboard/components/PublishedStatus'; @@ -52,11 +51,8 @@ import setPeriodicRunner, { import { options as PeriodicRefreshOptions } from 'src/dashboard/components/RefreshIntervalModal'; import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; -import { OBJECT_TYPES } from 'src/tags'; import { DashboardEmbedModal } from '../DashboardEmbedControls'; -const MAX_TAGS = 3; - const propTypes = { addSuccessToast: PropTypes.func.isRequired, addDangerToast: PropTypes.func.isRequired, @@ -525,14 +521,6 @@ class Header extends React.PureComponent { visible={!editMode} /> ), - isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && ( - - ), ]} rightPanelAdditionalItems={
diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index e96f5d3ec6c03..a043e001bea8b 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -35,13 +35,9 @@ import Icons from 'src/components/Icons'; import PropertiesModal from 'src/explore/components/PropertiesModal'; import { sliceUpdated } from 'src/explore/actions/exploreActions'; import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions'; -import { fetchTags, OBJECT_TYPES } from 'src/tags'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; -import { TagsList } from 'src/components/Tags'; import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu'; -const MAX_TAGS = 3; - const propTypes = { actions: PropTypes.object.isRequired, canOverwrite: PropTypes.bool.isRequired, @@ -154,23 +150,6 @@ export const ExploreChartHeader = ({ const oldSliceName = slice?.slice_name; - const [tags, setTags] = useState([]); - - useEffect(() => { - if (!isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) return; - fetchTags( - { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - includeTypes: false, - }, - tags => setTags(tags), - () => { - /** handle error */ - }, - ); - }, [slice]); - return ( <> ) : null, - isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( - - tag.type - ? tag.type === 1 || tag.type === 'TagTypes.custom' - : true, - )} - maxTags={MAX_TAGS} - /> - ) : null, ]} rightPanelAdditionalItems={ Date: Tue, 9 Aug 2022 11:58:01 -0400 Subject: [PATCH 29/76] Tags display in multiselect in properties modal --- .../components/PropertiesModal/index.tsx | 106 ++++++++++-------- .../components/PropertiesModal/index.tsx | 106 +++++++++++------- 2 files changed, 125 insertions(+), 87 deletions(-) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index dd5b9ff8e0cfd..e487543e6136a 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Input } from 'src/components/Input'; import { FormItem } from 'src/components/Form'; import jsonStringify from 'json-stringify-pretty-compact'; @@ -105,10 +105,19 @@ const PropertiesModal = ({ const [roles, setRoles] = useState([]); const saveLabel = onlyApply ? t('Apply') : t('Save'); const [tags, setTags] = useState([]); - const [newTags, setNewTags] = useState([]); - const [oldTags, setOldTags] = useState([]); const categoricalSchemeRegistry = getCategoricalSchemeRegistry(); + const tagsAsSelectValues = useMemo(() => { + const selectTags = tags.map((tag) => { + return { + value:tag.name, + label:tag.name + } + }); + return selectTags; + }, [tags.length]) + + const handleErrorResponse = async (response: Response) => { const { error, statusText, message } = await getClientErrorObject(response); let errorText = error || statusText || t('An error has occurred'); @@ -349,29 +358,21 @@ const PropertiesModal = ({ if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { // update tags - newTags.map((tag: TagType) => - addTag( + try { + fetchTags( { objectType: OBJECT_TYPES.DASHBOARD, objectId: dashboardId, includeTypes: false, }, - tag.name, - () => {}, - () => {}, - ), - ); - oldTags.map((tag: TagType) => - deleteTag( - { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: dashboardId, + (currentTags: TagType[]) => updateTags(currentTags, tags), + () => { + /* TODO: handle error */ }, - tag, - () => {}, - () => {}, - ), - ); + ); + } catch (error: any) { + console.log(error); + } } const moreOnSubmitProps: { roles?: Roles } = {}; @@ -575,24 +576,47 @@ const PropertiesModal = ({ } }, [dashboardId]); - const handleAddTag = (values: { label: string; value: number }[]) => { - values.map((value: { label: string; value: number }) => { - const tag = { name: value.label }; - if (tags.some(t => t.name === tag.name)) { - return; + const updateTags = (oldTags: TagType[], newTags: TagType[]) => { + // update the tags for this object + // add tags that are in new tags, but not in old tags + newTags.map((tag: TagType) => { + if (!oldTags.some(t => t.name === tag.name)) { + addTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + includeTypes: false, + }, + tag.name, + () => {}, + () => {}, + ); } - setTags([...tags, tag]); - setNewTags([...newTags, tag]); }); - }; + // delete tags that are in old tags, but not in new tags + oldTags.map((tag: TagType) => { + if (!newTags.some(t => t.name === tag.name)) { + deleteTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + }, + tag, + () => {}, + () => {}, + ) + } + }); + } - const handleDeleteTag = (tagIndex: number) => { - setOldTags([...oldTags, tags[tagIndex]]); - setTags([ - ...tags.slice(0, tagIndex), - ...tags.slice(tagIndex + 1, tags.length), - ]); - }; + const handleChangeTags = (values: { label: string; value: number }[]) => { + // triggered whenever a new tag is selected or a tag was deselected + // on new tag selected, add the tag + + const uniqueTags = [...new Set(values.map((v => v.label)))]; + setTags([...uniqueTags.map(t => ({name: t}))]) + return; + } return ( @@ -714,14 +738,6 @@ const PropertiesModal = ({ {t('A list of tags that have been applied to this chart.')}

- - - ) : null} diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index e2cd7229f5161..7458754755891 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -74,8 +74,16 @@ function PropertiesModal({ ); const [tags, setTags] = useState([]); - const [newTags, setNewTags] = useState([]); - const [oldTags, setOldTags] = useState([]); + + const tagsAsSelectValues = useMemo(() => { + const selectTags = tags.map((tag) => { + return { + value:tag.name, + label:tag.name + } + }); + return selectTags; + }, [tags.length]) function showError({ error, statusText, message }: any) { let errorText = error || statusText || t('An error has occurred'); @@ -164,29 +172,21 @@ function PropertiesModal({ } if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { // update tags - newTags.map((tag: TagType) => - addTag( + try { + fetchTags( { objectType: OBJECT_TYPES.CHART, objectId: slice.slice_id, includeTypes: false, }, - tag.name, - () => {}, - () => {}, - ), - ); - oldTags.map((tag: TagType) => - deleteTag( - { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, + (currentTags: TagType[]) => updateTags(currentTags, tags), + () => { + /* TODO: handle error */ }, - tag, - () => {}, - () => {}, - ), - ); + ); + } catch (error: any) { + console.log(error); + } } try { @@ -243,24 +243,51 @@ function PropertiesModal({ } }, [slice.slice_id]); - const handleAddTag = (values: { label: string; value: number }[]) => { - values.map((value: { label: string; value: number }) => { - const tag = { name: value.label }; - if (tags.some(t => t.name === tag.name)) { - return; + const updateTags = (oldTags: TagType[], newTags: TagType[]) => { + // update the tags for this object + // add tags that are in new tags, but not in old tags + newTags.map((tag: TagType) => { + if (!oldTags.some(t => t.name === tag.name)) { + addTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false, + }, + tag.name, + () => {}, + () => {}, + ); } - setTags([...tags, tag]); - setNewTags([...newTags, tag]); }); - }; + // delete tags that are in old tags, but not in new tags + oldTags.map((tag: TagType) => { + if (!newTags.some(t => t.name === tag.name)) { + deleteTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + }, + tag, + () => {}, + () => {}, + ) + } + }); + } - const handleDeleteTag = (tagIndex: number) => { - setOldTags([...oldTags, tags[tagIndex]]); - setTags([ - ...tags.slice(0, tagIndex), - ...tags.slice(tagIndex + 1, tags.length), - ]); - }; + const handleChangeTags = (values: { label: string; value: number }[]) => { + // triggered whenever a new tag is selected or a tag was deselected + // on new tag selected, add the tag + + const uniqueTags = [...new Set(values.map((v => v.label)))]; + setTags([...uniqueTags.map(t => ({name: t}))]) + return; + } + + const handleClearTags = () => { + setTags([]); + } return ( {t('A list of tags that have been applied to this chart.')} - )} From 63be0302e9abd741b90376fadf56ca797481bf76 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:07:06 -0400 Subject: [PATCH 30/76] CRUD tag search is now a select --- superset-frontend/src/components/Tags/TagsList.tsx | 4 ++-- superset-frontend/src/views/CRUD/chart/ChartList.tsx | 6 +++++- .../src/views/CRUD/dashboard/DashboardList.tsx | 7 +++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index 4748f55cda4ff..4f763299da9d1 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -30,8 +30,8 @@ export type TagsListProps = { * Only applies when editable is true * Callback for when a tag is deleted */ - onDelete: ((index: number) => void) | undefined; - maxTags: number | undefined; + onDelete?: ((index: number) => void) | undefined; + maxTags?: number | undefined; }; const TagsDiv = styled.div` diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 8249d9465c6f7..7b73e7b25c06e 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -64,6 +64,8 @@ import setupPlugins from 'src/setup/setupPlugins'; import InfoTooltip from 'src/components/InfoTooltip'; import CertifiedBadge from 'src/components/CertifiedBadge'; import ChartCard from './ChartCard'; +import { OBJECT_TYPES } from 'src/tags'; +import { loadTags } from 'src/components/ObjectTags'; const FlexRowContainer = styled.div` align-items: center; @@ -602,8 +604,10 @@ function ChartList(props: ChartListProps) { filters_list.push({ Header: t('Tags'), id: 'tags', - input: 'search', + input: 'select', operator: FilterOperator.chartTags, + unfilteredLabel: t('All'), + fetchSelects: loadTags }); } filters_list.push({ diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index e943ff8a1abeb..e2e56c08b5441 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -53,6 +53,7 @@ import Dashboard from 'src/dashboard/containers/Dashboard'; import CertifiedBadge from 'src/components/CertifiedBadge'; import DashboardCard from './DashboardCard'; import { DashboardStatus } from './types'; +import { loadTags } from 'src/components/ObjectTags'; const PAGE_SIZE = 25; const PASSWORDS_NEEDED_MESSAGE = t( @@ -551,8 +552,10 @@ function DashboardList(props: DashboardListProps) { filters_list.push({ Header: t('Tags'), id: 'tags', - input: 'search', - operator: FilterOperator.dashboardTags, + input: 'select', + operator: FilterOperator.chartTags, + unfilteredLabel: t('All'), + fetchSelects: loadTags }); } filters_list.push({ From f794b69748d8c8398e7072cee4dad38660aa83df Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 30 Aug 2022 08:54:13 -0400 Subject: [PATCH 31/76] added styles and removed comments --- .../legacy-preset-chart-deckgl/package.json | 1 - .../src/components/ObjectTags/ObjectTags.css | 149 ----------------- .../src/components/ObjectTags/index.tsx | 1 - .../src/components/ObjectTags/styles.ts | 155 ++++++++++++++++++ superset-frontend/src/components/Tags/Tag.tsx | 12 +- .../src/components/Tags/TagsList.test.tsx | 1 - .../components/PropertiesModal/index.tsx | 2 +- .../components/ExploreChartHeader/index.jsx | 5 - .../src/views/CRUD/tags/TagsTable.tsx | 2 +- 9 files changed, 161 insertions(+), 167 deletions(-) delete mode 100644 superset-frontend/src/components/ObjectTags/ObjectTags.css create mode 100644 superset-frontend/src/components/ObjectTags/styles.ts diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json index 7278bb705cbf9..7b11af60566e5 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json @@ -48,7 +48,6 @@ "react-dom": "^16.13.1", "react-map-gl": "^4.0.10", "mapbox-gl": "*" - }, "publishConfig": { "access": "public" diff --git a/superset-frontend/src/components/ObjectTags/ObjectTags.css b/superset-frontend/src/components/ObjectTags/ObjectTags.css deleted file mode 100644 index 069f247179532..0000000000000 --- a/superset-frontend/src/components/ObjectTags/ObjectTags.css +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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. - */ -.ant-tag { - color: black; -} - -.react-tags { - position: relative; - display: inline-block; - padding: 1px 0 0 1px; - margin: 0 10px; - border: 0px solid #f5f5f5; - border-radius: 1px; - - /* shared font styles */ - font-size: 12px; - line-height: 1.2; - - /* clicking anywhere will focus the input */ - cursor: text; -} - -.react-tags__selected { - display: inline; -} - -.react-tags__selected-tag { - display: inline-block; - box-sizing: border-box; - margin: 0; - padding: 6px 8px; - border: 0px solid #f5f5f5; - border-radius: 2px; - background: #f1f1f1; - - /* match the font styles */ - font-size: inherit; - line-height: inherit; -} - -.react-tags__search { - display: inline-block; - - /* new tag border layout */ - border: 1px dashed #d9d9d9; - - /* match tag layout */ - line-height: 20px; - margin-bottom: 0; - padding: 0 7px; - - /* prevent autoresize overflowing the container */ - max-width: 100%; -} - -.react-tags__search:focus-within { - border: 1px solid #000000; -} - -@media screen and (min-width: 30em) { - .react-tags__search { - /* this will become the offsetParent for suggestions */ - position: relative; - } -} - -.react-tags__search input { - max-width: 150%; - - /* remove styles and layout from this element */ - margin: 0; - margin-left: 0; - padding: 0; - border: 0; - outline: none; - - /* match the font styles */ - font-size: inherit; - line-height: inherit; -} - -.react-tags__search input::-ms-clear { - display: none; -} - -.react-tags__suggestions { - position: absolute; - top: 100%; - left: 0; - width: 100%; - z-index: 9999; -} - -@media screen and (min-width: 30em) { - .react-tags__suggestions { - width: 240px; - } -} - -.react-tags__suggestions ul { - margin: 4px -1px; - padding: 0; - list-style: none; - background: white; - border: 1px solid #d1d1d1; - border-radius: 2px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); -} - -.react-tags__suggestions li { - border-bottom: 1px solid #ddd; - padding: 6px 8px; -} - -.react-tags__suggestions li mark { - text-decoration: underline; - background: none; - font-weight: 600; -} - -.react-tags__suggestions li:hover { - cursor: pointer; - background: #eee; -} - -.react-tags__suggestions li.is-active { - background: #b7cfe0; -} - -.react-tags__suggestions li.is-disabled { - opacity: 0.5; - cursor: auto; -} diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 035281da6adff..4ae38022dbfca 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -93,7 +93,6 @@ export const loadTags = async ( return cachedSupersetGet({ endpoint: `/api/v1/tag/?q=${query}`, - // endpoint: `/api/v1/tags/?q=${query}`, }) .then(response => { const data: { diff --git a/superset-frontend/src/components/ObjectTags/styles.ts b/superset-frontend/src/components/ObjectTags/styles.ts new file mode 100644 index 0000000000000..18fea1ca9a9e9 --- /dev/null +++ b/superset-frontend/src/components/ObjectTags/styles.ts @@ -0,0 +1,155 @@ +/** + * 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 { css, SupersetTheme } from '@superset-ui/core'; + + export const objectTagsStyles = (theme: SupersetTheme) => css` + .ant-tag { + color: ${theme.colors.grayscale.dark2}; + } + + .react-tags { + position: relative; + display: inline-block; + padding: 1px 0 0 1px; + margin: 0 ${theme.gridUnit * 2.5}px; + border: 0px solid #f5f5f5; + border-radius: 1px; + + /* shared font styles */ + font-size: ${theme.gridUnit * 3}px; + line-height: 1.2; + + /* clicking anywhere will focus the input */ + cursor: text; + } + + .react-tags__selected { + display: inline; + } + + .react-tags__selected-tag { + display: inline-block; + box-sizing: border-box; + margin: 0; + padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 2}px; + border: 0px solid #f5f5f5; + border-radius: ${theme.borderRadius}px; + background: #f1f1f1; + + /* match the font styles */ + font-size: inherit; + line-height: inherit; + } + + .react-tags__search { + display: inline-block; + + /* new tag border layout */ + border: 1px dashed #d9d9d9; + + /* match tag layout */ + line-height: ${theme.gridUnit * 5}px; + margin-bottom: 0; + padding: 0 ${theme.gridUnit * 1.75}px; + + /* prevent autoresize overflowing the container */ + max-width: 100%; + } + + .react-tags__search:focus-within { + border: 1px solid ${theme.colors.grayscale.dark2}; + } + + @media screen and (min-width: ${theme.gridUnit * 7.5}em) { + .react-tags__search { + /* this will become the offsetParent for suggestions */ + position: relative; + } + } + + .react-tags__search input { + max-width: 150%; + + /* remove styles and layout from this element */ + margin: 0; + margin-left: 0; + padding: 0; + border: 0; + outline: none; + + /* match the font styles */ + font-size: inherit; + line-height: inherit; + } + + .react-tags__search input::-ms-clear { + display: none; + } + + .react-tags__suggestions { + position: absolute; + top: 100%; + left: 0; + width: 100%; + z-index: ${theme.zIndex.max}; + } + + @media screen and (min-width: ${theme.gridUnit * 7.5}em) { + .react-tags__suggestions { + width: ${theme.gridUnit * 60}px; + } + } + + .react-tags__suggestions ul { + margin: 4px -1px; + padding: 0; + list-style: none; + background: white; + border: 1px solid #d1d1d1; + border-radius: 2px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); + } + + .react-tags__suggestions li { + border-bottom: 1px solid #ddd; + padding: ${ theme.gridUnit * 1.5}px ${theme.gridUnit * 2}px; + } + + .react-tags__suggestions li mark { + text-decoration: underline; + background: none; + font-weight: ${theme.typography.weights.bold}; + } + + .react-tags__suggestions li:hover { + cursor: pointer; + background: #eee; + } + + .react-tags__suggestions li.is-active { + background: #b7cfe0; + } + + .react-tags__suggestions li.is-disabled { + opacity: calc(${theme.opacity.mediumHeavy}); + cursor: auto; + } + `; + \ No newline at end of file diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index 0dbd62edbdda2..f75b19f0db020 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -23,14 +23,10 @@ import AntdTag from 'antd/lib/tag'; import React, { useMemo } from 'react'; import { Tooltip } from 'src/components/Tooltip'; -const customTagStyler = (theme: SupersetTheme) => ` - margin-top: ${theme.gridUnit * 1}px; - margin-bottom: ${theme.gridUnit * 1}px; - font-size: ${theme.typography.sizes.s}px; -`; - const StyledTag = styled(AntdTag)` - ${({ theme }) => customTagStyler(theme)}} + margin-top: ${({ theme }) => theme.gridUnit}px; + margin-bottom: ${({ theme }) => theme.gridUnit}px; + font-size: ${({ theme }) => theme.typography.sizes.s}px; `; const Tag = ({ @@ -59,7 +55,7 @@ const Tag = ({ ) : ( {id ? ( - + {isLongTag ? `${name.slice(0, 20)}...` : name} ) : isLongTag ? ( diff --git a/superset-frontend/src/components/Tags/TagsList.test.tsx b/superset-frontend/src/components/Tags/TagsList.test.tsx index d9817e6458134..4e2ffd2567d5d 100644 --- a/superset-frontend/src/components/Tags/TagsList.test.tsx +++ b/superset-frontend/src/components/Tags/TagsList.test.tsx @@ -54,7 +54,6 @@ const findAllTags = () => screen.getAllByRole('link')! as HTMLElement[]; test('should render', () => { const { container } = render(); expect(container).toBeInTheDocument(); - // console.log(screen.getAllByRole("tag")); }); test('should render 5 elements', () => { diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index e487543e6136a..1ae440a4d695f 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -717,7 +717,7 @@ const PropertiesModal = ({ -

{t('Tags')}

+

{t('Tags')}

{isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index a043e001bea8b..cf35e79e7d052 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -61,11 +61,6 @@ const saveButtonStyles = theme => css` } `; -// const StyledButtons = styled.span` -// display: flex; -// align-items: center; -// `; - export const ExploreChartHeader = ({ dashboardId, slice, diff --git a/superset-frontend/src/views/CRUD/tags/TagsTable.tsx b/superset-frontend/src/views/CRUD/tags/TagsTable.tsx index 1afb4d29715c9..ae8fc44674f0b 100644 --- a/superset-frontend/src/views/CRUD/tags/TagsTable.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagsTable.tsx @@ -85,7 +85,7 @@ export default function TagsTable({ search = '' }: TagsTableProps) { const data = objects[type].map((o: TaggedObject) => ({ [type]: {o.name}, // eslint-disable-next-line react/no-danger - creator:
, + creator:
o.creator
, modified: moment.utc(o.changed_on).fromNow(), })); return ( From 10a5cefbca9b439187e7f361ef272a5b65d334ac Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 31 Aug 2022 09:00:14 -0400 Subject: [PATCH 32/76] added missing error handling --- .../src/components/ObjectTags/index.tsx | 14 ++++++++------ .../dashboard/components/PropertiesModal/index.tsx | 6 +++--- .../explore/components/PropertiesModal/index.tsx | 8 ++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 4ae38022dbfca..88dfe69ba58e7 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -21,7 +21,7 @@ import React, { useEffect, useState } from 'react'; import { styled, SupersetClient, t } from '@superset-ui/core'; import Tag from 'src/types/TagType'; -import './ObjectTags.css'; +import { objectTagsStyles } from './styles'; import { TagsList } from 'src/components/Tags'; import rison from 'rison'; import { cacheWrapper } from 'src/utils/cacheWrapper'; @@ -128,11 +128,11 @@ export const ObjectTags = ({ { objectType, objectId, includeTypes }, (tags: Tag[]) => setTags(tags), () => { - /* TODO: handle error */ + throw new Error(t('An Error occured when fetching tags')); }, ); } catch (error: any) { - console.log(error); + throw new Error(t('An Error occured when fetching tags')); } }, [objectType, objectId, includeTypes]); @@ -141,8 +141,8 @@ export const ObjectTags = ({ { objectType, objectId }, tags[tagIndex], () => setTags(tags.filter((_, i) => i !== tagIndex)), - () => { - /* TODO: handle error */ + (error) => { + throw new Error(t('An Error occured when deleting the tag')); }, ); onChange?.(tags); @@ -150,7 +150,9 @@ export const ObjectTags = ({ return ( - + updateTags(currentTags, tags), - () => { - /* TODO: handle error */ + (error) => { + handleErrorResponse(error); }, ); } catch (error: any) { @@ -568,7 +568,7 @@ const PropertiesModal = ({ }, (tags: TagType[]) => setTags(tags), () => { - /* TODO: handle error */ + handleErrorResponse(error); }, ); } catch (error: any) { diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 7458754755891..2cf2628e01510 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -180,8 +180,8 @@ function PropertiesModal({ includeTypes: false, }, (currentTags: TagType[]) => updateTags(currentTags, tags), - () => { - /* TODO: handle error */ + (error) => { + showError(error) }, ); } catch (error: any) { @@ -234,8 +234,8 @@ function PropertiesModal({ includeTypes: false, }, (tags: TagType[]) => setTags(tags), - () => { - /* TODO: handle error */ + (error) => { + showError(error); }, ); } catch (error: any) { From 4f03a3f0139db7e4342da5580ba8bb8d1456e0e5 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 15 Sep 2022 09:24:05 -0400 Subject: [PATCH 33/76] changed tags view to all entities view --- superset-frontend/src/tags.ts | 10 +- .../Tags.tsx => allentities/AllEntities.tsx} | 32 +- .../AllEntitiesTable.tsx} | 44 +- superset-frontend/src/views/routes.tsx | 8 +- superset/initialization/__init__.py | 11 +- superset/views/all_entities.py | 255 +++++++++ superset/views/tags.py | 510 +++++++++--------- 7 files changed, 565 insertions(+), 305 deletions(-) rename superset-frontend/src/views/CRUD/{tags/Tags.tsx => allentities/AllEntities.tsx} (78%) rename superset-frontend/src/views/CRUD/{tags/TagsTable.tsx => allentities/AllEntitiesTable.tsx} (78%) create mode 100644 superset/views/all_entities.py diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index 16d0d1c14a776..e0f1fd5b61792 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -37,7 +37,7 @@ export function fetchTags( if (objectType === undefined || objectId === undefined) { throw new Error('Need to specify objectType and objectId'); } - SupersetClient.get({ endpoint: `/tagview/tags/${objectType}/${objectId}/` }) + SupersetClient.get({ endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/` }) .then(({ json }) => callback( json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), @@ -51,7 +51,7 @@ export function fetchSuggestions( callback: (json: JsonObject) => void, error: (response: Response) => void, ) { - SupersetClient.get({ endpoint: '/tagview/tags/suggestions/' }) + SupersetClient.get({ endpoint: '/taggedobjectview/tags/suggestions/' }) .then(({ json }) => callback( json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), @@ -70,7 +70,7 @@ export function deleteTag( throw new Error('Need to specify objectType and objectId'); } SupersetClient.delete({ - endpoint: `/tagview/tags/${objectType}/${objectId}/`, + endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/`, body: JSON.stringify([tag.name]), parseMethod: 'text', }) @@ -95,7 +95,7 @@ export function addTag( return; } SupersetClient.post({ - endpoint: `/tagview/tags/${objectType}/${objectId}/`, + endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/`, body: JSON.stringify([tag]), parseMethod: 'text', }) @@ -108,7 +108,7 @@ export function fetchObjects( callback: (json: JsonObject) => void, error: (response: Response) => void, ) { - let url = `/tagview/tagged_objects/?tags=${tags}`; + let url = `/taggedobjectview/tagged_objects/?tags=${tags}`; if (types) { url += `&types=${types}`; } diff --git a/superset-frontend/src/views/CRUD/tags/Tags.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx similarity index 78% rename from superset-frontend/src/views/CRUD/tags/Tags.tsx rename to superset-frontend/src/views/CRUD/allentities/AllEntities.tsx index b5fba5a1b2b78..fda791ff9a654 100644 --- a/superset-frontend/src/views/CRUD/tags/Tags.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx @@ -16,16 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { styled } from '@superset-ui/core'; -import Tag from 'src/types/TagType'; +import Tag, { TagType } from 'src/types/TagType'; import { StringParam, useQueryParam } from 'use-query-params'; import withToasts from 'src/components/MessageToasts/withToasts'; import SelectControl from 'src/explore/components/controls/SelectControl'; import { fetchSuggestions } from 'src/tags'; -import TagsTable from './TagsTable'; +import AllEntitiesTable from './AllEntitiesTable'; +import { loadTags } from 'src/components/ObjectTags'; +import { AsyncSelect, Select } from 'src/components'; +import { LabeledValue } from 'antd/lib/select'; -const TagsContainer = styled.div` +const AllEntitiesContainer = styled.div` background-color: ${({ theme }) => theme.colors.grayscale.light4}; .select-control { margin-left: ${({ theme }) => theme.gridUnit * 4}px; @@ -40,7 +43,7 @@ const TagsContainer = styled.div` } `; -const TagsNav = styled.div` +const AllEntitiesNav = styled.div` height: 50px; background-color: ${({ theme }) => theme.colors.grayscale.light5}; margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; @@ -50,7 +53,8 @@ const TagsNav = styled.div` } `; -function Tags() { +function AllEntities() { + const [tagSuggestions, setTagSuggestions] = useState(); const [tagsQuery, setTagsQuery] = useQueryParam('tags', StringParam); @@ -73,12 +77,12 @@ function Tags() { }; return ( - - - Tags - + + + All Entities +
-
Search tags
+
search by tags
- -
+ + ); } -export default withToasts(Tags); +export default withToasts(AllEntities); diff --git a/superset-frontend/src/views/CRUD/tags/TagsTable.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx similarity index 78% rename from superset-frontend/src/views/CRUD/tags/TagsTable.tsx rename to superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx index ae8fc44674f0b..ef9514ec7ffd7 100644 --- a/superset-frontend/src/views/CRUD/tags/TagsTable.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx @@ -20,10 +20,12 @@ import React, { useState, useEffect } from 'react'; import moment from 'moment'; import { t, styled } from '@superset-ui/core'; import TableView, { EmptyWrapperType } from 'src/components/TableView'; -import { fetchObjects } from '../../../tags'; +import { fetchObjects, fetchTags } from '../../../tags'; import Loading from '../../../components/Loading'; +import { Tag, TagsList } from 'src/components/Tags'; +import { loadTags } from 'src/components/ObjectTags'; -const TagsTableContainer = styled.div` +const AllEntitiesTableContainer = styled.div` text-align: left; border-radius: ${({ theme }) => theme.gridUnit * 1}px 0; margin: 0 ${({ theme }) => theme.gridUnit * 4}px; @@ -51,11 +53,11 @@ interface TaggedObjects { query: TaggedObject[]; } -interface TagsTableProps { +interface AllEntitiesTableProps { search?: string; } -export default function TagsTable({ search = '' }: TagsTableProps) { +export default function AllEntitiesTable({ search = '' }: AllEntitiesTableProps) { const [objects, setObjects] = useState({ dashboard: [], chart: [], @@ -63,22 +65,20 @@ export default function TagsTable({ search = '' }: TagsTableProps) { }); useEffect(() => { - const fetchResults = (search: string) => { - fetchObjects( - { tags: search, types: null }, - (data: TaggedObject[]) => { - const objects = { dashboard: [], chart: [], query: [] }; - data.forEach(object => { - objects[object.type].push(object); - }); - setObjects(objects); - }, - (error: Response) => { - console.log(error.json()); - }, - ); - }; - fetchResults(search); + fetchObjects( + { tags: search, types: null }, + (data: TaggedObject[]) => { + const objects = { dashboard: [], chart: [], query: [] }; + data.forEach(object => { + objects[object.type].push(object); + }); + setObjects(objects); + }, + (error: Response) => { + console.log(error.json()); + }, + ); + }, [search]); const renderTable = (type: any) => { @@ -108,7 +108,7 @@ export default function TagsTable({ search = '' }: TagsTableProps) { if (objects) { return ( - +

{t('Dashboards')}

{renderTable('dashboard')}
@@ -117,7 +117,7 @@ export default function TagsTable({ search = '' }: TagsTableProps) {

{t('Queries')}

{renderTable('query')} -
+ ); } return ; diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx index e76f7f0aea23f..2ab6fc6c48180 100644 --- a/superset-frontend/src/views/routes.tsx +++ b/superset-frontend/src/views/routes.tsx @@ -103,8 +103,8 @@ const SavedQueryList = lazy( /* webpackChunkName: "SavedQueryList" */ 'src/views/CRUD/data/savedquery/SavedQueryList' ), ); -const TagsPage = lazy( - () => import(/* webpackChunkName: "Tags" */ 'src/views/CRUD/tags/Tags'), +const AllEntitiesPage = lazy( + () => import(/* webpackChunkName: "AllEntities" */ 'src/views/CRUD/allentities/AllEntities'), ); type Routes = { @@ -197,8 +197,8 @@ export const routes: Routes = [ if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { routes.push({ - path: '/superset/tags/', - Component: TagsPage, + path: '/superset/all_entities/', + Component: AllEntitiesPage, }); } diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 36915d64cc5af..d596ef419d466 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -184,8 +184,9 @@ def init_views(self) -> None: TableSchemaView, TabStateView, ) - from superset.views.tags import TagModelView, TagView from superset.views.users.api import CurrentUserRestApi + from superset.views.all_entities import TaggedObjectsModelView, TaggedObjectView + # # Setup API views @@ -307,7 +308,7 @@ def init_views(self) -> None: appbuilder.add_view_no_menu(TableModelView) appbuilder.add_view_no_menu(TableSchemaView) appbuilder.add_view_no_menu(TabStateView) - appbuilder.add_view_no_menu(TagView) + appbuilder.add_view_no_menu(TaggedObjectView) appbuilder.add_view_no_menu(ReportView) # @@ -369,9 +370,9 @@ def init_views(self) -> None: ) appbuilder.add_separator("Data") appbuilder.add_view( - TagModelView, - "Tags", - label=__("Tags"), + TaggedObjectsModelView, + "All Entities", + label=__("All Entities"), icon="", category="", category_icon="", diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py new file mode 100644 index 0000000000000..0c57da097284b --- /dev/null +++ b/superset/views/all_entities.py @@ -0,0 +1,255 @@ +# 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. +from __future__ import absolute_import, division, print_function, unicode_literals + +from typing import Any, Dict, List + +import simplejson as json +from flask import request, Response +from flask_appbuilder import expose +from flask_appbuilder.hooks import before_request +from flask_appbuilder.models.sqla.interface import SQLAInterface +from flask_appbuilder.security.decorators import has_access, has_access_api +from jinja2.sandbox import SandboxedEnvironment +from sqlalchemy import and_, func +from werkzeug.exceptions import NotFound + +from superset import db, is_feature_enabled, utils +from superset.jinja_context import ExtraCache +from superset.models.dashboard import Dashboard +from superset.models.slice import Slice +from superset.models.sql_lab import SavedQuery +from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.superset_typing import FlaskResponse +from superset.views.base import SupersetModelView + +from .base import BaseSupersetView, json_success + + +def process_template(content: str) -> str: + env = SandboxedEnvironment() + template = env.from_string(content) + context = { + "current_user_id": ExtraCache.current_user_id, + "current_username": ExtraCache.current_username, + } + return template.render(context) + +class TaggedObjectsModelView(SupersetModelView): + route_base = "/superset/all_entities" + datamodel = SQLAInterface(Tag) + class_permission_name = "Tags" + + @has_access + @expose("/") + def list(self) -> FlaskResponse: + if not is_feature_enabled("TAGGING_SYSTEM"): + return super().list() + + return super().render_app_template() + +class TaggedObjectView(BaseSupersetView): + @staticmethod + def is_enabled() -> bool: + return is_feature_enabled("TAGGING_SYSTEM") + + @before_request + def ensure_enabled(self) -> None: + if not self.is_enabled(): + raise NotFound() + + @has_access_api + @expose("/tags/suggestions/", methods=["GET"]) + def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use + query = ( + db.session.query(TaggedObject) + .join(Tag) + .with_entities(TaggedObject.tag_id, Tag.name) + .group_by(TaggedObject.tag_id, Tag.name) + .order_by(func.count().desc()) + .all() + ) + tags = [{"id": id, "name": name} for id, name in query] + return json_success(json.dumps(tags)) + + @has_access_api + @expose("/tags///", methods=["GET"]) + def get( # pylint: disable=no-self-use + self, object_type: ObjectTypes, object_id: int + ) -> FlaskResponse: + """List all tags a given object has.""" + if object_id == 0: + return json_success(json.dumps([])) + + query = db.session.query(TaggedObject).filter( + and_( + TaggedObject.object_type == object_type, + TaggedObject.object_id == object_id, + ) + ) + tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query] + return json_success(json.dumps(tags)) + + @has_access_api + @expose("/tags///", methods=["POST"]) + def post( # pylint: disable=no-self-use + self, object_type: ObjectTypes, object_id: int + ) -> FlaskResponse: + """Add new tags to an object.""" + if object_id == 0: + return Response(status=404) + + tagged_objects = [] + for name in request.get_json(force=True): + if ":" in name: + type_name = name.split(":", 1)[0] + type_ = TagTypes[type_name] + else: + type_ = TagTypes.custom + + tag = db.session.query(Tag).filter_by(name=name, type=type_).first() + if not tag: + tag = Tag(name=name, type=type_) + + tagged_objects.append( + TaggedObject(object_id=object_id, object_type=object_type, tag=tag) + ) + + db.session.add_all(tagged_objects) + db.session.commit() + + return Response(status=201) # 201 CREATED + + @has_access_api + @expose("/tags///", methods=["DELETE"]) + def delete( # pylint: disable=no-self-use + self, object_type: ObjectTypes, object_id: int + ) -> FlaskResponse: + """Remove tags from an object.""" + tag_names = request.get_json(force=True) + if not tag_names: + return Response(status=403) + + db.session.query(TaggedObject).filter( + and_( + TaggedObject.object_type == object_type, + TaggedObject.object_id == object_id, + ), + TaggedObject.tag.has(Tag.name.in_(tag_names)), + ).delete(synchronize_session=False) + db.session.commit() + + return Response(status=204) # 204 NO CONTENT + + @has_access_api + @expose("/tagged_objects/", methods=["GET", "POST"]) + def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use + tags = [ + process_template(tag) + for tag in request.args.get("tags", "").split(",") + if tag + ] + if not tags: + return json_success(json.dumps([])) + + # filter types + types = [type_ for type_ in request.args.get("types", "").split(",") if type_] + + results: List[Dict[str, Any]] = [] + + # dashboards + if not types or "dashboard" in types: + dashboards = ( + db.session.query(Dashboard) + .join( + TaggedObject, + and_( + TaggedObject.object_id == Dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard, + ), + ) + .join(Tag, TaggedObject.tag_id == Tag.id) + .filter(Tag.name.in_(tags)) + ) + results.extend( + { + "id": obj.id, + "type": ObjectTypes.dashboard.name, + "name": obj.dashboard_title, + "url": obj.url, + "changed_on": obj.changed_on, + "created_by": obj.created_by_fk, + "creator": obj.creator(), + } + for obj in dashboards + ) + + # charts + if not types or "chart" in types: + charts = ( + db.session.query(Slice) + .join( + TaggedObject, + and_( + TaggedObject.object_id == Slice.id, + TaggedObject.object_type == ObjectTypes.chart, + ), + ) + .join(Tag, TaggedObject.tag_id == Tag.id) + .filter(Tag.name.in_(tags)) + ) + results.extend( + { + "id": obj.id, + "type": ObjectTypes.chart.name, + "name": obj.slice_name, + "url": obj.url, + "changed_on": obj.changed_on, + "created_by": obj.created_by_fk, + "creator": obj.creator(), + } + for obj in charts + ) + + # saved queries + if not types or "query" in types: + saved_queries = ( + db.session.query(SavedQuery) + .join( + TaggedObject, + and_( + TaggedObject.object_id == SavedQuery.id, + TaggedObject.object_type == ObjectTypes.query, + ), + ) + .join(Tag, TaggedObject.tag_id == Tag.id) + .filter(Tag.name.in_(tags)) + ) + results.extend( + { + "id": obj.id, + "type": ObjectTypes.query.name, + "name": obj.label, + "url": obj.url(), + "changed_on": obj.changed_on, + "created_by": obj.created_by_fk, + "creator": obj.creator(), + } + for obj in saved_queries + ) + + return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) diff --git a/superset/views/tags.py b/superset/views/tags.py index 009e2319320c6..5230349ec5f08 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -1,255 +1,255 @@ -# 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. -from __future__ import absolute_import, division, print_function, unicode_literals - -from typing import Any, Dict, List - -import simplejson as json -from flask import request, Response -from flask_appbuilder import expose -from flask_appbuilder.hooks import before_request -from flask_appbuilder.models.sqla.interface import SQLAInterface -from flask_appbuilder.security.decorators import has_access, has_access_api -from jinja2.sandbox import SandboxedEnvironment -from sqlalchemy import and_, func -from werkzeug.exceptions import NotFound - -from superset import db, is_feature_enabled, utils -from superset.jinja_context import ExtraCache -from superset.models.dashboard import Dashboard -from superset.models.slice import Slice -from superset.models.sql_lab import SavedQuery -from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes -from superset.superset_typing import FlaskResponse -from superset.views.base import SupersetModelView - -from .base import BaseSupersetView, json_success - - -def process_template(content: str) -> str: - env = SandboxedEnvironment() - template = env.from_string(content) - context = { - "current_user_id": ExtraCache.current_user_id, - "current_username": ExtraCache.current_username, - } - return template.render(context) - -class TagModelView(SupersetModelView): - route_base = "/superset/tags" - datamodel = SQLAInterface(Tag) - class_permission_name = "Tags" - - @has_access - @expose("/") - def list(self) -> FlaskResponse: - if not is_feature_enabled("TAGGING_SYSTEM"): - return super().list() - - return super().render_app_template() - -class TagView(BaseSupersetView): - @staticmethod - def is_enabled() -> bool: - return is_feature_enabled("TAGGING_SYSTEM") - - @before_request - def ensure_enabled(self) -> None: - if not self.is_enabled(): - raise NotFound() - - @has_access_api - @expose("/tags/suggestions/", methods=["GET"]) - def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use - query = ( - db.session.query(TaggedObject) - .join(Tag) - .with_entities(TaggedObject.tag_id, Tag.name) - .group_by(TaggedObject.tag_id, Tag.name) - .order_by(func.count().desc()) - .all() - ) - tags = [{"id": id, "name": name} for id, name in query] - return json_success(json.dumps(tags)) - - @has_access_api - @expose("/tags///", methods=["GET"]) - def get( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """List all tags a given object has.""" - if object_id == 0: - return json_success(json.dumps([])) - - query = db.session.query(TaggedObject).filter( - and_( - TaggedObject.object_type == object_type, - TaggedObject.object_id == object_id, - ) - ) - tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query] - return json_success(json.dumps(tags)) - - @has_access_api - @expose("/tags///", methods=["POST"]) - def post( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """Add new tags to an object.""" - if object_id == 0: - return Response(status=404) - - tagged_objects = [] - for name in request.get_json(force=True): - if ":" in name: - type_name = name.split(":", 1)[0] - type_ = TagTypes[type_name] - else: - type_ = TagTypes.custom - - tag = db.session.query(Tag).filter_by(name=name, type=type_).first() - if not tag: - tag = Tag(name=name, type=type_) - - tagged_objects.append( - TaggedObject(object_id=object_id, object_type=object_type, tag=tag) - ) - - db.session.add_all(tagged_objects) - db.session.commit() - - return Response(status=201) # 201 CREATED - - @has_access_api - @expose("/tags///", methods=["DELETE"]) - def delete( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """Remove tags from an object.""" - tag_names = request.get_json(force=True) - if not tag_names: - return Response(status=403) - - db.session.query(TaggedObject).filter( - and_( - TaggedObject.object_type == object_type, - TaggedObject.object_id == object_id, - ), - TaggedObject.tag.has(Tag.name.in_(tag_names)), - ).delete(synchronize_session=False) - db.session.commit() - - return Response(status=204) # 204 NO CONTENT - - @has_access_api - @expose("/tagged_objects/", methods=["GET", "POST"]) - def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use - tags = [ - process_template(tag) - for tag in request.args.get("tags", "").split(",") - if tag - ] - if not tags: - return json_success(json.dumps([])) - - # filter types - types = [type_ for type_ in request.args.get("types", "").split(",") if type_] - - results: List[Dict[str, Any]] = [] - - # dashboards - if not types or "dashboard" in types: - dashboards = ( - db.session.query(Dashboard) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Dashboard.id, - TaggedObject.object_type == ObjectTypes.dashboard, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.dashboard.name, - "name": obj.dashboard_title, - "url": obj.url, - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in dashboards - ) - - # charts - if not types or "chart" in types: - charts = ( - db.session.query(Slice) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Slice.id, - TaggedObject.object_type == ObjectTypes.chart, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.chart.name, - "name": obj.slice_name, - "url": obj.url, - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in charts - ) - - # saved queries - if not types or "query" in types: - saved_queries = ( - db.session.query(SavedQuery) - .join( - TaggedObject, - and_( - TaggedObject.object_id == SavedQuery.id, - TaggedObject.object_type == ObjectTypes.query, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.query.name, - "name": obj.label, - "url": obj.url(), - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in saved_queries - ) - - return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) +# # 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. +# from __future__ import absolute_import, division, print_function, unicode_literals + +# from typing import Any, Dict, List + +# import simplejson as json +# from flask import request, Response +# from flask_appbuilder import expose +# from flask_appbuilder.hooks import before_request +# from flask_appbuilder.models.sqla.interface import SQLAInterface +# from flask_appbuilder.security.decorators import has_access, has_access_api +# from jinja2.sandbox import SandboxedEnvironment +# from sqlalchemy import and_, func +# from werkzeug.exceptions import NotFound + +# from superset import db, is_feature_enabled, utils +# from superset.jinja_context import ExtraCache +# from superset.models.dashboard import Dashboard +# from superset.models.slice import Slice +# from superset.models.sql_lab import SavedQuery +# from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes +# from superset.superset_typing import FlaskResponse +# from superset.views.base import SupersetModelView + +# from .base import BaseSupersetView, json_success + + +# def process_template(content: str) -> str: +# env = SandboxedEnvironment() +# template = env.from_string(content) +# context = { +# "current_user_id": ExtraCache.current_user_id, +# "current_username": ExtraCache.current_username, +# } +# return template.render(context) + +# class TagModelView(SupersetModelView): +# route_base = "/superset/tags" +# datamodel = SQLAInterface(Tag) +# class_permission_name = "Tags" + +# @has_access +# @expose("/") +# def list(self) -> FlaskResponse: +# if not is_feature_enabled("TAGGING_SYSTEM"): +# return super().list() + +# return super().render_app_template() + +# class TagView(BaseSupersetView): +# @staticmethod +# def is_enabled() -> bool: +# return is_feature_enabled("TAGGING_SYSTEM") + +# @before_request +# def ensure_enabled(self) -> None: +# if not self.is_enabled(): +# raise NotFound() + +# @has_access_api +# @expose("/tags/suggestions/", methods=["GET"]) +# def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use +# query = ( +# db.session.query(TaggedObject) +# .join(Tag) +# .with_entities(TaggedObject.tag_id, Tag.name) +# .group_by(TaggedObject.tag_id, Tag.name) +# .order_by(func.count().desc()) +# .all() +# ) +# tags = [{"id": id, "name": name} for id, name in query] +# return json_success(json.dumps(tags)) + +# @has_access_api +# @expose("/tags///", methods=["GET"]) +# def get( # pylint: disable=no-self-use +# self, object_type: ObjectTypes, object_id: int +# ) -> FlaskResponse: +# """List all tags a given object has.""" +# if object_id == 0: +# return json_success(json.dumps([])) + +# query = db.session.query(TaggedObject).filter( +# and_( +# TaggedObject.object_type == object_type, +# TaggedObject.object_id == object_id, +# ) +# ) +# tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query] +# return json_success(json.dumps(tags)) + +# @has_access_api +# @expose("/tags///", methods=["POST"]) +# def post( # pylint: disable=no-self-use +# self, object_type: ObjectTypes, object_id: int +# ) -> FlaskResponse: +# """Add new tags to an object.""" +# if object_id == 0: +# return Response(status=404) + +# tagged_objects = [] +# for name in request.get_json(force=True): +# if ":" in name: +# type_name = name.split(":", 1)[0] +# type_ = TagTypes[type_name] +# else: +# type_ = TagTypes.custom + +# tag = db.session.query(Tag).filter_by(name=name, type=type_).first() +# if not tag: +# tag = Tag(name=name, type=type_) + +# tagged_objects.append( +# TaggedObject(object_id=object_id, object_type=object_type, tag=tag) +# ) + +# db.session.add_all(tagged_objects) +# db.session.commit() + +# return Response(status=201) # 201 CREATED + +# @has_access_api +# @expose("/tags///", methods=["DELETE"]) +# def delete( # pylint: disable=no-self-use +# self, object_type: ObjectTypes, object_id: int +# ) -> FlaskResponse: +# """Remove tags from an object.""" +# tag_names = request.get_json(force=True) +# if not tag_names: +# return Response(status=403) + +# db.session.query(TaggedObject).filter( +# and_( +# TaggedObject.object_type == object_type, +# TaggedObject.object_id == object_id, +# ), +# TaggedObject.tag.has(Tag.name.in_(tag_names)), +# ).delete(synchronize_session=False) +# db.session.commit() + +# return Response(status=204) # 204 NO CONTENT + +# @has_access_api +# @expose("/tagged_objects/", methods=["GET", "POST"]) +# def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use +# tags = [ +# process_template(tag) +# for tag in request.args.get("tags", "").split(",") +# if tag +# ] +# if not tags: +# return json_success(json.dumps([])) + +# # filter types +# types = [type_ for type_ in request.args.get("types", "").split(",") if type_] + +# results: List[Dict[str, Any]] = [] + +# # dashboards +# if not types or "dashboard" in types: +# dashboards = ( +# db.session.query(Dashboard) +# .join( +# TaggedObject, +# and_( +# TaggedObject.object_id == Dashboard.id, +# TaggedObject.object_type == ObjectTypes.dashboard, +# ), +# ) +# .join(Tag, TaggedObject.tag_id == Tag.id) +# .filter(Tag.name.in_(tags)) +# ) +# results.extend( +# { +# "id": obj.id, +# "type": ObjectTypes.dashboard.name, +# "name": obj.dashboard_title, +# "url": obj.url, +# "changed_on": obj.changed_on, +# "created_by": obj.created_by_fk, +# "creator": obj.creator(), +# } +# for obj in dashboards +# ) + +# # charts +# if not types or "chart" in types: +# charts = ( +# db.session.query(Slice) +# .join( +# TaggedObject, +# and_( +# TaggedObject.object_id == Slice.id, +# TaggedObject.object_type == ObjectTypes.chart, +# ), +# ) +# .join(Tag, TaggedObject.tag_id == Tag.id) +# .filter(Tag.name.in_(tags)) +# ) +# results.extend( +# { +# "id": obj.id, +# "type": ObjectTypes.chart.name, +# "name": obj.slice_name, +# "url": obj.url, +# "changed_on": obj.changed_on, +# "created_by": obj.created_by_fk, +# "creator": obj.creator(), +# } +# for obj in charts +# ) + +# # saved queries +# if not types or "query" in types: +# saved_queries = ( +# db.session.query(SavedQuery) +# .join( +# TaggedObject, +# and_( +# TaggedObject.object_id == SavedQuery.id, +# TaggedObject.object_type == ObjectTypes.query, +# ), +# ) +# .join(Tag, TaggedObject.tag_id == Tag.id) +# .filter(Tag.name.in_(tags)) +# ) +# results.extend( +# { +# "id": obj.id, +# "type": ObjectTypes.query.name, +# "name": obj.label, +# "url": obj.url(), +# "changed_on": obj.changed_on, +# "created_by": obj.created_by_fk, +# "creator": obj.creator(), +# } +# for obj in saved_queries +# ) + +# return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) From 211d62c00ac9c9ba059356eb13ab11b57d818d45 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:56:42 -0400 Subject: [PATCH 34/76] added tags and allentities CRUD views --- superset-frontend/src/tags.ts | 11 + .../views/CRUD/allentities/AllEntities.tsx | 7 +- .../CRUD/allentities/AllEntitiesTable.tsx | 6 +- .../src/views/CRUD/chart/ChartList.tsx | 5 +- .../views/CRUD/dashboard/DashboardList.tsx | 5 +- .../src/views/CRUD/tags/TagCard.tsx | 152 ++++++ .../src/views/CRUD/tags/TagList.tsx | 482 ++++++++++++++++++ superset-frontend/src/views/CRUD/types.ts | 15 +- superset-frontend/src/views/CRUD/utils.tsx | 4 + superset-frontend/src/views/routes.tsx | 7 + superset/initialization/__init__.py | 17 +- superset/tags/api.py | 30 +- superset/views/all_entities.py | 9 +- superset/views/tags.py | 364 ++++--------- 14 files changed, 822 insertions(+), 292 deletions(-) create mode 100644 superset-frontend/src/views/CRUD/tags/TagCard.tsx create mode 100644 superset-frontend/src/views/CRUD/tags/TagList.tsx diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index e0f1fd5b61792..4d0a1580c1125 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -25,6 +25,17 @@ export const OBJECT_TYPES = Object.freeze({ QUERY: 'query', }); +export function fetchAllTags( + callback: (json: JsonObject) => void, + error: (response: Response) => void, +) { + let url = `/tagview/tags/`; + SupersetClient.get({ endpoint: url }) + .then(({ json }) => callback(json)) + .catch(response => error(response)); +} + + export function fetchTags( { objectType, diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx index fda791ff9a654..c9fb2fea149ea 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx @@ -16,17 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { styled } from '@superset-ui/core'; -import Tag, { TagType } from 'src/types/TagType'; +import Tag from 'src/types/TagType'; import { StringParam, useQueryParam } from 'use-query-params'; import withToasts from 'src/components/MessageToasts/withToasts'; import SelectControl from 'src/explore/components/controls/SelectControl'; import { fetchSuggestions } from 'src/tags'; import AllEntitiesTable from './AllEntitiesTable'; -import { loadTags } from 'src/components/ObjectTags'; -import { AsyncSelect, Select } from 'src/components'; -import { LabeledValue } from 'antd/lib/select'; const AllEntitiesContainer = styled.div` background-color: ${({ theme }) => theme.colors.grayscale.light4}; diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx index ef9514ec7ffd7..d5fb262e2e7ee 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx @@ -20,10 +20,8 @@ import React, { useState, useEffect } from 'react'; import moment from 'moment'; import { t, styled } from '@superset-ui/core'; import TableView, { EmptyWrapperType } from 'src/components/TableView'; -import { fetchObjects, fetchTags } from '../../../tags'; +import { fetchObjects } from '../../../tags'; import Loading from '../../../components/Loading'; -import { Tag, TagsList } from 'src/components/Tags'; -import { loadTags } from 'src/components/ObjectTags'; const AllEntitiesTableContainer = styled.div` text-align: left; @@ -85,7 +83,6 @@ export default function AllEntitiesTable({ search = '' }: AllEntitiesTableProps) const data = objects[type].map((o: TaggedObject) => ({ [type]: {o.name}, // eslint-disable-next-line react/no-danger - creator:
o.creator
, modified: moment.utc(o.changed_on).fromNow(), })); return ( @@ -99,7 +96,6 @@ export default function AllEntitiesTable({ search = '' }: AllEntitiesTableProps) accessor: type, Header: type.charAt(0).toUpperCase() + type.slice(1), }, - { accessor: 'creator', Header: 'Creator' }, { accessor: 'modified', Header: 'Modified' }, ]} /> diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 7b73e7b25c06e..96ee90167f419 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -28,6 +28,7 @@ import { uniqBy } from 'lodash'; import moment from 'moment'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { + Actions, createErrorHandler, createFetchRelated, handleChartDelete, @@ -146,10 +147,6 @@ interface ChartListProps { }; } -const Actions = styled.div` - color: ${({ theme }) => theme.colors.grayscale.base}; -`; - function ChartList(props: ChartListProps) { const { addDangerToast, diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index e2e56c08b5441..0f6e1031ed190 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -25,6 +25,7 @@ import { createFetchRelated, createErrorHandler, handleDashboardDelete, + Actions, } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; @@ -94,10 +95,6 @@ interface Dashboard { created_by: object; } -const Actions = styled.div` - color: ${({ theme }) => theme.colors.grayscale.base}; -`; - function DashboardList(props: DashboardListProps) { const { addDangerToast, diff --git a/superset-frontend/src/views/CRUD/tags/TagCard.tsx b/superset-frontend/src/views/CRUD/tags/TagCard.tsx new file mode 100644 index 0000000000000..ed4d82eba7150 --- /dev/null +++ b/superset-frontend/src/views/CRUD/tags/TagCard.tsx @@ -0,0 +1,152 @@ +/** + * 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 { Link, useHistory } from 'react-router-dom'; + import { t, useTheme } from '@superset-ui/core'; + import { handleDashboardDelete, CardStyles } from 'src/views/CRUD/utils'; + import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; + import { AntdDropdown } from 'src/components'; + import { Menu } from 'src/components/Menu'; + import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; + import ListViewCard from 'src/components/ListViewCard'; + import Icons from 'src/components/Icons'; + import Label from 'src/components/Label'; + import FacePile from 'src/components/FacePile'; + import FaveStar from 'src/components/FaveStar'; + import { Tag } from 'src/views/CRUD/types'; + + interface TagCardProps { + tag: Tag; + hasPerm: (name: string) => boolean; + bulkSelectEnabled: boolean; + refreshData: () => void; + loading: boolean; + addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; + openTagEditModal?: (t: Tag) => void; + tagFilter?: string; + userId?: string | number; + showThumbnails?: boolean; + } + + function TagCard({ + tag, + hasPerm, + bulkSelectEnabled, + tagFilter, + refreshData, + userId, + addDangerToast, + addSuccessToast, + openTagEditModal, + showThumbnails, + }: TagCardProps) { + const history = useHistory(); + const canEdit = hasPerm('can_write'); + const canDelete = hasPerm('can_write'); + const canExport = hasPerm('can_export'); + + const theme = useTheme(); + const menu = ( + + {canEdit && openTagEditModal && ( + +
+ openTagEditModal && openTagEditModal(tag) + } + data-test="dashboard-card-option-edit-button" + > + {t('Edit')} +
+
+ )} + {canDelete && ( + + + {t('Are you sure you want to delete')}{' '} + {tag.name}? + + } + onConfirm={() => + handleTagDelete( + tag, + refreshData, + addSuccessToast, + addDangerToast, + tagFilter, + userId, + ) + } + > + {confirmDelete => ( +
+ {t('Delete')} +
+ )} +
+
+ )} +
+ ); + return ( + + + ) : null + } + url={undefined} + linkComponent={Link} + imgFallbackURL="/static/assets/images/dashboard-card-fallback.svg" + description={t('Modified %s', tag.changed_on)} + actions={ + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + + } + /> + + ); + } + + export default TagCard; + \ No newline at end of file diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx new file mode 100644 index 0000000000000..09d1751fba8d4 --- /dev/null +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -0,0 +1,482 @@ +/** + * 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 { SupersetClient, t } from '@superset-ui/core'; + import React, { useState, useMemo, useCallback } from 'react'; + import rison from 'rison'; + import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; + import { + createFetchRelated, + createErrorHandler, + handleDashboardDelete, + Actions, + } from 'src/views/CRUD/utils'; + import { useListViewResource } from 'src/views/CRUD/hooks'; + import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; + import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; + import ListView, { + ListViewProps, + Filters, + FilterOperator, + } from 'src/components/ListView'; + import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers'; + import withToasts from 'src/components/MessageToasts/withToasts'; + import Icons from 'src/components/Icons'; + import PropertiesModal from 'src/dashboard/components/PropertiesModal'; + import { Tooltip } from 'src/components/Tooltip'; + import TagCard from './TagCard'; +import { Tag } from '../types'; +import FacePile from 'src/components/FacePile'; +import { Link } from 'react-router-dom'; + + const PAGE_SIZE = 25; + + interface TagListProps { + addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; + user: { + userId: string | number; + firstName: string; + lastName: string; + }; + } + + function TagList(props: TagListProps) { + const { + addDangerToast, + addSuccessToast, + user: { userId }, + } = props; + + const { + state: { + loading, + resourceCount: tagCount, + resourceCollection: tags, + bulkSelectEnabled, + }, + setResourceCollection: setTags, + hasPerm, + fetchData, + toggleBulkSelect, + refreshData, + } = useListViewResource( + 'tag', + t('tag'), + addDangerToast, + ); + + const [tagToEdit, setTagToEdit] = useState( + null, + ); + + // TODO: Fix usage of localStorage keying on the user id + const userKey = dangerouslyGetItemDoNotUse(userId?.toString(), null); + + const canCreate = hasPerm('can_write'); + const canEdit = hasPerm('can_write'); + const canDelete = hasPerm('can_write'); + + const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; + + function openTagEditModal(tag: Tag) { + setTagToEdit(tag); + } + + function handleTagEdit(edits: Tag) { + return SupersetClient.get({ + endpoint: `/api/v1/tag/${edits.id}`, + }).then( + ({ json = {} }) => { + setTags( + tags.map(tag => { + if (tag.id === json?.result?.id) { + const { + changed_by_name, + changed_by_url, + changed_by, + dashboard_title = '', + slug = '', + json_metadata = '', + changed_on_delta_humanized, + url = '', + certified_by = '', + certification_details = '', + owners, + tags, + } = json.result; + return { + ...tag, + changed_by_name, + changed_by_url, + changed_by, + dashboard_title, + slug, + json_metadata, + changed_on_delta_humanized, + url, + certified_by, + certification_details, + owners, + tags, + }; + } + return tag; + }), + ); + }, + createErrorHandler(errMsg => + addDangerToast( + t('An error occurred while fetching dashboards: %s', errMsg), + ), + ), + ); + } + + function handleBulkTagDelete(tagsToDelete: Tag[]) { + // TODO fix bulk tag delete + return SupersetClient.delete({ + endpoint: `/api/v1/tag/?q=${rison.encode( + tagsToDelete.map(({ id }) => id), + )}`, + }).then( + ({ json = {} }) => { + refreshData(); + addSuccessToast(json.message); + }, + createErrorHandler(errMsg => + addDangerToast( + t('There was an issue deleting the selected tags: ', errMsg), + ), + ), + ); + } + + const columns = useMemo( + () => [ + { + Cell: ({ + row: { + original: { + name: tagName, + }, + }, + }: any) => ( + + {tagName} + + ), + Header: t('Name'), + accessor: 'name', + }, + { + Cell: ({ + row: { + original: { + changed_by_name: changedByName, + changed_by_url: changedByUrl, + }, + }, + }: any) => {changedByName}, + Header: t('Modified by'), + accessor: 'changed_by.first_name', + size: 'xl', + }, + { + Cell: ({ + row: { + original: { changed_on_delta_humanized: changedOn }, + }, + }: any) => {changedOn}, + Header: t('Modified'), + accessor: 'changed_on_delta_humanized', + size: 'xl', + }, + { + Cell: ({ + row: { + original: { created_by: createdBy }, + }, + }: any) => + createdBy ? : '', + Header: t('Created by'), + accessor: 'created_by', + disableSortBy: true, + size: 'xl', + }, + // { + // Cell: ({ + // row: { + // original: { num_tagged_objects: numTaggedObjects }, + // }, + // }: any) => numTaggedObjects, + // Header: t('Number of Tagged Entities'), + // accessor: 'num_tagged_objects', + // disableSortBy: true, + // }, + { + Cell: ({ row: { original } }: any) => { + const handleDelete = () => + handleDashboardDelete( + original, + refreshData, + addSuccessToast, + addDangerToast, + ); + const handleEdit = () => openTagEditModal(original); + + return ( + + {canDelete && ( + + {t('Are you sure you want to delete')}{' '} + {original.dashboard_title}? + + } + onConfirm={handleDelete} + > + {confirmDelete => ( + + + {/* fix icon name */} + + + + )} + + )} + {canEdit && ( + + + + + + )} + + ); + }, + Header: t('Actions'), + id: 'actions', + hidden: !canEdit && !canDelete, + disableSortBy: true, + }, + ], + [ + userId, + canEdit, + canDelete, + refreshData, + addSuccessToast, + addDangerToast, + ], + ); + + const filters: Filters = useMemo(() => { + const filters_list = [ + { + Header: t('Created by'), + id: 'created_by', + input: 'select', + operator: FilterOperator.relationOneMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'tag', + 'created_by', + createErrorHandler(errMsg => + addDangerToast( + t( + 'An error occurred while fetching tag created by values: %s', + errMsg, + ), + ), + ), + props.user, + ), + paginate: true, + }, + { + Header: t('Search'), + id: 'name', + input: 'search', + operator: FilterOperator.titleOrSlug, + } + ] as Filters; + return filters_list; + }, [addDangerToast, props.user]); + + const sortTypes = [ + { + desc: false, + id: 'name', + label: t('Alphabetical'), + value: 'alphabetical', + }, + { + desc: true, + id: 'changed_on_delta_humanized', + label: t('Recently modified'), + value: 'recently_modified', + }, + { + desc: false, + id: 'changed_on_delta_humanized', + label: t('Least recently modified'), + value: 'least_recently_modified', + }, + ]; + + const renderCard = useCallback( + (tag: Tag) => ( + + ), + [ + addDangerToast, + addSuccessToast, + bulkSelectEnabled, + hasPerm, + loading, + userId, + refreshData, + userKey, + ], + ); + + const subMenuButtons: SubMenuProps['buttons'] = []; + if (canDelete) { + subMenuButtons.push({ + name: t('Bulk select'), + buttonStyle: 'secondary', + 'data-test': 'bulk-select', + onClick: toggleBulkSelect, + }); + } + if (canCreate) { + subMenuButtons.push({ + name: ( + <> + {t('Tag')} + + ), + buttonStyle: 'primary', + onClick: () => { + // TODO Add Tags?? + }, + }); + } + return ( + <> + + + {confirmDelete => { + const bulkActions: ListViewProps['bulkActions'] = []; + if (canDelete) { + bulkActions.push({ + key: 'delete', + name: t('Delete'), + type: 'danger', + onSelect: confirmDelete, + }); + } + return ( + <> + {tagToEdit && ( + setTagToEdit(null)} + onSubmit={handleTagEdit} + /> + )} + + bulkActions={bulkActions} + bulkSelectEnabled={bulkSelectEnabled} + cardSortSelectOptions={sortTypes} + className="dashboard-list-view" + columns={columns} + count={tagCount} + data={tags} + disableBulkSelect={toggleBulkSelect} + fetchData={fetchData} + filters={filters} + initialSort={initialSort} + loading={loading} + pageSize={PAGE_SIZE} + showThumbnails={ + userKey + ? userKey.thumbnails + : isFeatureEnabled(FeatureFlag.THUMBNAILS) + } + renderCard={renderCard} + defaultViewMode={ + isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW) + ? 'card' + : 'table' + } + /> + + ); + }} + + + ); + } + + export default withToasts(TagList); + \ No newline at end of file diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts index 0090697747ac5..3161e255ae49a 100644 --- a/superset-frontend/src/views/CRUD/types.ts +++ b/superset-frontend/src/views/CRUD/types.ts @@ -133,12 +133,15 @@ export enum QueryObjectColumns { tracking_url = 'tracking_url', } -export type ImportResourceName = - | 'chart' - | 'dashboard' - | 'database' - | 'dataset' - | 'saved_query'; +export interface Tag { + changed_by_name: string; + changed_by_url: string; + changed_on_delta_humanized: string; + changed_by: string; + name: string; + id: number; + created_by: object; +} export type DatabaseObject = Partial & Pick; diff --git a/superset-frontend/src/views/CRUD/utils.tsx b/superset-frontend/src/views/CRUD/utils.tsx index 64df8743035eb..82f54eff4bef9 100644 --- a/superset-frontend/src/views/CRUD/utils.tsx +++ b/superset-frontend/src/views/CRUD/utils.tsx @@ -64,6 +64,10 @@ import { Dashboard, Filters } from './types'; risonRef.next_id = new RegExp(idrx, 'g'); })(); +export const Actions = styled.div` + color: ${({ theme }) => theme.colors.grayscale.base}; +`; + const createFetchResourceMethod = (method: string) => ( diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx index 2ab6fc6c48180..7c4c0889173a2 100644 --- a/superset-frontend/src/views/routes.tsx +++ b/superset-frontend/src/views/routes.tsx @@ -106,6 +106,9 @@ const SavedQueryList = lazy( const AllEntitiesPage = lazy( () => import(/* webpackChunkName: "AllEntities" */ 'src/views/CRUD/allentities/AllEntities'), ); +const TagsPage = lazy( + () => import(/* webpackChunkName: "TagList" */ 'src/views/CRUD/tags/TagList'), +); type Routes = { path: string; @@ -200,6 +203,10 @@ if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) { path: '/superset/all_entities/', Component: AllEntitiesPage, }); + routes.push({ + path: '/superset/tags/', + Component: TagsPage, + }); } const frontEndRoutes = routes diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index d596ef419d466..74705b49d1e61 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -186,6 +186,7 @@ def init_views(self) -> None: ) from superset.views.users.api import CurrentUserRestApi from superset.views.all_entities import TaggedObjectsModelView, TaggedObjectView + from superset.views.tags import TagModelView, TagView # @@ -309,6 +310,7 @@ def init_views(self) -> None: appbuilder.add_view_no_menu(TableSchemaView) appbuilder.add_view_no_menu(TabStateView) appbuilder.add_view_no_menu(TaggedObjectView) + appbuilder.add_view_no_menu(TagView) appbuilder.add_view_no_menu(ReportView) # @@ -374,7 +376,20 @@ def init_views(self) -> None: "All Entities", label=__("All Entities"), icon="", - category="", + category="Tagging", + category_label=__("Tagging"), + category_icon="", + menu_cond=lambda: feature_flag_manager.is_feature_enabled( + "TAGGING_SYSTEM" + ), + ) + appbuilder.add_view( + TagModelView, + "Tags", + label=__("Tags"), + icon="", + category="Tagging", + category_label=__("Tagging"), category_icon="", menu_cond=lambda: feature_flag_manager.is_feature_enabled( "TAGGING_SYSTEM" diff --git a/superset/tags/api.py b/superset/tags/api.py index ab3d3beb1ddde..473e0e7b16dc4 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -55,13 +55,30 @@ class TagRestApi(BaseSupersetModelRestApi): method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP list_columns = [ - "id", - "name", - "type", + "id", + "name", + "type", + "changed_by.first_name", + "changed_by.last_name", + "changed_on_delta_humanized", + "created_by.first_name", + "created_by.last_name", ] list_select_columns = list_columns + show_columns = [ + "id", + "name", + "type", + "changed_by.first_name", + "changed_by.last_name", + "changed_on_delta_humanized", + "created_by.first_name", + "created_by.last_name", + "created_by" + ] + add_model_schema = TagPostSchema() tag_get_response_schema = TagGetResponseSchema() @@ -76,10 +93,9 @@ class TagRestApi(BaseSupersetModelRestApi): def __repr__(self) -> str: """Deterministic string representation of the API instance for etag_cache.""" - return "Superset.tags.api.TagRestApi@v{}{}".format( - self.appbuilder.app.config["VERSION_STRING"], - self.appbuilder.app.config["VERSION_SHA"], - ) + return 'Superset.tags.api.TagRestApi@v' \ + f'{self.appbuilder.app.config["VERSION_STRING"]}' \ + f'{self.appbuilder.app.config["VERSION_SHA"]}' @expose("///", methods=["POST"]) @protect() diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index 0c57da097284b..739db5db1f546 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -163,8 +163,6 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use for tag in request.args.get("tags", "").split(",") if tag ] - if not tags: - return json_success(json.dumps([])) # filter types types = [type_ for type_ in request.args.get("types", "").split(",") if type_] @@ -183,8 +181,9 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use ), ) .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) + .filter(not tags or Tag.name.in_(tags)) ) + results.extend( { "id": obj.id, @@ -210,7 +209,7 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use ), ) .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) + .filter(not tags or Tag.name.in_(tags)) ) results.extend( { @@ -237,7 +236,7 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use ), ) .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) + .filter(not tags or Tag.name.in_(tags)) ) results.extend( { diff --git a/superset/views/tags.py b/superset/views/tags.py index 5230349ec5f08..e1b87571a045c 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -1,255 +1,109 @@ -# # 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. -# from __future__ import absolute_import, division, print_function, unicode_literals - -# from typing import Any, Dict, List - -# import simplejson as json -# from flask import request, Response -# from flask_appbuilder import expose -# from flask_appbuilder.hooks import before_request -# from flask_appbuilder.models.sqla.interface import SQLAInterface -# from flask_appbuilder.security.decorators import has_access, has_access_api -# from jinja2.sandbox import SandboxedEnvironment -# from sqlalchemy import and_, func -# from werkzeug.exceptions import NotFound - -# from superset import db, is_feature_enabled, utils -# from superset.jinja_context import ExtraCache -# from superset.models.dashboard import Dashboard -# from superset.models.slice import Slice -# from superset.models.sql_lab import SavedQuery -# from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes -# from superset.superset_typing import FlaskResponse -# from superset.views.base import SupersetModelView - -# from .base import BaseSupersetView, json_success - - -# def process_template(content: str) -> str: -# env = SandboxedEnvironment() -# template = env.from_string(content) -# context = { -# "current_user_id": ExtraCache.current_user_id, -# "current_username": ExtraCache.current_username, -# } -# return template.render(context) - -# class TagModelView(SupersetModelView): -# route_base = "/superset/tags" -# datamodel = SQLAInterface(Tag) -# class_permission_name = "Tags" - -# @has_access -# @expose("/") -# def list(self) -> FlaskResponse: -# if not is_feature_enabled("TAGGING_SYSTEM"): -# return super().list() - -# return super().render_app_template() - -# class TagView(BaseSupersetView): -# @staticmethod -# def is_enabled() -> bool: -# return is_feature_enabled("TAGGING_SYSTEM") - -# @before_request -# def ensure_enabled(self) -> None: -# if not self.is_enabled(): -# raise NotFound() - -# @has_access_api -# @expose("/tags/suggestions/", methods=["GET"]) -# def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use -# query = ( -# db.session.query(TaggedObject) -# .join(Tag) -# .with_entities(TaggedObject.tag_id, Tag.name) -# .group_by(TaggedObject.tag_id, Tag.name) -# .order_by(func.count().desc()) -# .all() -# ) -# tags = [{"id": id, "name": name} for id, name in query] -# return json_success(json.dumps(tags)) - -# @has_access_api -# @expose("/tags///", methods=["GET"]) -# def get( # pylint: disable=no-self-use -# self, object_type: ObjectTypes, object_id: int -# ) -> FlaskResponse: -# """List all tags a given object has.""" -# if object_id == 0: -# return json_success(json.dumps([])) - -# query = db.session.query(TaggedObject).filter( -# and_( -# TaggedObject.object_type == object_type, -# TaggedObject.object_id == object_id, -# ) -# ) -# tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query] -# return json_success(json.dumps(tags)) - -# @has_access_api -# @expose("/tags///", methods=["POST"]) -# def post( # pylint: disable=no-self-use -# self, object_type: ObjectTypes, object_id: int -# ) -> FlaskResponse: -# """Add new tags to an object.""" -# if object_id == 0: -# return Response(status=404) - -# tagged_objects = [] -# for name in request.get_json(force=True): -# if ":" in name: -# type_name = name.split(":", 1)[0] -# type_ = TagTypes[type_name] -# else: -# type_ = TagTypes.custom - -# tag = db.session.query(Tag).filter_by(name=name, type=type_).first() -# if not tag: -# tag = Tag(name=name, type=type_) - -# tagged_objects.append( -# TaggedObject(object_id=object_id, object_type=object_type, tag=tag) -# ) - -# db.session.add_all(tagged_objects) -# db.session.commit() - -# return Response(status=201) # 201 CREATED - -# @has_access_api -# @expose("/tags///", methods=["DELETE"]) -# def delete( # pylint: disable=no-self-use -# self, object_type: ObjectTypes, object_id: int -# ) -> FlaskResponse: -# """Remove tags from an object.""" -# tag_names = request.get_json(force=True) -# if not tag_names: -# return Response(status=403) - -# db.session.query(TaggedObject).filter( -# and_( -# TaggedObject.object_type == object_type, -# TaggedObject.object_id == object_id, -# ), -# TaggedObject.tag.has(Tag.name.in_(tag_names)), -# ).delete(synchronize_session=False) -# db.session.commit() - -# return Response(status=204) # 204 NO CONTENT - -# @has_access_api -# @expose("/tagged_objects/", methods=["GET", "POST"]) -# def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use -# tags = [ -# process_template(tag) -# for tag in request.args.get("tags", "").split(",") -# if tag -# ] -# if not tags: -# return json_success(json.dumps([])) - -# # filter types -# types = [type_ for type_ in request.args.get("types", "").split(",") if type_] - -# results: List[Dict[str, Any]] = [] - -# # dashboards -# if not types or "dashboard" in types: -# dashboards = ( -# db.session.query(Dashboard) -# .join( -# TaggedObject, -# and_( -# TaggedObject.object_id == Dashboard.id, -# TaggedObject.object_type == ObjectTypes.dashboard, -# ), -# ) -# .join(Tag, TaggedObject.tag_id == Tag.id) -# .filter(Tag.name.in_(tags)) -# ) -# results.extend( -# { -# "id": obj.id, -# "type": ObjectTypes.dashboard.name, -# "name": obj.dashboard_title, -# "url": obj.url, -# "changed_on": obj.changed_on, -# "created_by": obj.created_by_fk, -# "creator": obj.creator(), -# } -# for obj in dashboards -# ) - -# # charts -# if not types or "chart" in types: -# charts = ( -# db.session.query(Slice) -# .join( -# TaggedObject, -# and_( -# TaggedObject.object_id == Slice.id, -# TaggedObject.object_type == ObjectTypes.chart, -# ), -# ) -# .join(Tag, TaggedObject.tag_id == Tag.id) -# .filter(Tag.name.in_(tags)) -# ) -# results.extend( -# { -# "id": obj.id, -# "type": ObjectTypes.chart.name, -# "name": obj.slice_name, -# "url": obj.url, -# "changed_on": obj.changed_on, -# "created_by": obj.created_by_fk, -# "creator": obj.creator(), -# } -# for obj in charts -# ) - -# # saved queries -# if not types or "query" in types: -# saved_queries = ( -# db.session.query(SavedQuery) -# .join( -# TaggedObject, -# and_( -# TaggedObject.object_id == SavedQuery.id, -# TaggedObject.object_type == ObjectTypes.query, -# ), -# ) -# .join(Tag, TaggedObject.tag_id == Tag.id) -# .filter(Tag.name.in_(tags)) -# ) -# results.extend( -# { -# "id": obj.id, -# "type": ObjectTypes.query.name, -# "name": obj.label, -# "url": obj.url(), -# "changed_on": obj.changed_on, -# "created_by": obj.created_by_fk, -# "creator": obj.creator(), -# } -# for obj in saved_queries -# ) - -# return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) +# 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. +from __future__ import absolute_import, division, print_function, unicode_literals + +from typing import Any, Dict, List + +import simplejson as json +from flask import request, Response +from flask_appbuilder import expose +from flask_appbuilder.hooks import before_request +from flask_appbuilder.models.sqla.interface import SQLAInterface +from flask_appbuilder.security.decorators import has_access, has_access_api +from jinja2.sandbox import SandboxedEnvironment +from sqlalchemy import and_, func +from werkzeug.exceptions import NotFound + +from superset import db, is_feature_enabled, utils +from superset.jinja_context import ExtraCache +from superset.models.dashboard import Dashboard +from superset.models.slice import Slice +from superset.models.sql_lab import SavedQuery +from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.superset_typing import FlaskResponse +from superset.views.base import SupersetModelView + +from .base import BaseSupersetView, json_success + + +def process_template(content: str) -> str: + env = SandboxedEnvironment() + template = env.from_string(content) + context = { + "current_user_id": ExtraCache.current_user_id, + "current_username": ExtraCache.current_username, + } + return template.render(context) + +class TagModelView(SupersetModelView): + route_base = "/superset/tags" + datamodel = SQLAInterface(Tag) + class_permission_name = "Tags" + + @has_access + @expose("/") + def list(self) -> FlaskResponse: + if not is_feature_enabled("TAGGING_SYSTEM"): + return super().list() + + return super().render_app_template() + +class TagView(BaseSupersetView): + @staticmethod + def is_enabled() -> bool: + return is_feature_enabled("TAGGING_SYSTEM") + + @before_request + def ensure_enabled(self) -> None: + if not self.is_enabled(): + raise NotFound() + + @has_access_api + @expose("/tags/", methods=["GET"]) + def tags(self) -> FlaskResponse: # pylint: disable=no-self-use + query = ( + db.session.query(Tag) + # .with_entities(Tag.name, Tag.id) + # .group_by(TaggedObject.tag_id, Tag.name) + # .order_by(func.count().desc()) + .all() + ) + results = [ + { + "id": obj.id, + "type": obj.type.name, + "name": obj.name, + "changed_on": obj.changed_on, + "changed_by": obj.changed_by_fk, + "created_by": obj.created_by_fk, + } for obj in query + ] + return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) + + @has_access_api + @expose("/tags/suggestions/", methods=["GET"]) + def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use + query = ( + db.session.query(TaggedObject) + .join(Tag) + .with_entities(TaggedObject.tag_id, Tag.name) + .group_by(TaggedObject.tag_id, Tag.name) + .order_by(func.count().desc()) + .all() + ) + tags = [{"id": id, "name": name} for id, name in query] + return json_success(json.dumps(tags)) From abd2499a775e0fcad1bedad357568bdb7e55bf14 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 5 Oct 2022 07:21:45 -0400 Subject: [PATCH 35/76] added TODOs for add/edit/delete tags in CRUD view --- .../src/views/CRUD/tags/TagList.tsx | 69 ++----------------- 1 file changed, 6 insertions(+), 63 deletions(-) diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx index 09d1751fba8d4..844ad8c51cdf1 100644 --- a/superset-frontend/src/views/CRUD/tags/TagList.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -99,72 +99,15 @@ import { Link } from 'react-router-dom'; } function handleTagEdit(edits: Tag) { - return SupersetClient.get({ - endpoint: `/api/v1/tag/${edits.id}`, - }).then( - ({ json = {} }) => { - setTags( - tags.map(tag => { - if (tag.id === json?.result?.id) { - const { - changed_by_name, - changed_by_url, - changed_by, - dashboard_title = '', - slug = '', - json_metadata = '', - changed_on_delta_humanized, - url = '', - certified_by = '', - certification_details = '', - owners, - tags, - } = json.result; - return { - ...tag, - changed_by_name, - changed_by_url, - changed_by, - dashboard_title, - slug, - json_metadata, - changed_on_delta_humanized, - url, - certified_by, - certification_details, - owners, - tags, - }; - } - return tag; - }), - ); - }, - createErrorHandler(errMsg => - addDangerToast( - t('An error occurred while fetching dashboards: %s', errMsg), - ), - ), - ); + /* TODO: + what permissions need to be checked here? + */ + return } function handleBulkTagDelete(tagsToDelete: Tag[]) { - // TODO fix bulk tag delete - return SupersetClient.delete({ - endpoint: `/api/v1/tag/?q=${rison.encode( - tagsToDelete.map(({ id }) => id), - )}`, - }).then( - ({ json = {} }) => { - refreshData(); - addSuccessToast(json.message); - }, - createErrorHandler(errMsg => - addDangerToast( - t('There was an issue deleting the selected tags: ', errMsg), - ), - ), - ); + // TODO what permissions need to be checked here? + return } const columns = useMemo( From fc156488ee5cb60d6007c147b7b9e0d81bc104ba Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:52:56 -0500 Subject: [PATCH 36/76] fixed imports from tags models refactoring --- superset/models/dashboard.py | 3 +-- superset/models/slice.py | 3 +-- superset/models/sql_lab.py | 3 +-- superset/tags/api.py | 2 +- superset/tags/commands/create.py | 2 +- superset/tags/dao.py | 2 +- superset/views/all_entities.py | 2 +- superset/views/base_api.py | 2 +- 8 files changed, 8 insertions(+), 11 deletions(-) diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index 4c41e1430c274..393a3df12cce4 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -53,7 +53,6 @@ from superset.models.filter_set import FilterSet from superset.models.helpers import AuditMixinNullable, ImportExportMixin from superset.models.slice import Slice -from superset.models.tags import DashboardUpdater, Tag from superset.models.user_attributes import UserAttribute from superset.tasks.thumbnails import cache_dashboard_thumbnail from superset.utils import core as utils @@ -150,7 +149,7 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin): ) owners = relationship(security_manager.user_model, secondary=dashboard_user) tags = relationship( - Tag, + "Tag", secondary="tagged_object", primaryjoin="and_(Dashboard.id == TaggedObject.object_id)", secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " diff --git a/superset/models/slice.py b/superset/models/slice.py index 901583961db11..8d65b563d155b 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -42,7 +42,6 @@ from superset import db, is_feature_enabled, security_manager from superset.legacy import update_time_range from superset.models.helpers import AuditMixinNullable, ImportExportMixin -from superset.models.tags import ChartUpdater, Tag from superset.tasks.thumbnails import cache_chart_thumbnail from superset.utils import core as utils from superset.utils.hashing import md5_sha_from_str @@ -99,7 +98,7 @@ class Slice( # pylint: disable=too-many-public-methods ) owners = relationship(security_manager.user_model, secondary=slice_user) tags = relationship( - Tag, + "Tag", secondary="tagged_object", primaryjoin="and_(Slice.id == TaggedObject.object_id)", secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py index cdd88c34c6def..1d7dab2677f1f 100644 --- a/superset/models/sql_lab.py +++ b/superset/models/sql_lab.py @@ -49,7 +49,6 @@ ExtraJSONMixin, ImportExportMixin, ) -from superset.models.tags import QueryUpdater, Tag from superset.sql_parse import CtasMethod, ParsedQuery, Table from superset.sqllab.limiting_factor import LimitingFactor from superset.utils.core import GenericDataType, QueryStatus, user_label @@ -366,7 +365,7 @@ class SavedQuery(Model, AuditMixinNullable, ExtraJSONMixin, ImportExportMixin): rows = Column(Integer, nullable=True) last_run = Column(DateTime, nullable=True) tags = relationship( - Tag, + "Tag", secondary="tagged_object", primaryjoin="and_(SavedQuery.id == TaggedObject.object_id)", secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " diff --git a/superset/tags/api.py b/superset/tags/api.py index 473e0e7b16dc4..22b23518cb76d 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -24,7 +24,7 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.extensions import event_logger -from superset.models.tags import ObjectTypes, Tag +from superset.tags.models import ObjectTypes, Tag from superset.tags.commands.create import CreateTagCommand from superset.tags.commands.exceptions import ( TagCreateFailedError, diff --git a/superset/tags/commands/create.py b/superset/tags/commands/create.py index ecee9de7502db..c02a248d6a575 100644 --- a/superset/tags/commands/create.py +++ b/superset/tags/commands/create.py @@ -21,7 +21,7 @@ from superset.commands.base import BaseCommand, CreateMixin from superset.dao.exceptions import DAOCreateFailedError -from superset.models.tags import ObjectTypes +from superset.tags.models import ObjectTypes from superset.tags.commands.exceptions import ( TagCreateFailedError, TagInvalidError, diff --git a/superset/tags/dao.py b/superset/tags/dao.py index 7e2e0985a40dc..e043ccc910a8e 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -20,7 +20,7 @@ from superset import security_manager from superset.dao.base import BaseDAO from superset.extensions import db -from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes logger = logging.getLogger(__name__) diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index 739db5db1f546..17f292a9bd449 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -33,7 +33,7 @@ from superset.models.dashboard import Dashboard from superset.models.slice import Slice from superset.models.sql_lab import SavedQuery -from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes from superset.superset_typing import FlaskResponse from superset.views.base import SupersetModelView diff --git a/superset/views/base_api.py b/superset/views/base_api.py index c9249ff847cdf..ad4299762e0c4 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -35,7 +35,7 @@ from superset.models.core import FavStar from superset.models.dashboard import Dashboard from superset.models.slice import Slice -from superset.models.tags import Tag +from superset.tags.models import Tag from superset.schemas import error_payload_content from superset.sql_lab import Query as SqllabQuery from superset.stats_logger import BaseStatsLogger From 7a2144bd48f180c823a9b26e03885448366e793b Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 1 Dec 2022 15:31:28 -0500 Subject: [PATCH 37/76] fixed initialization and tag list view --- .../src/components/ObjectTags/index.tsx | 4 +- .../components/PropertiesModal/index.tsx | 4 +- .../components/PropertiesModal/index.tsx | 4 +- superset-frontend/src/tags.ts | 40 +- .../src/views/CRUD/tags/TagCard.tsx | 248 +++--- .../src/views/CRUD/tags/TagList.tsx | 712 ++++++++---------- superset/initialization/__init__.py | 19 - superset/views/tags.py | 22 +- 8 files changed, 488 insertions(+), 565 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 88dfe69ba58e7..3e219a405236b 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -29,7 +29,7 @@ import { ClientErrorObject, getClientErrorObject, } from 'src/utils/getClientErrorObject'; -import { deleteTag, fetchTags } from 'src/tags'; +import { deleteTaggedObjects, fetchTags } from 'src/tags'; export interface ObjectTagsProps { objectType: string; @@ -137,7 +137,7 @@ export const ObjectTags = ({ }, [objectType, objectId, includeTypes]); const onDelete = (tagIndex: number) => { - deleteTag( + deleteTaggedObjects( { objectType, objectId }, tags[tagIndex], () => setTags(tags.filter((_, i) => i !== tagIndex)), diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 7ed82bccce2ab..3f4856fa24770 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -43,7 +43,7 @@ import withToasts from 'src/components/MessageToasts/withToasts'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { loadTags } from 'src/components/ObjectTags'; import TagType from 'src/types/TagType'; -import { addTag, deleteTag, fetchTags, OBJECT_TYPES } from 'src/tags'; +import { addTag, deleteTaggedObjects, fetchTags, OBJECT_TYPES } from 'src/tags'; import { TagsList } from 'src/components/Tags'; const StyledFormItem = styled(FormItem)` @@ -606,7 +606,7 @@ const PropertiesModal = ({ // delete tags that are in old tags, but not in new tags oldTags.map((tag: TagType) => { if (!newTags.some(t => t.name === tag.name)) { - deleteTag( + deleteTaggedObjects( { objectType: OBJECT_TYPES.DASHBOARD, objectId: dashboardId, diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index a122889d686be..31e24d3db521a 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -34,7 +34,7 @@ import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; import { loadTags } from 'src/components/ObjectTags'; -import { addTag, deleteTag, fetchTags, OBJECT_TYPES } from 'src/tags'; +import { addTag, deleteTaggedObjects, fetchTags, OBJECT_TYPES } from 'src/tags'; import TagType from 'src/types/TagType'; import { TagsList } from 'src/components/Tags'; @@ -264,7 +264,7 @@ function PropertiesModal({ // delete tags that are in old tags, but not in new tags oldTags.map((tag: TagType) => { if (!newTags.some(t => t.name === tag.name)) { - deleteTag( + deleteTaggedObjects( { objectType: OBJECT_TYPES.CHART, objectId: slice.slice_id, diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index 4d0a1580c1125..cf5a3432623ad 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -29,13 +29,12 @@ export function fetchAllTags( callback: (json: JsonObject) => void, error: (response: Response) => void, ) { - let url = `/tagview/tags/`; + const url = `/tagview/tags/`; SupersetClient.get({ endpoint: url }) .then(({ json }) => callback(json)) .catch(response => error(response)); } - export function fetchTags( { objectType, @@ -48,7 +47,9 @@ export function fetchTags( if (objectType === undefined || objectId === undefined) { throw new Error('Need to specify objectType and objectId'); } - SupersetClient.get({ endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/` }) + SupersetClient.get({ + endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/`, + }) .then(({ json }) => callback( json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), @@ -71,11 +72,11 @@ export function fetchSuggestions( .catch(response => error(response)); } -export function deleteTag( +export function deleteTaggedObjects( { objectType, objectId }: { objectType: string; objectId: number }, tag: Tag, callback: (text: string) => void, - error: (response: Response) => void, + error: (response: string) => void, ) { if (objectType === undefined || objectId === undefined) { throw new Error('Need to specify objectType and objectId'); @@ -85,8 +86,33 @@ export function deleteTag( body: JSON.stringify([tag.name]), parseMethod: 'text', }) - .then(({ text }) => callback(text)) - .catch(response => error(response)); + .then(({ text }) => + text ? callback(text) : callback('Successfully Deleted Tagged Objects'), + ) + .catch(response => { + const err_str = response.message; + return err_str ? error(err_str) : error('Error Deleting Tagged Objects'); + }); +} + +export function deleteTags( + tags: Tag[], + callback: (text: string) => void, + error: (response: string) => void, +) { + const tags_str = JSON.stringify(tags.map(tag => tag.name) as string[]); + SupersetClient.delete({ + endpoint: `/tagview/tags`, + body: tags_str, + parseMethod: 'text', + }) + .then(({ text }) => + text ? callback(text) : callback('Successfully Deleted Tag'), + ) + .catch(response => { + const err_str = response.message; + return err_str ? error(err_str) : error('Error Deleting Tag'); + }); } export function addTag( diff --git a/superset-frontend/src/views/CRUD/tags/TagCard.tsx b/superset-frontend/src/views/CRUD/tags/TagCard.tsx index ed4d82eba7150..617559a298070 100644 --- a/superset-frontend/src/views/CRUD/tags/TagCard.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagCard.tsx @@ -16,137 +16,117 @@ * specific language governing permissions and limitations * under the License. */ - import React from 'react'; - import { Link, useHistory } from 'react-router-dom'; - import { t, useTheme } from '@superset-ui/core'; - import { handleDashboardDelete, CardStyles } from 'src/views/CRUD/utils'; - import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; - import { AntdDropdown } from 'src/components'; - import { Menu } from 'src/components/Menu'; - import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; - import ListViewCard from 'src/components/ListViewCard'; - import Icons from 'src/components/Icons'; - import Label from 'src/components/Label'; - import FacePile from 'src/components/FacePile'; - import FaveStar from 'src/components/FaveStar'; - import { Tag } from 'src/views/CRUD/types'; - - interface TagCardProps { - tag: Tag; - hasPerm: (name: string) => boolean; - bulkSelectEnabled: boolean; - refreshData: () => void; - loading: boolean; - addDangerToast: (msg: string) => void; - addSuccessToast: (msg: string) => void; - openTagEditModal?: (t: Tag) => void; - tagFilter?: string; - userId?: string | number; - showThumbnails?: boolean; - } - - function TagCard({ - tag, - hasPerm, - bulkSelectEnabled, - tagFilter, - refreshData, - userId, - addDangerToast, - addSuccessToast, - openTagEditModal, - showThumbnails, - }: TagCardProps) { - const history = useHistory(); - const canEdit = hasPerm('can_write'); - const canDelete = hasPerm('can_write'); - const canExport = hasPerm('can_export'); - - const theme = useTheme(); - const menu = ( - - {canEdit && openTagEditModal && ( - -
- openTagEditModal && openTagEditModal(tag) - } - data-test="dashboard-card-option-edit-button" - > - {t('Edit')} -
-
- )} - {canDelete && ( - - - {t('Are you sure you want to delete')}{' '} - {tag.name}? - - } - onConfirm={() => - handleTagDelete( - tag, - refreshData, - addSuccessToast, - addDangerToast, - tagFilter, - userId, - ) - } - > - {confirmDelete => ( -
- {t('Delete')} -
- )} -
-
- )} -
- ); - return ( - - - ) : null - } - url={undefined} - linkComponent={Link} - imgFallbackURL="/static/assets/images/dashboard-card-fallback.svg" - description={t('Modified %s', tag.changed_on)} - actions={ - { - e.stopPropagation(); - e.preventDefault(); - }} - > - - - - - } - /> - - ); - } - - export default TagCard; - \ No newline at end of file +import React from 'react'; +import { Link, useHistory } from 'react-router-dom'; +import { t, useTheme } from '@superset-ui/core'; +import { handleDashboardDelete, CardStyles } from 'src/views/CRUD/utils'; +import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; +import { AntdDropdown } from 'src/components'; +import { Menu } from 'src/components/Menu'; +import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import ListViewCard from 'src/components/ListViewCard'; +import Icons from 'src/components/Icons'; +import Label from 'src/components/Label'; +import FacePile from 'src/components/FacePile'; +import FaveStar from 'src/components/FaveStar'; +import { Tag } from 'src/views/CRUD/types'; + +interface TagCardProps { + tag: Tag; + hasPerm: (name: string) => boolean; + bulkSelectEnabled: boolean; + refreshData: () => void; + loading: boolean; + addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; + tagFilter?: string; + userId?: string | number; + showThumbnails?: boolean; +} + +function TagCard({ + tag, + hasPerm, + bulkSelectEnabled, + tagFilter, + refreshData, + userId, + addDangerToast, + addSuccessToast, + showThumbnails, +}: TagCardProps) { + const history = useHistory(); + const canDelete = hasPerm('can_write'); + const canExport = hasPerm('can_export'); + + const theme = useTheme(); + const menu = ( + + {canDelete && ( + + + {t('Are you sure you want to delete')} {tag.name}? + + } + onConfirm={() => + handleTagDelete( + tag, + refreshData, + addSuccessToast, + addDangerToast, + tagFilter, + userId, + ) + } + > + {confirmDelete => ( +
+ {t('Delete')} +
+ )} +
+
+ )} +
+ ); + return ( + + + ) : null + } + url={undefined} + linkComponent={Link} + imgFallbackURL="/static/assets/images/dashboard-card-fallback.svg" + description={t('Modified %s', tag.changed_on)} + actions={ + { + e.stopPropagation(); + e.preventDefault(); + }} + > + + + + + } + /> + + ); +} + +export default TagCard; diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx index 844ad8c51cdf1..3c6ca081a1843 100644 --- a/superset-frontend/src/views/CRUD/tags/TagList.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -16,410 +16,326 @@ * specific language governing permissions and limitations * under the License. */ - import { SupersetClient, t } from '@superset-ui/core'; - import React, { useState, useMemo, useCallback } from 'react'; - import rison from 'rison'; - import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; - import { - createFetchRelated, - createErrorHandler, - handleDashboardDelete, - Actions, - } from 'src/views/CRUD/utils'; - import { useListViewResource } from 'src/views/CRUD/hooks'; - import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; - import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; - import ListView, { - ListViewProps, - Filters, - FilterOperator, - } from 'src/components/ListView'; - import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers'; - import withToasts from 'src/components/MessageToasts/withToasts'; - import Icons from 'src/components/Icons'; - import PropertiesModal from 'src/dashboard/components/PropertiesModal'; - import { Tooltip } from 'src/components/Tooltip'; - import TagCard from './TagCard'; -import { Tag } from '../types'; +import { t } from '@superset-ui/core'; +import React, { useMemo, useCallback } from 'react'; +import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; +import { + createFetchRelated, + createErrorHandler, + Actions, +} from 'src/views/CRUD/utils'; +import { useListViewResource } from 'src/views/CRUD/hooks'; +import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import SubMenu, { SubMenuProps } from 'src/views/components/SubMenu'; +import ListView, { + ListViewProps, + Filters, + FilterOperator, +} from 'src/components/ListView'; +import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers'; +import withToasts from 'src/components/MessageToasts/withToasts'; +import Icons from 'src/components/Icons'; +import { Tooltip } from 'src/components/Tooltip'; import FacePile from 'src/components/FacePile'; import { Link } from 'react-router-dom'; - - const PAGE_SIZE = 25; - - interface TagListProps { - addDangerToast: (msg: string) => void; - addSuccessToast: (msg: string) => void; - user: { - userId: string | number; - firstName: string; - lastName: string; - }; - } - - function TagList(props: TagListProps) { - const { - addDangerToast, - addSuccessToast, - user: { userId }, - } = props; - - const { - state: { - loading, - resourceCount: tagCount, - resourceCollection: tags, - bulkSelectEnabled, - }, - setResourceCollection: setTags, - hasPerm, - fetchData, - toggleBulkSelect, - refreshData, - } = useListViewResource( - 'tag', - t('tag'), - addDangerToast, - ); - - const [tagToEdit, setTagToEdit] = useState( - null, - ); - - // TODO: Fix usage of localStorage keying on the user id - const userKey = dangerouslyGetItemDoNotUse(userId?.toString(), null); - - const canCreate = hasPerm('can_write'); - const canEdit = hasPerm('can_write'); - const canDelete = hasPerm('can_write'); +import { deleteTags } from 'src/tags'; +import { Tag } from '../types'; +import TagCard from './TagCard'; + +const PAGE_SIZE = 25; + +interface TagListProps { + addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; + user: { + userId: string | number; + firstName: string; + lastName: string; + }; +} + +function TagList(props: TagListProps) { + const { + addDangerToast, + addSuccessToast, + user: { userId }, + } = props; + + const { + state: { + loading, + resourceCount: tagCount, + resourceCollection: tags, + bulkSelectEnabled, + }, + setResourceCollection: setTags, + hasPerm, + fetchData, + toggleBulkSelect, + refreshData, + } = useListViewResource('tag', t('tag'), addDangerToast); + + // TODO: Fix usage of localStorage keying on the user id + const userKey = dangerouslyGetItemDoNotUse(userId?.toString(), null); + + const canDelete = hasPerm('can_write'); + + const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; - const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; - - function openTagEditModal(tag: Tag) { - setTagToEdit(tag); - } - - function handleTagEdit(edits: Tag) { - /* TODO: - what permissions need to be checked here? - */ - return - } - - function handleBulkTagDelete(tagsToDelete: Tag[]) { - // TODO what permissions need to be checked here? - return - } - - const columns = useMemo( - () => [ - { - Cell: ({ - row: { - original: { - name: tagName, - }, - }, - }: any) => ( - - {tagName} - + function handleTagsDelete( + tags: Tag[], + callback: (text: string) => void, + error: (text: string) => void, + ) { + // TODO what permissions need to be checked here? + deleteTags(tags, callback, error); + refreshData(); + } + + const columns = useMemo( + () => [ + { + Cell: ({ + row: { + original: { name: tagName }, + }, + }: any) => ( + {tagName} ), - Header: t('Name'), - accessor: 'name', - }, - { - Cell: ({ - row: { - original: { - changed_by_name: changedByName, - changed_by_url: changedByUrl, - }, - }, - }: any) => {changedByName}, - Header: t('Modified by'), - accessor: 'changed_by.first_name', - size: 'xl', - }, - { - Cell: ({ - row: { - original: { changed_on_delta_humanized: changedOn }, - }, - }: any) => {changedOn}, - Header: t('Modified'), - accessor: 'changed_on_delta_humanized', - size: 'xl', - }, - { - Cell: ({ - row: { - original: { created_by: createdBy }, - }, - }: any) => - createdBy ? : '', - Header: t('Created by'), - accessor: 'created_by', - disableSortBy: true, - size: 'xl', - }, - // { - // Cell: ({ - // row: { - // original: { num_tagged_objects: numTaggedObjects }, - // }, - // }: any) => numTaggedObjects, - // Header: t('Number of Tagged Entities'), - // accessor: 'num_tagged_objects', - // disableSortBy: true, - // }, - { - Cell: ({ row: { original } }: any) => { - const handleDelete = () => - handleDashboardDelete( - original, - refreshData, - addSuccessToast, - addDangerToast, - ); - const handleEdit = () => openTagEditModal(original); - - return ( - - {canDelete && ( - - {t('Are you sure you want to delete')}{' '} - {original.dashboard_title}? - - } - onConfirm={handleDelete} - > - {confirmDelete => ( - - + Header: t('Name'), + accessor: 'name', + }, + { + Cell: ({ + row: { + original: { + changed_by_name: changedByName, + changed_by_url: changedByUrl, + }, + }, + }: any) => {changedByName}, + Header: t('Modified by'), + accessor: 'changed_by.first_name', + size: 'xl', + }, + { + Cell: ({ + row: { + original: { changed_on_delta_humanized: changedOn }, + }, + }: any) => {changedOn}, + Header: t('Modified'), + accessor: 'changed_on_delta_humanized', + size: 'xl', + }, + { + Cell: ({ + row: { + original: { created_by: createdBy }, + }, + }: any) => (createdBy ? : ''), + Header: t('Created by'), + accessor: 'created_by', + disableSortBy: true, + size: 'xl', + }, + { + Cell: ({ row: { original } }: any) => { + const handleDelete = () => + handleTagsDelete([original], addSuccessToast, addDangerToast); + return ( + + {canDelete && ( + + {t('Are you sure you want to delete')}{' '} + {original.dashboard_title}? + + } + onConfirm={handleDelete} + > + {confirmDelete => ( + + {/* fix icon name */} - - - - )} - - )} - {canEdit && ( - - - - - - )} - - ); - }, - Header: t('Actions'), - id: 'actions', - hidden: !canEdit && !canDelete, - disableSortBy: true, - }, - ], - [ - userId, - canEdit, - canDelete, - refreshData, - addSuccessToast, - addDangerToast, - ], - ); - - const filters: Filters = useMemo(() => { - const filters_list = [ - { - Header: t('Created by'), - id: 'created_by', - input: 'select', - operator: FilterOperator.relationOneMany, - unfilteredLabel: t('All'), - fetchSelects: createFetchRelated( - 'tag', - 'created_by', - createErrorHandler(errMsg => - addDangerToast( - t( - 'An error occurred while fetching tag created by values: %s', - errMsg, - ), - ), - ), - props.user, - ), - paginate: true, - }, - { + + + + )} + + )} + + ); + }, + Header: t('Actions'), + id: 'actions', + hidden: !canDelete, + disableSortBy: true, + }, + ], + [userId, canDelete, refreshData, addSuccessToast, addDangerToast], + ); + + const filters: Filters = useMemo(() => { + const filters_list = [ + { + Header: t('Created by'), + id: 'created_by', + input: 'select', + operator: FilterOperator.relationOneMany, + unfilteredLabel: t('All'), + fetchSelects: createFetchRelated( + 'tag', + 'created_by', + createErrorHandler(errMsg => + addDangerToast( + t( + 'An error occurred while fetching tag created by values: %s', + errMsg, + ), + ), + ), + props.user, + ), + paginate: true, + }, + { Header: t('Search'), id: 'name', input: 'search', operator: FilterOperator.titleOrSlug, - } - ] as Filters; - return filters_list; - }, [addDangerToast, props.user]); - - const sortTypes = [ - { - desc: false, - id: 'name', - label: t('Alphabetical'), - value: 'alphabetical', - }, - { - desc: true, - id: 'changed_on_delta_humanized', - label: t('Recently modified'), - value: 'recently_modified', - }, - { - desc: false, - id: 'changed_on_delta_humanized', - label: t('Least recently modified'), - value: 'least_recently_modified', - }, - ]; - - const renderCard = useCallback( - (tag: Tag) => ( - - ), - [ - addDangerToast, - addSuccessToast, - bulkSelectEnabled, - hasPerm, - loading, - userId, - refreshData, - userKey, - ], - ); - - const subMenuButtons: SubMenuProps['buttons'] = []; - if (canDelete) { - subMenuButtons.push({ - name: t('Bulk select'), - buttonStyle: 'secondary', - 'data-test': 'bulk-select', - onClick: toggleBulkSelect, - }); - } - if (canCreate) { - subMenuButtons.push({ - name: ( - <> - {t('Tag')} - - ), - buttonStyle: 'primary', - onClick: () => { - // TODO Add Tags?? - }, - }); - } - return ( - <> - - - {confirmDelete => { - const bulkActions: ListViewProps['bulkActions'] = []; - if (canDelete) { - bulkActions.push({ - key: 'delete', - name: t('Delete'), - type: 'danger', - onSelect: confirmDelete, - }); - } - return ( - <> - {tagToEdit && ( - setTagToEdit(null)} - onSubmit={handleTagEdit} - /> - )} - - bulkActions={bulkActions} - bulkSelectEnabled={bulkSelectEnabled} - cardSortSelectOptions={sortTypes} - className="dashboard-list-view" - columns={columns} - count={tagCount} - data={tags} - disableBulkSelect={toggleBulkSelect} - fetchData={fetchData} - filters={filters} - initialSort={initialSort} - loading={loading} - pageSize={PAGE_SIZE} - showThumbnails={ - userKey - ? userKey.thumbnails - : isFeatureEnabled(FeatureFlag.THUMBNAILS) - } - renderCard={renderCard} - defaultViewMode={ - isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW) - ? 'card' - : 'table' - } - /> - - ); - }} - - - ); - } - - export default withToasts(TagList); - \ No newline at end of file + }, + ] as Filters; + return filters_list; + }, [addDangerToast, props.user]); + + const sortTypes = [ + { + desc: false, + id: 'name', + label: t('Alphabetical'), + value: 'alphabetical', + }, + { + desc: true, + id: 'changed_on_delta_humanized', + label: t('Recently modified'), + value: 'recently_modified', + }, + { + desc: false, + id: 'changed_on_delta_humanized', + label: t('Least recently modified'), + value: 'least_recently_modified', + }, + ]; + + const renderCard = useCallback( + (tag: Tag) => ( + + ), + [ + addDangerToast, + addSuccessToast, + bulkSelectEnabled, + hasPerm, + loading, + userId, + refreshData, + userKey, + ], + ); + + const subMenuButtons: SubMenuProps['buttons'] = []; + if (canDelete) { + subMenuButtons.push({ + name: t('Bulk select'), + buttonStyle: 'secondary', + 'data-test': 'bulk-select', + onClick: toggleBulkSelect, + }); + } + + const handleBulkDelete = (tagsToDelete: Tag[]) => + handleTagsDelete(tagsToDelete, addSuccessToast, addDangerToast); + + return ( + <> + + + {confirmDelete => { + const bulkActions: ListViewProps['bulkActions'] = []; + if (canDelete) { + bulkActions.push({ + key: 'delete', + name: t('Delete'), + type: 'danger', + onSelect: confirmDelete, + }); + } + return ( + <> + + bulkActions={bulkActions} + bulkSelectEnabled={bulkSelectEnabled} + cardSortSelectOptions={sortTypes} + className="dashboard-list-view" + columns={columns} + count={tagCount} + data={tags.filter(tag => !tag.name.includes(':'))} + disableBulkSelect={toggleBulkSelect} + fetchData={fetchData} + filters={filters} + initialSort={initialSort} + loading={loading} + pageSize={PAGE_SIZE} + showThumbnails={ + userKey + ? userKey.thumbnails + : isFeatureEnabled(FeatureFlag.THUMBNAILS) + } + renderCard={renderCard} + defaultViewMode={ + isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW) + ? 'card' + : 'table' + } + /> + + ); + }} + + + ); +} + +export default withToasts(TagList); diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 6d0c8d379b022..d5e40c9d0abcb 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -363,25 +363,6 @@ def init_views(self) -> None: category="SQL Lab", category_label=__("SQL Lab"), ) - appbuilder.add_view( - DatabaseView, - "Databases", - label=__("Databases"), - icon="fa-database", - category="Data", - category_label=__("Data"), - category_icon="fa-database", - ) - appbuilder.add_link( - "Datasets", - label=__("Datasets"), - href="/tablemodelview/list/", - icon="fa-table", - category="Data", - category_label=__("Data"), - category_icon="fa-table", - ) - appbuilder.add_separator("Data") appbuilder.add_view( TaggedObjectsModelView, "All Entities", diff --git a/superset/views/tags.py b/superset/views/tags.py index 300835ebcf18b..4008857d40382 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -159,7 +159,7 @@ def post( # pylint: disable=no-self-use @has_access_api @expose("/tags///", methods=["DELETE"]) - def delete( # pylint: disable=no-self-use + def delete_tagged_objects( # pylint: disable=no-self-use self, object_type: ObjectTypes, object_id: int ) -> FlaskResponse: """Remove tags from an object.""" @@ -178,6 +178,26 @@ def delete( # pylint: disable=no-self-use return Response(status=204) # 204 NO CONTENT + @has_access_api + @expose("/tags", methods=["DELETE"]) + def delete_tags( # pylint: disable=no-self-use + self + ) -> FlaskResponse: + """Remove tags, and all tagged objects with that tag """ + tag_names = request.get_json(force=True) + if not tag_names: + return Response(status=403) + db.session() + db.session.query(TaggedObject).filter( + TaggedObject.tag.has(Tag.name.in_(tag_names)), + ).delete(synchronize_session=False) + db.session.query(Tag).filter( + Tag.name.in_(tag_names), + ).delete(synchronize_session=False) + db.session.commit() + + return Response(status=204) # 204 NO CONTENT + @has_access_api @expose("/tagged_objects/", methods=["GET", "POST"]) def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use From 7a5d8f7870eafbff5a14e2837964c1a7d2494b1e Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:44:31 -0500 Subject: [PATCH 38/76] lint fixes --- .../src/components/ObjectTags/index.tsx | 8 +-- .../src/components/ObjectTags/styles.ts | 58 +++++++++---------- superset-frontend/src/components/Tags/Tag.tsx | 6 +- .../components/PropertiesModal/index.tsx | 34 +++++------ .../components/ExploreChartHeader/index.jsx | 1 - .../components/PropertiesModal/index.tsx | 35 ++++++----- .../views/CRUD/allentities/AllEntities.tsx | 1 - .../CRUD/allentities/AllEntitiesTable.tsx | 5 +- .../src/views/CRUD/chart/ChartList.tsx | 2 +- .../views/CRUD/dashboard/DashboardList.tsx | 4 +- superset-frontend/src/views/routes.tsx | 5 +- 11 files changed, 80 insertions(+), 79 deletions(-) diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx index 3e219a405236b..1f613a4e7c8be 100644 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ b/superset-frontend/src/components/ObjectTags/index.tsx @@ -21,7 +21,6 @@ import React, { useEffect, useState } from 'react'; import { styled, SupersetClient, t } from '@superset-ui/core'; import Tag from 'src/types/TagType'; -import { objectTagsStyles } from './styles'; import { TagsList } from 'src/components/Tags'; import rison from 'rison'; import { cacheWrapper } from 'src/utils/cacheWrapper'; @@ -30,6 +29,7 @@ import { getClientErrorObject, } from 'src/utils/getClientErrorObject'; import { deleteTaggedObjects, fetchTags } from 'src/tags'; +import { objectTagsStyles } from './styles'; export interface ObjectTagsProps { objectType: string; @@ -141,7 +141,7 @@ export const ObjectTags = ({ { objectType, objectId }, tags[tagIndex], () => setTags(tags.filter((_, i) => i !== tagIndex)), - (error) => { + error => { throw new Error(t('An Error occured when deleting the tag')); }, ); @@ -150,9 +150,7 @@ export const ObjectTags = ({ return ( - + css` +export const objectTagsStyles = (theme: SupersetTheme) => css` .ant-tag { color: ${theme.colors.grayscale.dark2}; } - + .react-tags { position: relative; display: inline-block; @@ -31,19 +32,19 @@ margin: 0 ${theme.gridUnit * 2.5}px; border: 0px solid #f5f5f5; border-radius: 1px; - + /* shared font styles */ font-size: ${theme.gridUnit * 3}px; line-height: 1.2; - + /* clicking anywhere will focus the input */ cursor: text; } - + .react-tags__selected { display: inline; } - + .react-tags__selected-tag { display: inline-block; box-sizing: border-box; @@ -52,57 +53,57 @@ border: 0px solid #f5f5f5; border-radius: ${theme.borderRadius}px; background: #f1f1f1; - + /* match the font styles */ font-size: inherit; line-height: inherit; } - + .react-tags__search { display: inline-block; - + /* new tag border layout */ border: 1px dashed #d9d9d9; - + /* match tag layout */ line-height: ${theme.gridUnit * 5}px; margin-bottom: 0; padding: 0 ${theme.gridUnit * 1.75}px; - + /* prevent autoresize overflowing the container */ max-width: 100%; } - + .react-tags__search:focus-within { border: 1px solid ${theme.colors.grayscale.dark2}; } - + @media screen and (min-width: ${theme.gridUnit * 7.5}em) { .react-tags__search { /* this will become the offsetParent for suggestions */ position: relative; } } - + .react-tags__search input { max-width: 150%; - + /* remove styles and layout from this element */ margin: 0; margin-left: 0; padding: 0; border: 0; outline: none; - + /* match the font styles */ font-size: inherit; line-height: inherit; } - + .react-tags__search input::-ms-clear { display: none; } - + .react-tags__suggestions { position: absolute; top: 100%; @@ -110,13 +111,13 @@ width: 100%; z-index: ${theme.zIndex.max}; } - + @media screen and (min-width: ${theme.gridUnit * 7.5}em) { .react-tags__suggestions { width: ${theme.gridUnit * 60}px; } } - + .react-tags__suggestions ul { margin: 4px -1px; padding: 0; @@ -126,30 +127,29 @@ border-radius: 2px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } - + .react-tags__suggestions li { border-bottom: 1px solid #ddd; - padding: ${ theme.gridUnit * 1.5}px ${theme.gridUnit * 2}px; + padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 2}px; } - + .react-tags__suggestions li mark { text-decoration: underline; background: none; font-weight: ${theme.typography.weights.bold}; } - + .react-tags__suggestions li:hover { cursor: pointer; background: #eee; } - + .react-tags__suggestions li.is-active { background: #b7cfe0; } - + .react-tags__suggestions li.is-disabled { opacity: calc(${theme.opacity.mediumHeavy}); cursor: auto; } - `; - \ No newline at end of file +`; diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index f75b19f0db020..db072feba5804 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -55,7 +55,11 @@ const Tag = ({ ) : ( {id ? ( - + {isLongTag ? `${name.slice(0, 20)}...` : name} ) : isLongTag ? ( diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 3f4856fa24770..448d99b472f1d 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -109,15 +109,12 @@ const PropertiesModal = ({ const categoricalSchemeRegistry = getCategoricalSchemeRegistry(); const tagsAsSelectValues = useMemo(() => { - const selectTags = tags.map((tag) => { - return { - value:tag.name, - label:tag.name - } - }); + const selectTags = tags.map(tag => ({ + value: tag.name, + label: tag.name, + })); return selectTags; - }, [tags.length]) - + }, [tags.length]); const handleErrorResponse = async (response: Response) => { const { error, statusText, message } = await getClientErrorObject(response); @@ -376,7 +373,7 @@ const PropertiesModal = ({ includeTypes: false, }, (currentTags: TagType[]) => updateTags(currentTags, tags), - (error) => { + error => { handleErrorResponse(error); }, ); @@ -577,18 +574,19 @@ const PropertiesModal = ({ includeTypes: false, }, (tags: TagType[]) => setTags(tags), - () => { + (error: Response) => { handleErrorResponse(error); }, ); } catch (error: any) { - console.log(error); + handleErrorResponse(error); } }, [dashboardId]); const updateTags = (oldTags: TagType[], newTags: TagType[]) => { // update the tags for this object // add tags that are in new tags, but not in old tags + // eslint-disable-next-line array-callback-return newTags.map((tag: TagType) => { if (!oldTags.some(t => t.name === tag.name)) { addTag( @@ -604,6 +602,7 @@ const PropertiesModal = ({ } }); // delete tags that are in old tags, but not in new tags + // eslint-disable-next-line array-callback-return oldTags.map((tag: TagType) => { if (!newTags.some(t => t.name === tag.name)) { deleteTaggedObjects( @@ -614,19 +613,18 @@ const PropertiesModal = ({ tag, () => {}, () => {}, - ) + ); } }); - } + }; const handleChangeTags = (values: { label: string; value: number }[]) => { // triggered whenever a new tag is selected or a tag was deselected // on new tag selected, add the tag - - const uniqueTags = [...new Set(values.map((v => v.label)))]; - setTags([...uniqueTags.map(t => ({name: t}))]) - return; - } + + const uniqueTags = [...new Set(values.map(v => v.label))]; + setTags([...uniqueTags.map(t => ({ name: t }))]); + }; return ( ([]); const tagsAsSelectValues = useMemo(() => { - const selectTags = tags.map((tag) => { - return { - value:tag.name, - label:tag.name - } - }); + const selectTags = tags.map(tag => ({ + value: tag.name, + label: tag.name, + })); return selectTags; - }, [tags.length]) + }, [tags.length]); function showError({ error, statusText, message }: any) { let errorText = error || statusText || t('An error has occurred'); @@ -180,8 +178,8 @@ function PropertiesModal({ includeTypes: false, }, (currentTags: TagType[]) => updateTags(currentTags, tags), - (error) => { - showError(error) + error => { + showError(error); }, ); } catch (error: any) { @@ -235,7 +233,7 @@ function PropertiesModal({ includeTypes: false, }, (tags: TagType[]) => setTags(tags), - (error) => { + error => { showError(error); }, ); @@ -247,6 +245,7 @@ function PropertiesModal({ const updateTags = (oldTags: TagType[], newTags: TagType[]) => { // update the tags for this object // add tags that are in new tags, but not in old tags + // eslint-disable-next-line array-callback-return newTags.map((tag: TagType) => { if (!oldTags.some(t => t.name === tag.name)) { addTag( @@ -262,6 +261,7 @@ function PropertiesModal({ } }); // delete tags that are in old tags, but not in new tags + // eslint-disable-next-line array-callback-return oldTags.map((tag: TagType) => { if (!newTags.some(t => t.name === tag.name)) { deleteTaggedObjects( @@ -272,23 +272,22 @@ function PropertiesModal({ tag, () => {}, () => {}, - ) + ); } }); - } + }; const handleChangeTags = (values: { label: string; value: number }[]) => { // triggered whenever a new tag is selected or a tag was deselected // on new tag selected, add the tag - - const uniqueTags = [...new Set(values.map((v => v.label)))]; - setTags([...uniqueTags.map(t => ({name: t}))]) - return; - } + + const uniqueTags = [...new Set(values.map(v => v.label))]; + setTags([...uniqueTags.map(t => ({ name: t }))]); + }; const handleClearTags = () => { setTags([]); - } + }; return ( (); const [tagsQuery, setTagsQuery] = useQueryParam('tags', StringParam); diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx index d5fb262e2e7ee..6a204abee940e 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx @@ -55,7 +55,9 @@ interface AllEntitiesTableProps { search?: string; } -export default function AllEntitiesTable({ search = '' }: AllEntitiesTableProps) { +export default function AllEntitiesTable({ + search = '', +}: AllEntitiesTableProps) { const [objects, setObjects] = useState({ dashboard: [], chart: [], @@ -76,7 +78,6 @@ export default function AllEntitiesTable({ search = '' }: AllEntitiesTableProps) console.log(error.json()); }, ); - }, [search]); const renderTable = (type: any) => { diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index e46a3b22dfe1e..572306a1a0877 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -70,9 +70,9 @@ import CertifiedBadge from 'src/components/CertifiedBadge'; import { GenericLink } from 'src/components/GenericLink/GenericLink'; import { bootstrapData } from 'src/preamble'; import Owner from 'src/types/Owner'; -import ChartCard from './ChartCard'; import { OBJECT_TYPES } from 'src/tags'; import { loadTags } from 'src/components/ObjectTags'; +import ChartCard from './ChartCard'; const FlexRowContainer = styled.div` align-items: center; diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 3ec7e88d62af2..31f76c52d77e0 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -53,9 +53,9 @@ import ImportModelsModal from 'src/components/ImportModal/index'; import Dashboard from 'src/dashboard/containers/Dashboard'; import CertifiedBadge from 'src/components/CertifiedBadge'; import { bootstrapData } from 'src/preamble'; +import { loadTags } from 'src/components/ObjectTags'; import DashboardCard from './DashboardCard'; import { DashboardStatus } from './types'; -import { loadTags } from 'src/components/ObjectTags'; const PAGE_SIZE = 25; const PASSWORDS_NEEDED_MESSAGE = t( @@ -566,7 +566,7 @@ function DashboardList(props: DashboardListProps) { input: 'select', operator: FilterOperator.chartTags, unfilteredLabel: t('All'), - fetchSelects: loadTags + fetchSelects: loadTags, }); } filters_list.push({ diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx index 5097b370ccb51..2821fad71b288 100644 --- a/superset-frontend/src/views/routes.tsx +++ b/superset-frontend/src/views/routes.tsx @@ -112,7 +112,10 @@ const SavedQueryList = lazy( ), ); const AllEntitiesPage = lazy( - () => import(/* webpackChunkName: "AllEntities" */ 'src/views/CRUD/allentities/AllEntities'), + () => + import( + /* webpackChunkName: "AllEntities" */ 'src/views/CRUD/allentities/AllEntities' + ), ); const TagsPage = lazy( () => import(/* webpackChunkName: "TagList" */ 'src/views/CRUD/tags/TagList'), From 06820db3bbedf319e9ce433cc96d60d80d0920fb Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:51:46 -0500 Subject: [PATCH 39/76] undid acidental changes --- .../components/ExploreChartHeader/index.jsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index c36c2a0577e8d..958aa16a31994 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -206,7 +206,6 @@ export const ExploreChartHeader = ({ }, [metadata, slice?.description]); const oldSliceName = slice?.slice_name; - return ( <> - ) : null +
+ {sliceFormData ? ( + + ) : null} + {metadataBar} +
} rightPanelAdditionalItems={ Date: Fri, 2 Dec 2022 14:31:43 -0500 Subject: [PATCH 40/76] pylint errors --- superset/charts/schemas.py | 47 ++++++++++++++++++++---------- superset/connectors/sqla/models.py | 1 - superset/dashboards/schemas.py | 18 ++++++++---- superset/tags/api.py | 3 +- superset/tags/commands/create.py | 3 +- superset/tags/dao.py | 13 ++++++--- superset/tags/models.py | 8 +++-- superset/views/tags.py | 13 ++++++--- 8 files changed, 69 insertions(+), 37 deletions(-) diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 12c62052c1243..1195c61e82861 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -149,7 +149,7 @@ } -class TagSchema(Schema): +class TagSchema(Schema): id = fields.Int() name = fields.String() type = fields.String() @@ -171,7 +171,8 @@ class ChartEntityResponseSchema(Schema): form_data = fields.Dict(description=form_data_description) slice_url = fields.String(description=slice_url_description) certified_by = fields.String(description=certified_by_description) - certification_details = fields.String(description=certification_details_description) + certification_details = fields.String( + description=certification_details_description) class ChartPostSchema(Schema): @@ -182,7 +183,8 @@ class ChartPostSchema(Schema): slice_name = fields.String( description=slice_name_description, required=True, validate=Length(1, 250) ) - description = fields.String(description=description_description, allow_none=True) + description = fields.String( + description=description_description, allow_none=True) viz_type = fields.String( description=viz_type_description, validate=Length(0, 250), @@ -203,7 +205,8 @@ class ChartPostSchema(Schema): cache_timeout = fields.Integer( description=cache_timeout_description, allow_none=True ) - datasource_id = fields.Integer(description=datasource_id_description, required=True) + datasource_id = fields.Integer( + description=datasource_id_description, required=True) datasource_type = fields.String( description=datasource_type_description, validate=validate.OneOf(choices=[ds.value for ds in DatasourceType]), @@ -212,8 +215,10 @@ class ChartPostSchema(Schema): datasource_name = fields.String( description=datasource_name_description, allow_none=True ) - dashboards = fields.List(fields.Integer(description=dashboards_description)) - certified_by = fields.String(description=certified_by_description, allow_none=True) + dashboards = fields.List(fields.Integer( + description=dashboards_description)) + certified_by = fields.String( + description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) @@ -229,7 +234,8 @@ class ChartPutSchema(Schema): slice_name = fields.String( description=slice_name_description, allow_none=True, validate=Length(0, 250) ) - description = fields.String(description=description_description, allow_none=True) + description = fields.String( + description=description_description, allow_none=True) viz_type = fields.String( description=viz_type_description, allow_none=True, @@ -255,8 +261,10 @@ class ChartPutSchema(Schema): validate=validate.OneOf(choices=[ds.value for ds in DatasourceType]), allow_none=True, ) - dashboards = fields.List(fields.Integer(description=dashboards_description)) - certified_by = fields.String(description=certified_by_description, allow_none=True) + dashboards = fields.List(fields.Integer( + description=dashboards_description)) + certified_by = fields.String( + description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) @@ -918,14 +926,16 @@ class AnnotationLayerSchema(Schema): keys=fields.String( desciption="Name of property to be overridden", validate=validate.OneOf( - choices=("granularity", "time_grain_sqla", "time_range", "time_shift"), + choices=("granularity", "time_grain_sqla", + "time_range", "time_shift"), ), ), values=fields.Raw(allow_none=True), description="which properties should be overridable", allow_none=True, ) - show = fields.Boolean(description="Should the layer be shown", required=True) + show = fields.Boolean( + description="Should the layer be shown", required=True) showLabel = fields.Boolean( description="Should the label always be shown", allow_none=True, @@ -998,7 +1008,8 @@ class Meta: # pylint: disable=too-few-public-methods unknown = EXCLUDE datasource = fields.Nested(ChartDataDatasourceSchema, allow_none=True) - result_type = EnumField(ChartDataResultType, by_value=True, allow_none=True) + result_type = EnumField(ChartDataResultType, + by_value=True, allow_none=True) annotation_layers = fields.List( fields.Nested(AnnotationLayerSchema), @@ -1015,7 +1026,8 @@ class Meta: # pylint: disable=too-few-public-methods "if defined in datasource", allow_none=True, ) - filters = fields.List(fields.Nested(ChartDataFilterSchema), allow_none=True) + filters = fields.List(fields.Nested( + ChartDataFilterSchema), allow_none=True) granularity = fields.String( description="Name of temporal column used for time filtering. For legacy Druid " "datasources this defines the time grain.", @@ -1141,7 +1153,8 @@ class Meta: # pylint: disable=too-few-public-methods ( fields.Raw( validate=[ - Length(min=1, error=_("orderby column must be populated")) + Length(min=1, error=_( + "orderby column must be populated")) ], allow_none=False, ), @@ -1312,7 +1325,8 @@ class ChartDataResponseResult(Schema): allow_none=False, ) data = fields.List(fields.Dict(), description="A list with results") - colnames = fields.List(fields.String(), description="A list of column names") + colnames = fields.List( + fields.String(), description="A list of column names") coltypes = fields.List( fields.Integer(), description="A list of generic data types of each column" ) @@ -1376,7 +1390,8 @@ class ImportV1ChartSchema(Schema): slice_name = fields.String(required=True) viz_type = fields.String(required=True) params = fields.Dict() - query_context = fields.String(allow_none=True, validate=utils.validate_json) + query_context = fields.String( + allow_none=True, validate=utils.validate_json) cache_timeout = fields.Integer(allow_none=True) uuid = fields.UUID(required=True) version = fields.String(required=True) diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 7447be0c0096a..95d1b2f5837b4 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -40,7 +40,6 @@ import dateutil.parser import numpy as np import pandas as pd -from pyrsistent import v import sqlalchemy as sa import sqlparse from flask import escape, Markup diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index 2892b4929b530..7bb6c50f068ff 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -147,7 +147,8 @@ class RolesSchema(Schema): id = fields.Int() name = fields.String() -class TagSchema(Schema): + +class TagSchema(Schema): id = fields.Int() name = fields.String() type = fields.String() @@ -164,7 +165,8 @@ class DashboardGetResponseSchema(Schema): json_metadata = fields.String(description=json_metadata_description) position_json = fields.String(description=position_json_description) certified_by = fields.String(description=certified_by_description) - certification_details = fields.String(description=certification_details_description) + certification_details = fields.String( + description=certification_details_description) changed_by_name = fields.String() changed_by_url = fields.String() changed_by = fields.Nested(UserSchema) @@ -254,7 +256,8 @@ class DashboardPostSchema(BaseDashboardSchema): validate=validate_json_metadata, ) published = fields.Boolean(description=published_description) - certified_by = fields.String(description=certified_by_description, allow_none=True) + certified_by = fields.String( + description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) @@ -274,7 +277,8 @@ class DashboardPutSchema(BaseDashboardSchema): owners = fields.List( fields.Integer(description=owners_description, allow_none=True) ) - roles = fields.List(fields.Integer(description=roles_description, allow_none=True)) + roles = fields.List(fields.Integer( + description=roles_description, allow_none=True)) position_json = fields.String( description=position_json_description, allow_none=True, validate=validate_json ) @@ -284,8 +288,10 @@ class DashboardPutSchema(BaseDashboardSchema): allow_none=True, validate=validate_json_metadata, ) - published = fields.Boolean(description=published_description, allow_none=True) - certified_by = fields.String(description=certified_by_description, allow_none=True) + published = fields.Boolean( + description=published_description, allow_none=True) + certified_by = fields.String( + description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) diff --git a/superset/tags/api.py b/superset/tags/api.py index 22b23518cb76d..03599236bd932 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=too-many-lines import logging from flask import request, Response @@ -105,7 +104,7 @@ def __repr__(self) -> str: action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.post", log_to_statsd=False, ) - def post(self, object_type: ObjectTypes, object_id: int) -> Response: + def add_new_tags(self, object_type: ObjectTypes, object_id: int) -> Response: """Adds new tags to an object. --- post: diff --git a/superset/tags/commands/create.py b/superset/tags/commands/create.py index c02a248d6a575..be4ad99c04b4b 100644 --- a/superset/tags/commands/create.py +++ b/superset/tags/commands/create.py @@ -40,7 +40,8 @@ def __init__(self, object_type: ObjectTypes, object_id: int, data: Dict[str, Any def run(self) -> Model: self.validate() try: - tag = TagDAO.create_tagged_objects(self._object_type, self._object_id, self._properties) + tag = TagDAO.create_tagged_objects( + self._object_type, self._object_id, self._properties) except DAOCreateFailedError as ex: logger.exception(ex.exception) raise TagCreateFailedError() from ex diff --git a/superset/tags/dao.py b/superset/tags/dao.py index e043ccc910a8e..aff05551c7bf6 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -17,7 +17,6 @@ import logging from typing import Any, Dict -from superset import security_manager from superset.dao.base import BaseDAO from superset.extensions import db from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes @@ -30,7 +29,11 @@ class TagDAO(BaseDAO): # base_filter = TagAccessFilter @staticmethod - def create_tagged_objects(object_type: ObjectTypes, object_id: int, properties: Dict[str, Any]) -> None: + def create_tagged_objects( + object_type: ObjectTypes, + object_id: int, + properties: Dict[str, Any] + ) -> None: tag_names = properties["tags"] tagged_objects = [] @@ -43,7 +46,8 @@ def create_tagged_objects(object_type: ObjectTypes, object_id: int, properties: tag = TagDAO.get_by_name(name, type_) tagged_objects.append( - TaggedObject(object_id=object_id, object_type=object_type, tag=tag) + TaggedObject(object_id=object_id, + object_type=object_type, tag=tag) ) db.session.add_all(tagged_objects) @@ -53,7 +57,8 @@ def create_tagged_objects(object_type: ObjectTypes, object_id: int, properties: @staticmethod def get_by_name(name: str, type_: str) -> Tag: - tag = db.session.query(Tag).filter(Tag.name == name, Tag.type == type_).first() + tag = db.session.query(Tag).filter( + Tag.name == name, Tag.type == type_).first() if not tag: tag = Tag(name=name, type=type_) # security_manager.raise_for_tag_access(tag) diff --git a/superset/tags/models.py b/superset/tags/models.py index da1b895d8bb32..283cdfede2616 100644 --- a/superset/tags/models.py +++ b/superset/tags/models.py @@ -92,7 +92,8 @@ class TaggedObject(Model, AuditMixinNullable): __tablename__ = "tagged_object" id = Column(Integer, primary_key=True) tag_id = Column(Integer, ForeignKey("tag.id")) - object_id = Column(Integer, ForeignKey("dashboards.id"), ForeignKey("slices.id"), ForeignKey("saved_query.id")) + object_id = Column(Integer, ForeignKey("dashboards.id"), + ForeignKey("slices.id"), ForeignKey("saved_query.id")) object_type = Column(Enum(ObjectTypes)) tag = relationship("Tag", backref="objects") @@ -157,7 +158,8 @@ def after_insert( cls._add_owners(session, target) # add `type:` tags - tag = get_tag("type:{0}".format(cls.object_type), session, TagTypes.type) + tag = get_tag("type:{0}".format(cls.object_type), + session, TagTypes.type) tagged_object = TaggedObject( tag_id=tag.id, object_id=target.id, object_type=cls.object_type ) @@ -270,7 +272,7 @@ def after_delete( cls, _mapper: Mapper, connection: Connection, target: FavStar ) -> None: session = Session(bind=connection) - name = "favorited_by:{0}".format(target.user_id) + name = f'favorited_by:{target.user_id}' query = ( session.query(TaggedObject.id) .join(Tag) diff --git a/superset/views/tags.py b/superset/views/tags.py index 4008857d40382..fd4535f275b7d 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -50,6 +50,7 @@ def process_template(content: str) -> str: } return template.render(context) + class TagModelView(SupersetModelView): route_base = "/superset/tags" datamodel = SQLAInterface(Tag) @@ -63,6 +64,7 @@ def list(self) -> FlaskResponse: return super().render_app_template() + class TagView(BaseSupersetView): @staticmethod def is_enabled() -> bool: @@ -108,7 +110,7 @@ def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use ) tags = [{"id": id, "name": name} for id, name in query] return json_success(json.dumps(tags)) - + @has_access_api @expose("/tags///", methods=["GET"]) def get( # pylint: disable=no-self-use @@ -144,12 +146,14 @@ def post( # pylint: disable=no-self-use else: type_ = TagTypes.custom - tag = db.session.query(Tag).filter_by(name=name, type=type_).first() + tag = db.session.query(Tag).filter_by( + name=name, type=type_).first() if not tag: tag = Tag(name=name, type=type_) tagged_objects.append( - TaggedObject(object_id=object_id, object_type=object_type, tag=tag) + TaggedObject(object_id=object_id, + object_type=object_type, tag=tag) ) db.session.add_all(tagged_objects) @@ -210,7 +214,8 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use return json_success(json.dumps([])) # filter types - types = [type_ for type_ in request.args.get("types", "").split(",") if type_] + types = [type_ for type_ in request.args.get( + "types", "").split(",") if type_] results: List[Dict[str, Any]] = [] From 8970fba1adafd1856d40a52c5bfb7aeb39b9b381 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 5 Dec 2022 09:23:46 -0500 Subject: [PATCH 41/76] suggestion fixes and removing unused object tags --- .../src/components/ObjectTags/index.tsx | 165 ------------------ .../src/components/ObjectTags/styles.ts | 155 ---------------- .../src/components/Tags/TagsList.tsx | 5 +- .../components/PropertiesModal/index.tsx | 1 - 4 files changed, 2 insertions(+), 324 deletions(-) delete mode 100644 superset-frontend/src/components/ObjectTags/index.tsx delete mode 100644 superset-frontend/src/components/ObjectTags/styles.ts diff --git a/superset-frontend/src/components/ObjectTags/index.tsx b/superset-frontend/src/components/ObjectTags/index.tsx deleted file mode 100644 index 1f613a4e7c8be..0000000000000 --- a/superset-frontend/src/components/ObjectTags/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/** - * 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, { useEffect, useState } from 'react'; -import { styled, SupersetClient, t } from '@superset-ui/core'; -import Tag from 'src/types/TagType'; - -import { TagsList } from 'src/components/Tags'; -import rison from 'rison'; -import { cacheWrapper } from 'src/utils/cacheWrapper'; -import { - ClientErrorObject, - getClientErrorObject, -} from 'src/utils/getClientErrorObject'; -import { deleteTaggedObjects, fetchTags } from 'src/tags'; -import { objectTagsStyles } from './styles'; - -export interface ObjectTagsProps { - objectType: string; - objectId: number; - includeTypes: boolean; - editMode: boolean; - maxTags: number | undefined; - onChange?: (tags: Tag[]) => void; -} - -const StyledTagsDiv = styled.div` - margin-left: ${({ theme }) => theme.gridUnit * 2}px; - max-width: 100%; - display: -webkit-flex; - display: flex; - -webkit-flex-direction: row; - -webkit-flex-wrap: wrap; -`; - -const localCache = new Map(); - -const cachedSupersetGet = cacheWrapper( - SupersetClient.get, - localCache, - ({ endpoint }) => endpoint || '', -); - -type SelectTagsValue = { - value: string | number | undefined; - label: string; -}; - -export const tagToSelectOption = ( - item: Tag & { table_name: string }, -): SelectTagsValue => ({ - value: item.id, - label: item.name, -}); - -export const loadTags = async ( - search: string, - page: number, - pageSize: number, -) => { - const searchColumn = 'name'; - const query = rison.encode({ - filters: [{ col: searchColumn, opr: 'ct', value: search }], - page, - page_size: pageSize, - order_column: searchColumn, - order_direction: 'asc', - }); - - const getErrorMessage = ({ error, message }: ClientErrorObject) => { - let errorText = message || error || t('An error has occurred'); - if (message === 'Forbidden') { - errorText = t('You do not have permission to edit this dashboard'); - } - return errorText; - }; - - return cachedSupersetGet({ - endpoint: `/api/v1/tag/?q=${query}`, - }) - .then(response => { - const data: { - label: string; - value: string | number; - }[] = response.json.result - .filter((item: Tag & { table_name: string }) => item.type === 1) - .map(tagToSelectOption); - return { - data, - totalCount: response.json.count, - }; - }) - .catch(async error => { - const errorMessage = getErrorMessage(await getClientErrorObject(error)); - throw new Error(errorMessage); - }); -}; - -export const ObjectTags = ({ - objectType, - objectId, - includeTypes, - editMode = false, - maxTags = undefined, - onChange, -}: ObjectTagsProps) => { - const [tags, setTags] = useState([]); - - useEffect(() => { - try { - fetchTags( - { objectType, objectId, includeTypes }, - (tags: Tag[]) => setTags(tags), - () => { - throw new Error(t('An Error occured when fetching tags')); - }, - ); - } catch (error: any) { - throw new Error(t('An Error occured when fetching tags')); - } - }, [objectType, objectId, includeTypes]); - - const onDelete = (tagIndex: number) => { - deleteTaggedObjects( - { objectType, objectId }, - tags[tagIndex], - () => setTags(tags.filter((_, i) => i !== tagIndex)), - error => { - throw new Error(t('An Error occured when deleting the tag')); - }, - ); - onChange?.(tags); - }; - - return ( - - - - - - ); -}; - -export default ObjectTags; diff --git a/superset-frontend/src/components/ObjectTags/styles.ts b/superset-frontend/src/components/ObjectTags/styles.ts deleted file mode 100644 index 4ff3cfcd9a3e4..0000000000000 --- a/superset-frontend/src/components/ObjectTags/styles.ts +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable theme-colors/no-literal-colors */ -/** - * 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 { css, SupersetTheme } from '@superset-ui/core'; - -export const objectTagsStyles = (theme: SupersetTheme) => css` - .ant-tag { - color: ${theme.colors.grayscale.dark2}; - } - - .react-tags { - position: relative; - display: inline-block; - padding: 1px 0 0 1px; - margin: 0 ${theme.gridUnit * 2.5}px; - border: 0px solid #f5f5f5; - border-radius: 1px; - - /* shared font styles */ - font-size: ${theme.gridUnit * 3}px; - line-height: 1.2; - - /* clicking anywhere will focus the input */ - cursor: text; - } - - .react-tags__selected { - display: inline; - } - - .react-tags__selected-tag { - display: inline-block; - box-sizing: border-box; - margin: 0; - padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 2}px; - border: 0px solid #f5f5f5; - border-radius: ${theme.borderRadius}px; - background: #f1f1f1; - - /* match the font styles */ - font-size: inherit; - line-height: inherit; - } - - .react-tags__search { - display: inline-block; - - /* new tag border layout */ - border: 1px dashed #d9d9d9; - - /* match tag layout */ - line-height: ${theme.gridUnit * 5}px; - margin-bottom: 0; - padding: 0 ${theme.gridUnit * 1.75}px; - - /* prevent autoresize overflowing the container */ - max-width: 100%; - } - - .react-tags__search:focus-within { - border: 1px solid ${theme.colors.grayscale.dark2}; - } - - @media screen and (min-width: ${theme.gridUnit * 7.5}em) { - .react-tags__search { - /* this will become the offsetParent for suggestions */ - position: relative; - } - } - - .react-tags__search input { - max-width: 150%; - - /* remove styles and layout from this element */ - margin: 0; - margin-left: 0; - padding: 0; - border: 0; - outline: none; - - /* match the font styles */ - font-size: inherit; - line-height: inherit; - } - - .react-tags__search input::-ms-clear { - display: none; - } - - .react-tags__suggestions { - position: absolute; - top: 100%; - left: 0; - width: 100%; - z-index: ${theme.zIndex.max}; - } - - @media screen and (min-width: ${theme.gridUnit * 7.5}em) { - .react-tags__suggestions { - width: ${theme.gridUnit * 60}px; - } - } - - .react-tags__suggestions ul { - margin: 4px -1px; - padding: 0; - list-style: none; - background: white; - border: 1px solid #d1d1d1; - border-radius: 2px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); - } - - .react-tags__suggestions li { - border-bottom: 1px solid #ddd; - padding: ${theme.gridUnit * 1.5}px ${theme.gridUnit * 2}px; - } - - .react-tags__suggestions li mark { - text-decoration: underline; - background: none; - font-weight: ${theme.typography.weights.bold}; - } - - .react-tags__suggestions li:hover { - cursor: pointer; - background: #eee; - } - - .react-tags__suggestions li.is-active { - background: #b7cfe0; - } - - .react-tags__suggestions li.is-disabled { - opacity: calc(${theme.opacity.mediumHeavy}); - cursor: auto; - } -`; diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index 4f763299da9d1..028ae5c5a832f 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -36,10 +36,9 @@ export type TagsListProps = { const TagsDiv = styled.div` max-width: 100%; - display: -webkit-flex; display: flex; - -webkit-flex-direction: row; - -webkit-flex-wrap: wrap; + flex-direction: row; + flex-wrap: wrap; `; const TagsList = ({ diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index ed96a6f53574f..dc22c10a80af1 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -36,7 +36,6 @@ import withToasts from 'src/components/MessageToasts/withToasts'; import { loadTags } from 'src/components/ObjectTags'; import { addTag, deleteTaggedObjects, fetchTags, OBJECT_TYPES } from 'src/tags'; import TagType from 'src/types/TagType'; -import { TagsList } from 'src/components/Tags'; export type PropertiesModalProps = { slice: Slice; From 74733e5941524c9b5a994bca24d4d935b5b240f4 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 5 Dec 2022 10:24:22 -0500 Subject: [PATCH 42/76] added TagList stories file --- .../src/components/Tags/TagsList.stories.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 superset-frontend/src/components/Tags/TagsList.stories.tsx diff --git a/superset-frontend/src/components/Tags/TagsList.stories.tsx b/superset-frontend/src/components/Tags/TagsList.stories.tsx new file mode 100644 index 0000000000000..0bfe27b42a17a --- /dev/null +++ b/superset-frontend/src/components/Tags/TagsList.stories.tsx @@ -0,0 +1,58 @@ +/** + * 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 TagType from 'src/types/TagType'; +import { TagsList } from '.'; +import { TagsListProps } from './TagsList'; + +export default { + title: 'Tags', + component: TagsList, +}; + +export const InteractiveTags = ({ tags, editable, maxTags }: TagsListProps) => ( + +); + +const tags: TagType[] = [ + { name: 'tag1' }, + { name: 'tag2' }, + { name: 'tag3' }, + { name: 'tag4' }, + { name: 'tag5' }, + { name: 'tag6' }, +]; + +const editable = true; + +const maxTags = 3; + +InteractiveTags.args = { + tags, + editable, + maxTags, +}; + +InteractiveTags.story = { + parameters: { + knobs: { + disable: true, + }, + }, +}; From 4ca90da1aa26e859af5f056d8012a9ca2ae25c7b Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 6 Dec 2022 10:12:29 -0500 Subject: [PATCH 43/76] fixed package-lock --- superset-frontend/package-lock.json | 1753 +++++++++++++++++---------- 1 file changed, 1102 insertions(+), 651 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index c31184b040afe..34dc1c17845ae 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -88,7 +88,7 @@ "match-sorter": "^6.1.0", "memoize-one": "^5.1.1", "moment": "^2.26.0", - "moment-timezone": "^0.5.33", + "moment-timezone": "^0.5.37", "mousetrap": "^1.6.1", "mustache": "^2.2.1", "polished": "^3.7.2", @@ -273,7 +273,7 @@ "webpack": "^5.52.1", "webpack-bundle-analyzer": "^4.4.2", "webpack-cli": "^4.8.0", - "webpack-dev-server": "^4.2.0", + "webpack-dev-server": "^4.10.1", "webpack-manifest-plugin": "^4.0.2", "webpack-sources": "^3.2.0" }, @@ -6600,6 +6600,12 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "devOptional": true + }, "node_modules/@lerna/add": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-4.0.0.tgz", @@ -8829,15 +8835,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@lerna/publish/node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@lerna/publish/node_modules/node-gyp": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", @@ -10837,16 +10834,6 @@ "devOptional": true, "peer": true }, - "node_modules/@npmcli/run-script/node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@npmcli/run-script/node_modules/node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -17866,6 +17853,25 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "devOptional": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cheerio": { "version": "0.22.21", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz", @@ -17892,6 +17898,25 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "devOptional": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, "node_modules/@types/d3": { "version": "3.5.38", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.38.tgz", @@ -18020,6 +18045,29 @@ "devOptional": true, "peer": true }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "devOptional": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "devOptional": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "node_modules/@types/fetch-mock": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/@types/fetch-mock/-/fetch-mock-7.3.5.tgz", @@ -18082,9 +18130,9 @@ "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" }, "node_modules/@types/http-proxy": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", - "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", "devOptional": true, "dependencies": { "@types/node": "*" @@ -18192,6 +18240,12 @@ "@types/unist": "*" } }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "devOptional": true + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -18287,6 +18341,12 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "devOptional": true + }, "node_modules/@types/react": { "version": "16.9.43", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz", @@ -18497,6 +18557,25 @@ "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz", "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==" }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "devOptional": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "devOptional": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@types/shortid": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", @@ -18524,6 +18603,15 @@ "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", "dev": true }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -18654,6 +18742,15 @@ "node": ">=0.10.0" } }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", @@ -19806,12 +19903,12 @@ "integrity": "sha512-9jN7+BijYKWO8fxfcG7QZh7js6V+g3OjkxMRHfKWNjjs85048VY4cd27Uoe6yk55P66L/z7Dflu5+YEApgMzkA==" }, "node_modules/accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -20057,7 +20154,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, + "devOptional": true, "dependencies": { "ajv": "^8.0.0" }, @@ -20074,7 +20171,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, + "devOptional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -20090,7 +20187,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "devOptional": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -22176,18 +22273,16 @@ "node": ">= 0.6" } }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "node_modules/bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", "devOptional": true, "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", + "array-flatten": "^2.1.2", "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, "node_modules/boolbase": { @@ -22558,12 +22653,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "devOptional": true - }, "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -23155,9 +23244,15 @@ } }, "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -24109,9 +24204,9 @@ "dev": true }, "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "devOptional": true, "engines": { "node": ">=0.8" @@ -26872,23 +26967,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "devOptional": true, - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-equal-ident": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", @@ -27385,26 +27463,19 @@ "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", "devOptional": true }, "node_modules/dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "devOptional": true, - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", "devOptional": true, "dependencies": { - "buffer-indexof": "^1.0.0" + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/doctrine": { @@ -29741,39 +29812,11 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "node_modules/express/node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -30474,14 +30517,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/finalhandler/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -33018,9 +33053,9 @@ } }, "node_modules/http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "devOptional": true }, "node_modules/http-proxy": { @@ -33075,12 +33110,12 @@ "devOptional": true }, "node_modules/http-proxy-middleware": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", - "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "devOptional": true, "dependencies": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", @@ -33088,6 +33123,14 @@ }, "engines": { "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, "node_modules/http-proxy-middleware/node_modules/braces": { @@ -33136,13 +33179,13 @@ } }, "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "devOptional": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -33701,33 +33744,6 @@ "resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz", "integrity": "sha1-610Ql7dUL0x56jBg067gfQU4gPQ=" }, - "node_modules/internal-ip": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", - "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", - "devOptional": true, - "dependencies": { - "default-gateway": "^6.0.0", - "ipaddr.js": "^1.9.1", - "is-ip": "^3.1.0", - "p-event": "^4.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/internal-ip?sponsor=1" - } - }, - "node_modules/internal-ip/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "devOptional": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -34086,27 +34102,6 @@ "node": ">=8" } }, - "node_modules/is-ip": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", - "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", - "devOptional": true, - "dependencies": { - "ip-regex": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-ip/node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -34197,20 +34192,12 @@ "node": ">=4" } }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "devOptional": true, + "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -39757,11 +39744,11 @@ } }, "node_modules/memfs": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.4.tgz", - "integrity": "sha512-2mDCPhuduRPOxlfgsXF9V+uqC6Jgz8zt/bNe4d4W7d5f6pCzHrWkxLNr17jKGXd4+j2kQNsAG2HARPnt74sqVQ==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.7.tgz", + "integrity": "sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==", "dependencies": { - "fs-monkey": "1.0.3" + "fs-monkey": "^1.0.3" }, "engines": { "node": ">= 4.0.0" @@ -40465,19 +40452,19 @@ } }, "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.49.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -40923,9 +40910,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.5.33", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", - "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "version": "0.5.37", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz", + "integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==", "dependencies": { "moment": ">= 2.9.0" }, @@ -41006,24 +40993,18 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "devOptional": true, "dependencies": { - "dns-packet": "^1.3.1", + "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "devOptional": true - }, "node_modules/multimatch": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", @@ -41177,9 +41158,9 @@ } }, "node_modules/negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } @@ -41335,12 +41316,12 @@ } }, "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "devOptional": true, "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" } }, "node_modules/node-gyp": { @@ -41997,15 +41978,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "devOptional": true }, - "node_modules/npm-registry-fetch/node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/npm-registry-fetch/node_modules/socks-proxy-agent": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", @@ -43268,9 +43240,9 @@ } }, "node_modules/parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "engines": { "node": ">= 0.8" } @@ -43399,9 +43371,9 @@ "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { "node": ">=8.6" }, @@ -43566,45 +43538,6 @@ "node": ">=10" } }, - "node_modules/portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "devOptional": true, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "devOptional": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "devOptional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/portfinder/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true - }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -47931,7 +47864,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -48282,12 +48215,15 @@ "devOptional": true }, "node_modules/selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "devOptional": true, "dependencies": { - "node-forge": "^0.10.0" + "node-forge": "^1" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver": { @@ -48432,14 +48368,6 @@ "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -48852,16 +48780,25 @@ } }, "node_modules/sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "devOptional": true, "dependencies": { "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "devOptional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", @@ -53051,36 +52988,40 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.2.0.tgz", - "integrity": "sha512-iBaDkHBLfW3cEITeJWNkjZBrm+b5A3YLg8XVdNOdjUNABdXJwcsJv4dzKSnVf1q4Ch489+6epWVW6OcOyVfG7w==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.10.1.tgz", + "integrity": "sha512-FIzMq3jbBarz3ld9l7rbM7m6Rj1lOsgq/DyLGMX/fPEB1UBUPtf5iL/4eNfhx8YYJTRlzfv107UfWSWcBK5Odw==", "devOptional": true, "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", "ansi-html-community": "^0.0.8", - "bonjour": "^3.5.0", - "chokidar": "^3.5.1", - "colorette": "^1.2.2", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "del": "^6.0.0", - "express": "^4.17.1", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", "graceful-fs": "^4.2.6", "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.0", - "internal-ip": "^6.2.0", + "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", "open": "^8.0.9", "p-retry": "^4.5.0", - "portfinder": "^1.0.28", - "schema-utils": "^3.1.0", - "selfsigned": "^1.10.11", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", "serve-index": "^1.9.1", - "sockjs": "^0.3.21", + "sockjs": "^0.3.24", "spdy": "^4.0.2", - "strip-ansi": "^7.0.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^5.1.0", - "ws": "^8.1.0" + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" @@ -53088,6 +53029,10 @@ "engines": { "node": ">= 12.13.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, "peerDependencies": { "webpack": "^4.37.0 || ^5.0.0" }, @@ -53097,40 +53042,201 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "devOptional": true, - "engines": { - "node": ">=12" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/webpack-dev-server/node_modules/del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "devOptional": true, "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "devOptional": true + }, + "node_modules/webpack-dev-server/node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "devOptional": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/webpack-dev-server/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "devOptional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "devOptional": true + }, + "node_modules/webpack-dev-server/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "devOptional": true, + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "devOptional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "devOptional": true, + "engines": { + "node": ">= 0.8" } }, + "node_modules/webpack-dev-server/node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "devOptional": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/webpack-dev-server/node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "devOptional": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "devOptional": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "devOptional": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "devOptional": true + }, "node_modules/webpack-dev-server/node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -53143,6 +53249,30 @@ "node": ">=8" } }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true + }, + "node_modules/webpack-dev-server/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true + }, + "node_modules/webpack-dev-server/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "devOptional": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/webpack-dev-server/node_modules/open": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/open/-/open-8.2.1.tgz", @@ -53160,41 +53290,155 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webpack-dev-server/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/webpack-dev-server/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "devOptional": true + }, + "node_modules/webpack-dev-server/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "devOptional": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { - "node": ">=8" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/webpack-dev-server/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "node_modules/webpack-dev-server/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "devOptional": true, "dependencies": { - "ansi-regex": "^6.0.1" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" }, "engines": { - "node": ">=12" + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "devOptional": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "devOptional": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "devOptional": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "devOptional": true + }, + "node_modules/webpack-dev-server/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "devOptional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "devOptional": true, + "engines": { + "node": ">=0.6" } }, "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.1.0.tgz", - "integrity": "sha512-oT660AR1gOnU/NTdUQi3EiGR0iXG7CFxmKsj3ylWCBA2khJ8LFHK+sKv3BZEsC11gl1eChsltRhzUq7nWj7XIQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", "devOptional": true, "dependencies": { - "colorette": "^1.2.2", - "memfs": "^3.2.2", + "colorette": "^2.0.10", + "memfs": "^3.4.3", "mime-types": "^2.1.31", "range-parser": "^1.2.1", - "schema-utils": "^3.1.0" + "schema-utils": "^4.0.0" }, "engines": { "node": ">= 12.13.0" @@ -57000,6 +57244,7 @@ "@ant-design/icons": "^4.2.2", "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", + "lodash": "^4.17.11", "prop-types": "*", "react": "^16.13.1", "react-dom": "^16.13.1" @@ -57021,9 +57266,13 @@ "regenerator-runtime": "^0.13.7", "xss": "^1.0.10" }, + "devDependencies": { + "@testing-library/react": "^11.2.0" + }, "peerDependencies": { "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", + "@types/classnames": "*", "@types/react": "*", "react": "^16.13.1", "react-dom": "^16.13.1" @@ -57051,6 +57300,7 @@ "peerDependencies": { "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", + "@types/lodash": "*", "@types/react": "*", "react": "^16.13.1" } @@ -62017,6 +62267,12 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "devOptional": true + }, "@lerna/add": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-4.0.0.tgz", @@ -63757,12 +64013,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, "node-gyp": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", @@ -65379,13 +65629,6 @@ "devOptional": true, "peer": true }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "devOptional": true, - "peer": true - }, "node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -70957,6 +71200,7 @@ "version": "file:plugins/plugin-chart-table", "requires": { "@react-icons/all-files": "^4.1.0", + "@testing-library/react": "^11.2.0", "@types/d3-array": "^2.9.0", "@types/enzyme": "^3.10.5", "@types/react-table": "^7.0.29", @@ -71458,6 +71702,25 @@ "@babel/types": "^7.3.0" } }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "devOptional": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "devOptional": true, + "requires": { + "@types/node": "*" + } + }, "@types/cheerio": { "version": "0.22.21", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz", @@ -71484,6 +71747,25 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "devOptional": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "devOptional": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, "@types/d3": { "version": "3.5.38", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.38.tgz", @@ -71612,6 +71894,29 @@ "devOptional": true, "peer": true }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "devOptional": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "devOptional": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/fetch-mock": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/@types/fetch-mock/-/fetch-mock-7.3.5.tgz", @@ -71674,9 +71979,9 @@ "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" }, "@types/http-proxy": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", - "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", "devOptional": true, "requires": { "@types/node": "*" @@ -71784,6 +72089,12 @@ "@types/unist": "*" } }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "devOptional": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -71878,6 +72189,12 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "devOptional": true + }, "@types/react": { "version": "16.9.43", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz", @@ -72090,6 +72407,25 @@ "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz", "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==" }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "devOptional": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "devOptional": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, "@types/shortid": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", @@ -72117,6 +72453,15 @@ "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", "dev": true }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "devOptional": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -72238,6 +72583,15 @@ } } }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "devOptional": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", @@ -73121,12 +73475,12 @@ "integrity": "sha512-9jN7+BijYKWO8fxfcG7QZh7js6V+g3OjkxMRHfKWNjjs85048VY4cd27Uoe6yk55P66L/z7Dflu5+YEApgMzkA==" }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "ace-builds": { @@ -73321,7 +73675,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, + "devOptional": true, "requires": { "ajv": "^8.0.0" }, @@ -73330,7 +73684,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, + "devOptional": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -73342,7 +73696,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "devOptional": true } } }, @@ -74947,18 +75301,16 @@ } } }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", "devOptional": true, "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", + "array-flatten": "^2.1.2", "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, "boolbase": { @@ -75249,12 +75601,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "devOptional": true - }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -75704,9 +76050,9 @@ } }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -76462,9 +76808,9 @@ "dev": true }, "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "devOptional": true }, "console-browserify": { @@ -78596,20 +78942,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "devOptional": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, "deep-equal-ident": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz", @@ -79001,26 +79333,16 @@ "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", "devOptional": true }, "dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "devOptional": true, - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", "devOptional": true, "requires": { - "buffer-indexof": "^1.0.0" + "@leichtgewicht/ip-codec": "^2.0.1" } }, "doctrine": { @@ -80866,30 +81188,11 @@ "vary": "~1.1.2" }, "dependencies": { - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -81448,11 +81751,6 @@ "unpipe": "~1.0.0" }, "dependencies": { - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -83365,9 +83663,9 @@ } }, "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "devOptional": true }, "http-proxy": { @@ -83410,12 +83708,12 @@ } }, "http-proxy-middleware": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", - "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "devOptional": true, "requires": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", @@ -83453,13 +83751,13 @@ "devOptional": true }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "devOptional": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "to-regex-range": { @@ -83877,26 +84175,6 @@ "resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz", "integrity": "sha1-610Ql7dUL0x56jBg067gfQU4gPQ=" }, - "internal-ip": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", - "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", - "devOptional": true, - "requires": { - "default-gateway": "^6.0.0", - "ipaddr.js": "^1.9.1", - "is-ip": "^3.1.0", - "p-event": "^4.2.0" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "devOptional": true - } - } - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -84159,23 +84437,6 @@ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "devOptional": true }, - "is-ip": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", - "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", - "devOptional": true, - "requires": { - "ip-regex": "^4.0.0" - }, - "dependencies": { - "ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "devOptional": true - } - } - }, "is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -84238,17 +84499,12 @@ "symbol-observable": "^1.1.0" } }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "devOptional": true - }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "devOptional": true + "dev": true, + "peer": true }, "is-plain-obj": { "version": "1.1.0", @@ -88579,11 +88835,11 @@ } }, "memfs": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.4.tgz", - "integrity": "sha512-2mDCPhuduRPOxlfgsXF9V+uqC6Jgz8zt/bNe4d4W7d5f6pCzHrWkxLNr17jKGXd4+j2kQNsAG2HARPnt74sqVQ==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.7.tgz", + "integrity": "sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==", "requires": { - "fs-monkey": "1.0.3" + "fs-monkey": "^1.0.3" } }, "memoize-one": { @@ -89017,16 +89273,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.49.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -89372,9 +89628,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.33", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", - "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "version": "0.5.37", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz", + "integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==", "requires": { "moment": ">= 2.9.0" } @@ -89444,21 +89700,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "devOptional": true, "requires": { - "dns-packet": "^1.3.1", + "dns-packet": "^5.2.2", "thunky": "^1.0.2" } }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "devOptional": true - }, "multimatch": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", @@ -89583,9 +89833,9 @@ } }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "neo-async": { "version": "2.6.2", @@ -89729,9 +89979,9 @@ } }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "devOptional": true }, "node-gyp": { @@ -90255,12 +90505,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "devOptional": true }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "devOptional": true - }, "socks-proxy-agent": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", @@ -91240,9 +91484,9 @@ } }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascal-case": { "version": "3.1.2", @@ -91349,9 +91593,9 @@ "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "3.0.0", @@ -91467,43 +91711,6 @@ "@babel/runtime": "^7.12.5" } }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "devOptional": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "devOptional": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "devOptional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true - } - } - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -94804,7 +95011,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "devOptional": true }, "require-main-filename": { "version": "2.0.0", @@ -95087,12 +95294,12 @@ "devOptional": true }, "selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "devOptional": true, "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1" } }, "semver": { @@ -95216,13 +95423,6 @@ "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.17.1" - }, - "dependencies": { - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - } } }, "set-blocking": { @@ -95552,14 +95752,22 @@ } }, "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "devOptional": true, "requires": { "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "devOptional": true + } } }, "socks": { @@ -98920,60 +99128,201 @@ } }, "webpack-dev-server": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.2.0.tgz", - "integrity": "sha512-iBaDkHBLfW3cEITeJWNkjZBrm+b5A3YLg8XVdNOdjUNABdXJwcsJv4dzKSnVf1q4Ch489+6epWVW6OcOyVfG7w==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.10.1.tgz", + "integrity": "sha512-FIzMq3jbBarz3ld9l7rbM7m6Rj1lOsgq/DyLGMX/fPEB1UBUPtf5iL/4eNfhx8YYJTRlzfv107UfWSWcBK5Odw==", "devOptional": true, "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", "ansi-html-community": "^0.0.8", - "bonjour": "^3.5.0", - "chokidar": "^3.5.1", - "colorette": "^1.2.2", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "del": "^6.0.0", - "express": "^4.17.1", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", "graceful-fs": "^4.2.6", "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.0", - "internal-ip": "^6.2.0", + "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", "open": "^8.0.9", "p-retry": "^4.5.0", - "portfinder": "^1.0.28", - "schema-utils": "^3.1.0", - "selfsigned": "^1.10.11", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", "serve-index": "^1.9.1", - "sockjs": "^0.3.21", + "sockjs": "^0.3.24", "spdy": "^4.0.2", - "strip-ansi": "^7.0.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^5.1.0", - "ws": "^8.1.0" + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" }, "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "devOptional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "devOptional": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "devOptional": true }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "devOptional": true, "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "devOptional": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "devOptional": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "devOptional": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "devOptional": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "devOptional": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "devOptional": true + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "devOptional": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "devOptional": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" } }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "devOptional": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "devOptional": true + }, "is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -98983,6 +99332,27 @@ "is-docker": "^2.0.0" } }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "devOptional": true, + "requires": { + "ee-first": "1.1.1" + } + }, "open": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/open/-/open-8.2.1.tgz", @@ -98994,32 +99364,113 @@ "is-wsl": "^2.2.0" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "devOptional": true }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "devOptional": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "devOptional": true, "requires": { - "ansi-regex": "^6.0.1" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "devOptional": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "devOptional": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "devOptional": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "devOptional": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "devOptional": true + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "devOptional": true + }, "webpack-dev-middleware": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.1.0.tgz", - "integrity": "sha512-oT660AR1gOnU/NTdUQi3EiGR0iXG7CFxmKsj3ylWCBA2khJ8LFHK+sKv3BZEsC11gl1eChsltRhzUq7nWj7XIQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", "devOptional": true, "requires": { - "colorette": "^1.2.2", - "memfs": "^3.2.2", + "colorette": "^2.0.10", + "memfs": "^3.4.3", "mime-types": "^2.1.31", "range-parser": "^1.2.1", - "schema-utils": "^3.1.0" + "schema-utils": "^4.0.0" } }, "ws": { From 2cab6ee234784875e8cf53e8f4e8079d8e6b8f18 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 6 Dec 2022 11:30:13 -0500 Subject: [PATCH 44/76] fixed import errors and changed tag crud so they display as antdtags --- .../src/components/Tags/utils.tsx | 91 +++++++++++++++++++ .../components/PropertiesModal/index.tsx | 3 +- .../components/PropertiesModal/index.tsx | 2 +- .../src/views/CRUD/chart/ChartList.tsx | 2 +- .../views/CRUD/dashboard/DashboardList.tsx | 2 +- .../src/views/CRUD/tags/TagList.tsx | 20 ++-- 6 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 superset-frontend/src/components/Tags/utils.tsx diff --git a/superset-frontend/src/components/Tags/utils.tsx b/superset-frontend/src/components/Tags/utils.tsx new file mode 100644 index 0000000000000..af2853c5c0b7b --- /dev/null +++ b/superset-frontend/src/components/Tags/utils.tsx @@ -0,0 +1,91 @@ +/** + * 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 { SupersetClient, t } from '@superset-ui/core'; +import Tag from 'src/types/TagType'; + +import rison from 'rison'; +import { cacheWrapper } from 'src/utils/cacheWrapper'; +import { + ClientErrorObject, + getClientErrorObject, +} from 'src/utils/getClientErrorObject'; + +const localCache = new Map(); + +const cachedSupersetGet = cacheWrapper( + SupersetClient.get, + localCache, + ({ endpoint }) => endpoint || '', +); + +type SelectTagsValue = { + value: string | number | undefined; + label: string; +}; + +export const tagToSelectOption = ( + item: Tag & { table_name: string }, +): SelectTagsValue => ({ + value: item.id, + label: item.name, +}); + +export const loadTags = async ( + search: string, + page: number, + pageSize: number, +) => { + const searchColumn = 'name'; + const query = rison.encode({ + filters: [{ col: searchColumn, opr: 'ct', value: search }], + page, + page_size: pageSize, + order_column: searchColumn, + order_direction: 'asc', + }); + + const getErrorMessage = ({ error, message }: ClientErrorObject) => { + let errorText = message || error || t('An error has occurred'); + if (message === 'Forbidden') { + errorText = t('You do not have permission to edit this dashboard'); + } + return errorText; + }; + + return cachedSupersetGet({ + endpoint: `/api/v1/tag/?q=${query}`, + }) + .then(response => { + const data: { + label: string; + value: string | number; + }[] = response.json.result + .filter((item: Tag & { table_name: string }) => item.type === 1) + .map(tagToSelectOption); + return { + data, + totalCount: response.json.count, + }; + }) + .catch(async error => { + const errorMessage = getErrorMessage(await getClientErrorObject(error)); + throw new Error(errorMessage); + }); +}; diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 448d99b472f1d..119140068f92c 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -41,10 +41,9 @@ import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeMo import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; -import { loadTags } from 'src/components/ObjectTags'; import TagType from 'src/types/TagType'; import { addTag, deleteTaggedObjects, fetchTags, OBJECT_TYPES } from 'src/tags'; -import { TagsList } from 'src/components/Tags'; +import { loadTags } from 'src/components/Tags/utils'; const StyledFormItem = styled(FormItem)` margin-bottom: 0; diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index dc22c10a80af1..9d3f802f1b6c0 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -33,7 +33,7 @@ import { import Chart, { Slice } from 'src/types/Chart'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/components/MessageToasts/withToasts'; -import { loadTags } from 'src/components/ObjectTags'; +import { loadTags } from 'src/components/Tags/utils'; import { addTag, deleteTaggedObjects, fetchTags, OBJECT_TYPES } from 'src/tags'; import TagType from 'src/types/TagType'; diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 572306a1a0877..8cf8efc1b9cf5 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -71,7 +71,7 @@ import { GenericLink } from 'src/components/GenericLink/GenericLink'; import { bootstrapData } from 'src/preamble'; import Owner from 'src/types/Owner'; import { OBJECT_TYPES } from 'src/tags'; -import { loadTags } from 'src/components/ObjectTags'; +import { loadTags } from 'src/components/Tags/utils'; import ChartCard from './ChartCard'; const FlexRowContainer = styled.div` diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 31f76c52d77e0..6c7f4e6558034 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -53,7 +53,7 @@ import ImportModelsModal from 'src/components/ImportModal/index'; import Dashboard from 'src/dashboard/containers/Dashboard'; import CertifiedBadge from 'src/components/CertifiedBadge'; import { bootstrapData } from 'src/preamble'; -import { loadTags } from 'src/components/ObjectTags'; +import { loadTags } from 'src/components/Tags/utils'; import DashboardCard from './DashboardCard'; import { DashboardStatus } from './types'; diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx index 3c6ca081a1843..728c483355d99 100644 --- a/superset-frontend/src/views/CRUD/tags/TagList.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -39,6 +39,7 @@ import { Tooltip } from 'src/components/Tooltip'; import FacePile from 'src/components/FacePile'; import { Link } from 'react-router-dom'; import { deleteTags } from 'src/tags'; +import { Tag as AntdTag } from 'antd'; import { Tag } from '../types'; import TagCard from './TagCard'; @@ -100,24 +101,15 @@ function TagList(props: TagListProps) { original: { name: tagName }, }, }: any) => ( - {tagName} + + + {tagName} + + ), Header: t('Name'), accessor: 'name', }, - { - Cell: ({ - row: { - original: { - changed_by_name: changedByName, - changed_by_url: changedByUrl, - }, - }, - }: any) => {changedByName}, - Header: t('Modified by'), - accessor: 'changed_by.first_name', - size: 'xl', - }, { Cell: ({ row: { From c4b4fa2751aba4d5389f220b788f3e879ced6983 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 6 Dec 2022 12:02:38 -0500 Subject: [PATCH 45/76] fixed type errors --- superset-frontend/src/components/Tags/Tag.tsx | 2 +- .../src/views/CRUD/chart/ChartList.tsx | 8 ++--- .../views/CRUD/dashboard/DashboardList.tsx | 2 +- .../CRUD/data/savedquery/SavedQueryList.tsx | 1 + .../src/views/CRUD/tags/TagCard.tsx | 29 +++++++------------ .../src/views/CRUD/tags/TagList.tsx | 1 - superset-frontend/src/views/CRUD/types.ts | 10 +++++-- 7 files changed, 23 insertions(+), 30 deletions(-) diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index db072feba5804..4070f9d1c2676 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { styled, SupersetTheme } from '@superset-ui/core'; +import { styled } from '@superset-ui/core'; import TagType from 'src/types/TagType'; import AntdTag from 'antd/lib/tag'; import React, { useMemo } from 'react'; diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 8cf8efc1b9cf5..8a24bc6efd344 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -30,7 +30,6 @@ import { uniqBy } from 'lodash'; import moment from 'moment'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { - Actions, createErrorHandler, createFetchRelated, handleChartDelete, @@ -70,7 +69,6 @@ import CertifiedBadge from 'src/components/CertifiedBadge'; import { GenericLink } from 'src/components/GenericLink/GenericLink'; import { bootstrapData } from 'src/preamble'; import Owner from 'src/types/Owner'; -import { OBJECT_TYPES } from 'src/tags'; import { loadTags } from 'src/components/Tags/utils'; import ChartCard from './ChartCard'; @@ -158,7 +156,7 @@ type ChartLinkedDashboard = { dashboard_title: string; }; -const Actions = styled.div` +const StyledActions = styled.div` color: ${({ theme }) => theme.colors.grayscale.base}; `; @@ -493,7 +491,7 @@ function ChartList(props: ChartListProps) { } return ( - + {canDelete && ( )} - + ); }, Header: t('Actions'), diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 6c7f4e6558034..28c1001995791 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { styled, SupersetClient, t } from '@superset-ui/core'; +import { SupersetClient, t } from '@superset-ui/core'; import React, { useState, useMemo, useCallback } from 'react'; import { Link } from 'react-router-dom'; import rison from 'rison'; diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx index 15e4ef7361e02..b88d816943ae2 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx @@ -480,6 +480,7 @@ function SavedQueryList({ { Header: t('Tags'), id: 'tags', + key: 'tags', input: 'search', operator: FilterOperator.savedQueryTags, }, diff --git a/superset-frontend/src/views/CRUD/tags/TagCard.tsx b/superset-frontend/src/views/CRUD/tags/TagCard.tsx index 617559a298070..84e600262e642 100644 --- a/superset-frontend/src/views/CRUD/tags/TagCard.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagCard.tsx @@ -17,19 +17,17 @@ * under the License. */ import React from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { t, useTheme } from '@superset-ui/core'; -import { handleDashboardDelete, CardStyles } from 'src/views/CRUD/utils'; +import { CardStyles } from 'src/views/CRUD/utils'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { AntdDropdown } from 'src/components'; import { Menu } from 'src/components/Menu'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import ListViewCard from 'src/components/ListViewCard'; import Icons from 'src/components/Icons'; -import Label from 'src/components/Label'; -import FacePile from 'src/components/FacePile'; -import FaveStar from 'src/components/FaveStar'; import { Tag } from 'src/views/CRUD/types'; +import { deleteTags } from 'src/tags'; interface TagCardProps { tag: Tag; @@ -55,9 +53,12 @@ function TagCard({ addSuccessToast, showThumbnails, }: TagCardProps) { - const history = useHistory(); const canDelete = hasPerm('can_write'); - const canExport = hasPerm('can_export'); + + const handleTagDelete = (tag: Tag) => { + deleteTags([tag], addSuccessToast, addDangerToast); + refreshData(); + }; const theme = useTheme(); const menu = ( @@ -71,16 +72,7 @@ function TagCard({ {t('Are you sure you want to delete')} {tag.name}? } - onConfirm={() => - handleTagDelete( - tag, - refreshData, - addSuccessToast, - addDangerToast, - tagFilter, - userId, - ) - } + onConfirm={() => handleTagDelete(tag)} > {confirmDelete => (
{ diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx index 728c483355d99..8001f3822431c 100644 --- a/superset-frontend/src/views/CRUD/tags/TagList.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -69,7 +69,6 @@ function TagList(props: TagListProps) { resourceCollection: tags, bulkSelectEnabled, }, - setResourceCollection: setTags, hasPerm, fetchData, toggleBulkSelect, diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts index 3161e255ae49a..c3ef45133c061 100644 --- a/superset-frontend/src/views/CRUD/types.ts +++ b/superset-frontend/src/views/CRUD/types.ts @@ -133,11 +133,15 @@ export enum QueryObjectColumns { tracking_url = 'tracking_url', } +export type ImportResourceName = + | 'chart' + | 'dashboard' + | 'database' + | 'dataset' + | 'saved_query'; + export interface Tag { - changed_by_name: string; - changed_by_url: string; changed_on_delta_humanized: string; - changed_by: string; name: string; id: number; created_by: object; From a060998785aa7ec263c929182eefc9aa081fa5bc Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 6 Dec 2022 12:56:23 -0500 Subject: [PATCH 46/76] fixed api test error --- tests/integration_tests/tags/api_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index d3ccdc3302519..ec580595aaafc 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -30,7 +30,7 @@ from superset.common.db_query_status import QueryStatus from superset.models.core import Database from superset.utils.database import get_example_database, get_main_database -from superset.models.sql_lab import Tag +from superset.tags.models import Tag from tests.integration_tests.base_tests import SupersetTestCase From adcde8100c2d1f0b1522af96cb9bb21657f8f5a0 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:07:18 -0500 Subject: [PATCH 47/76] fixed frontend tests --- .../src/components/Tags/TagsList.test.tsx | 15 +++++++++------ .../src/components/Tags/TagsList.tsx | 2 ++ .../PropertiesModal/PropertiesModal.test.tsx | 15 ++++++++------- .../components/PropertiesModal/index.tsx | 2 +- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/superset-frontend/src/components/Tags/TagsList.test.tsx b/superset-frontend/src/components/Tags/TagsList.test.tsx index 4e2ffd2567d5d..988f30f43b5bf 100644 --- a/superset-frontend/src/components/Tags/TagsList.test.tsx +++ b/superset-frontend/src/components/Tags/TagsList.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import TagsList, { TagsListProps } from './TagsList'; const testTags = [ @@ -49,16 +49,19 @@ const mockedProps: TagsListProps = { maxTags: 5, }; -const findAllTags = () => screen.getAllByRole('link')! as HTMLElement[]; +const getElementsByClassName = (className: string) => + document.querySelectorAll(className)! as NodeListOf; + +const findAllTags = () => waitFor(() => getElementsByClassName('.ant-tag')); test('should render', () => { const { container } = render(); expect(container).toBeInTheDocument(); }); -test('should render 5 elements', () => { +test('should render 5 elements', async () => { render(); - const tagsListItems = findAllTags(); + const tagsListItems = await findAllTags(); expect(tagsListItems).toHaveLength(5); expect(tagsListItems[0]).toHaveTextContent(testTags[0].name); expect(tagsListItems[1]).toHaveTextContent(testTags[1].name); @@ -67,9 +70,9 @@ test('should render 5 elements', () => { expect(tagsListItems[4]).toHaveTextContent(testTags[4].name); }); -test('should render 3 elements when maxTags is set to 3', () => { +test('should render 3 elements when maxTags is set to 3', async () => { render(); - const tagsListItems = findAllTags(); + const tagsListItems = await findAllTags(); expect(tagsListItems).toHaveLength(3); expect(tagsListItems[2]).toHaveTextContent('+3...'); }); diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index 028ae5c5a832f..c5c669ee00d61 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -75,6 +75,7 @@ const TagsList = ({ {tags.slice(0, tempMaxTags - 1).map((tag: TagType, index) => ( ( { expect( screen.getByRole('heading', { name: 'Certification' }), ).toBeInTheDocument(); - expect(screen.getAllByRole('heading')).toHaveLength(5); + expect(screen.getAllByRole('heading')).toHaveLength(6); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Advanced' })).toBeInTheDocument(); @@ -217,7 +217,8 @@ test('should render - FeatureFlag enabled', async () => { expect( screen.getByRole('heading', { name: 'Certification' }), ).toBeInTheDocument(); - expect(screen.getAllByRole('heading')).toHaveLength(4); + expect(screen.getByRole('heading', { name: 'Tags' })).toBeInTheDocument(); + expect(screen.getAllByRole('heading')).toHaveLength(5); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Advanced' })).toBeInTheDocument(); @@ -226,7 +227,7 @@ test('should render - FeatureFlag enabled', async () => { expect(screen.getAllByRole('button')).toHaveLength(4); expect(screen.getAllByRole('textbox')).toHaveLength(4); - expect(screen.getAllByRole('combobox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(3); expect(spyColorSchemeControlWrapper).toBeCalledWith( expect.objectContaining({ colorScheme: 'supersetColors' }), @@ -245,10 +246,10 @@ test('should open advance', async () => { ).toBeInTheDocument(); expect(screen.getAllByRole('textbox')).toHaveLength(4); - expect(screen.getAllByRole('combobox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(3); userEvent.click(screen.getByRole('button', { name: 'Advanced' })); expect(screen.getAllByRole('textbox')).toHaveLength(5); - expect(screen.getAllByRole('combobox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(3); }); test('should close modal', async () => { @@ -382,7 +383,7 @@ test('should show all roles', async () => { useRedux: true, }); - expect(screen.getAllByRole('combobox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(3); expect( screen.getByRole('combobox', { name: SupersetCore.t('Roles') }), ).toBeInTheDocument(); @@ -415,7 +416,7 @@ test('should show active owners with dashboard rbac', async () => { useRedux: true, }); - expect(screen.getAllByRole('combobox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(3); expect( screen.getByRole('combobox', { name: SupersetCore.t('Owners') }), ).toBeInTheDocument(); diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 119140068f92c..97916d444ac21 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -574,7 +574,7 @@ const PropertiesModal = ({ }, (tags: TagType[]) => setTags(tags), (error: Response) => { - handleErrorResponse(error); + addDangerToast(`Error fetching tags: ${error.text}`); }, ); } catch (error: any) { From 14a24efbf8e9622bad68dc76a5cc2a29aa066eb9 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 7 Dec 2022 08:24:24 -0500 Subject: [PATCH 48/76] fixed unused import --- superset-frontend/src/components/Tags/TagsList.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset-frontend/src/components/Tags/TagsList.test.tsx b/superset-frontend/src/components/Tags/TagsList.test.tsx index 988f30f43b5bf..f67dbce294663 100644 --- a/superset-frontend/src/components/Tags/TagsList.test.tsx +++ b/superset-frontend/src/components/Tags/TagsList.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import { render, waitFor } from 'spec/helpers/testing-library'; import TagsList, { TagsListProps } from './TagsList'; const testTags = [ From 8e00386cbaf314294dd0d903b0adf15b6556bce5 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 7 Dec 2022 09:37:52 -0500 Subject: [PATCH 49/76] fixed feature flag set --- superset/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/config.py b/superset/config.py index ec8f6dd539307..f163997c6ee4b 100644 --- a/superset/config.py +++ b/superset/config.py @@ -417,7 +417,7 @@ def _try_json_readsha(filepath: str, length: int) -> Optional[str]: "DASHBOARD_CACHE": False, "REMOVE_SLICE_LEVEL_LABEL_COLORS": False, "SHARE_QUERIES_VIA_KV_STORE": False, - "TAGGING_SYSTEM": True, + "TAGGING_SYSTEM": False, "SQLLAB_BACKEND_PERSISTENCE": True, "LISTVIEWS_DEFAULT_CARD_VIEW": False, # When True, this flag allows display of HTML tags in Markdown components From b07343ce99d2392398e95270cdd829fb99b2cc17 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:52:24 -0500 Subject: [PATCH 50/76] feature flag fixes --- superset/charts/api.py | 17 +++++++++-------- superset/connectors/sqla/models.py | 1 - superset/dashboards/api.py | 12 +++++++----- superset/initialization/__init__.py | 4 ---- superset/models/dashboard.py | 15 ++++++++------- superset/models/slice.py | 15 ++++++++------- superset/models/sql_lab.py | 17 +++++++++-------- superset/queries/saved_queries/api.py | 13 ++++++++----- superset/tags/models.py | 1 - tests/integration_tests/fixtures/tags.py | 1 + 10 files changed, 50 insertions(+), 46 deletions(-) diff --git a/superset/charts/api.py b/superset/charts/api.py index ed31a9b80c824..20eb5cda502f4 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -130,9 +130,6 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "owners.id", "owners.last_name", "owners.username", - "tags.id", - "tags.name", - "tags.type", "dashboards.id", "dashboards.dashboard_title", "params", @@ -143,6 +140,9 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "query_context", "is_managed_externally", ] + if is_feature_enabled("TAGGING_SYSTEM"): + show_columns += ["tags.id","tags.name","tags.type"] + show_select_columns = show_columns + ["table.id"] list_columns = [ "is_managed_externally", @@ -181,13 +181,12 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "slice_name", "table.default_endpoint", "table.table_name", - "tags.id", - "tags.name", - "tags.type", "thumbnail_url", "url", "viz_type", ] + if is_feature_enabled("TAGGING_SYSTEM"): + list_columns += ["tags.id", "tags.name", "tags.type"] list_select_columns = list_columns + ["changed_by_fk", "changed_on"] order_columns = [ "changed_by.first_name", @@ -216,17 +215,19 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "owners", "dashboards", "slice_name", - "tags", "viz_type", ] + if is_feature_enabled("TAGGING_SYSTEM"): + search_columns += ['tags'] base_order = ("changed_on", "desc") base_filters = [["id", ChartFilter, lambda: []]] search_filters = { "id": [ChartFavoriteFilter, ChartCertifiedFilter], "slice_name": [ChartAllTextFilter], - "tags": [ChartTagFilter], "created_by": [ChartHasCreatedByFilter, ChartCreatedByMeFilter], } + if is_feature_enabled("TAGGING_SYSTEM"): + search_filters['tags'] = [ChartTagFilter] # Will just affect _info endpoint edit_columns = ["slice_name"] diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 12ddce7ecb617..fd5942c51a5a0 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -2253,7 +2253,6 @@ def write_shadow_dataset( sa.event.listen(SqlMetric, "after_update", SqlaTable.update_column) sa.event.listen(TableColumn, "after_update", SqlaTable.update_column) - RLSFilterRoles = Table( "rls_filter_roles", metadata, diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index 0cd9e16effdbf..64df100620f44 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -181,11 +181,10 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "owners.email", "roles.id", "roles.name", - "tags.id", - "tags.name", - "tags.type", "is_managed_externally", ] + if is_feature_enabled("TAGGING_SYSTEM"): + list_columns += ["tags.id", "tags.name", "tags.type"] list_select_columns = list_columns + ["changed_on", "created_on", "changed_by_fk"] order_columns = [ "changed_by.first_name", @@ -219,14 +218,17 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "published", "roles", "slug", - "tags", ) + if is_feature_enabled("TAGGING_SYSTEM"): + search_columns += ("tags",) search_filters = { "dashboard_title": [DashboardTitleOrSlugFilter], "id": [DashboardFavoriteFilter, DashboardCertifiedFilter], "created_by": [DashboardCreatedByMeFilter, DashboardHasCreatedByFilter], - "tags": [DashboardTagFilter], } + if is_feature_enabled("TAGGING_SYSTEM"): + search_filters['tags'] = [DashboardTagFilter] + base_order = ("changed_on", "desc") add_model_schema = DashboardPostSchema() diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index d5e40c9d0abcb..d363e5ec1a702 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -368,8 +368,6 @@ def init_views(self) -> None: "All Entities", label=__("All Entities"), icon="", - category="Tagging", - category_label=__("Tagging"), category_icon="", menu_cond=lambda: feature_flag_manager.is_feature_enabled( "TAGGING_SYSTEM" @@ -380,8 +378,6 @@ def init_views(self) -> None: "Tags", label=__("Tags"), icon="", - category="Tagging", - category_label=__("Tagging"), category_icon="", menu_cond=lambda: feature_flag_manager.is_feature_enabled( "TAGGING_SYSTEM" diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index 393a3df12cce4..299f2c51e756e 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -148,13 +148,14 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin): Slice, secondary=dashboard_slices, backref="dashboards" ) owners = relationship(security_manager.user_model, secondary=dashboard_user) - tags = relationship( - "Tag", - secondary="tagged_object", - primaryjoin="and_(Dashboard.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " - "TaggedObject.object_type == 'dashboard')", - ) + if is_feature_enabled("TAGGING_SYSTEM"): + tags = relationship( + "Tag", + secondary="tagged_object", + primaryjoin="and_(Dashboard.id == TaggedObject.object_id)", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " \ + "TaggedObject.object_type == 'dashboard')", + ) published = Column(Boolean, default=False) is_managed_externally = Column(Boolean, nullable=False, default=False) external_url = Column(Text, nullable=True) diff --git a/superset/models/slice.py b/superset/models/slice.py index 8d65b563d155b..ff89ed9b73a08 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -97,13 +97,14 @@ class Slice( # pylint: disable=too-many-public-methods security_manager.user_model, foreign_keys=[last_saved_by_fk] ) owners = relationship(security_manager.user_model, secondary=slice_user) - tags = relationship( - "Tag", - secondary="tagged_object", - primaryjoin="and_(Slice.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " - "TaggedObject.object_type == 'chart')", - ) + if is_feature_enabled("TAGGING_SYSTEM"): + tags = relationship( + "Tag", + secondary="tagged_object", + primaryjoin="and_(Slice.id == TaggedObject.object_id)", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " \ + "TaggedObject.object_type == 'chart')", + ) table = relationship( "SqlaTable", foreign_keys=[datasource_id], diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py index 1d7dab2677f1f..7f05d60afe315 100644 --- a/superset/models/sql_lab.py +++ b/superset/models/sql_lab.py @@ -41,7 +41,7 @@ from sqlalchemy.engine.url import URL from sqlalchemy.orm import backref, relationship -from superset import security_manager +from superset import is_feature_enabled, security_manager from superset.jinja_context import BaseTemplateProcessor, get_template_processor from superset.models.helpers import ( AuditMixinNullable, @@ -364,13 +364,14 @@ class SavedQuery(Model, AuditMixinNullable, ExtraJSONMixin, ImportExportMixin): ) rows = Column(Integer, nullable=True) last_run = Column(DateTime, nullable=True) - tags = relationship( - "Tag", - secondary="tagged_object", - primaryjoin="and_(SavedQuery.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " - "TaggedObject.object_type == 'query')", - ) + if is_feature_enabled("TAGGING_SYSTEM"): + tags = relationship( + "Tag", + secondary="tagged_object", + primaryjoin="and_(SavedQuery.id == TaggedObject.object_id)", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " \ + "TaggedObject.object_type == 'saved_query')", + ) export_parent = "database" export_fields = [ diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py index f42c6c38c2d3d..343d17fba4e46 100644 --- a/superset/queries/saved_queries/api.py +++ b/superset/queries/saved_queries/api.py @@ -26,6 +26,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_babel import ngettext +from superset import is_feature_enabled from superset.commands.importers.exceptions import ( IncorrectFormatError, NoValidFilesFoundError, @@ -117,10 +118,9 @@ class SavedQueryRestApi(BaseSupersetModelRestApi): "schema", "sql", "sql_tables", - "tags.id", - "tags.name", - "tags.type", ] + if is_feature_enabled("TAGGING_SYSTEM"): + list_columns += ["tags.id", "tags.name", "tags.type"] list_select_columns = list_columns + ["changed_by_fk", "changed_on"] add_columns = [ "db_id", @@ -144,12 +144,15 @@ class SavedQueryRestApi(BaseSupersetModelRestApi): "last_run_delta_humanized", ] - search_columns = ["id", "database", "label", "schema", "created_by", "tags"] + search_columns = ["id", "database", "label", "schema", "created_by"] + if is_feature_enabled("TAGGING_SYSTEM"): + search_columns += ['tags'] search_filters = { "id": [SavedQueryFavoriteFilter], "label": [SavedQueryAllTextFilter], - "tags": [SavedQueryTagFilter] } + if is_feature_enabled("TAGGING_SYSTEM"): + search_filters['tags'] = [SavedQueryTagFilter] apispec_parameter_schemas = { "get_delete_ids_schema": get_delete_ids_schema, diff --git a/superset/tags/models.py b/superset/tags/models.py index 283cdfede2616..1258f3b96110c 100644 --- a/superset/tags/models.py +++ b/superset/tags/models.py @@ -84,7 +84,6 @@ class Tag(Model, AuditMixinNullable): name = Column(String(250), unique=True) type = Column(Enum(TagTypes)) - class TaggedObject(Model, AuditMixinNullable): """An association between an object and a tag.""" diff --git a/tests/integration_tests/fixtures/tags.py b/tests/integration_tests/fixtures/tags.py index 57fd4ec7196e2..cc3969ac2bea0 100644 --- a/tests/integration_tests/fixtures/tags.py +++ b/tests/integration_tests/fixtures/tags.py @@ -31,3 +31,4 @@ def with_tagging_system_feature(): yield app.config["DEFAULT_FEATURE_FLAGS"]["TAGGING_SYSTEM"] = False clear_sqla_event_listeners() + yield From 6d9dca4ed88b8ec5c2726d29601bf551ec48c25c Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:05:49 -0500 Subject: [PATCH 51/76] fixed pylint error --- superset/dashboards/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index 64df100620f44..b49cd5346e791 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -184,7 +184,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "is_managed_externally", ] if is_feature_enabled("TAGGING_SYSTEM"): - list_columns += ["tags.id", "tags.name", "tags.type"] + list_columns += ["tags.id", "tags.name", "tags.type"] list_select_columns = list_columns + ["changed_on", "created_on", "changed_by_fk"] order_columns = [ "changed_by.first_name", From 6ad36535230cae04b4714f5433053847d7e73d86 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:13:16 -0500 Subject: [PATCH 52/76] fixed pylint error v2 --- superset/dashboards/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index b49cd5346e791..f7286c6c224c0 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -184,7 +184,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "is_managed_externally", ] if is_feature_enabled("TAGGING_SYSTEM"): - list_columns += ["tags.id", "tags.name", "tags.type"] + list_columns += ["tags.id", "tags.name", "tags.type"] list_select_columns = list_columns + ["changed_on", "created_on", "changed_by_fk"] order_columns = [ "changed_by.first_name", From b0fb771e49b1f85f8563e14b04e080d5b32ece5d Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:25:07 -0500 Subject: [PATCH 53/76] removed accidental test fixture error --- tests/integration_tests/fixtures/tags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration_tests/fixtures/tags.py b/tests/integration_tests/fixtures/tags.py index cc3969ac2bea0..57fd4ec7196e2 100644 --- a/tests/integration_tests/fixtures/tags.py +++ b/tests/integration_tests/fixtures/tags.py @@ -31,4 +31,3 @@ def with_tagging_system_feature(): yield app.config["DEFAULT_FEATURE_FLAGS"]["TAGGING_SYSTEM"] = False clear_sqla_event_listeners() - yield From 2093ecc7c85fc297a3a15b7d52a4c46d657144a3 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 8 Dec 2022 09:21:16 -0500 Subject: [PATCH 54/76] fixing tagging tests --- tests/integration_tests/tags/api_tests.py | 34 ++++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index ec580595aaafc..cc02abe62941b 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -30,12 +30,24 @@ from superset.common.db_query_status import QueryStatus from superset.models.core import Database from superset.utils.database import get_example_database, get_main_database -from superset.tags.models import Tag +from superset.tags.models import Tag, TagTypes from tests.integration_tests.base_tests import SupersetTestCase TAGS_FIXTURE_COUNT = 10 +TAGS_LIST_COLUMNS = [ + 'id', + 'name', + 'type', + 'changed_by.first_name', + 'changed_by.last_name', + 'changed_on_delta_humanized', + 'created_by.first_name', + 'created_by.last_name' +] + + class TestTagApi(SupersetTestCase): def insert_tag( self, @@ -53,6 +65,9 @@ def insert_tag( @pytest.fixture() def create_tags(self): with self.create_app().app_context(): + # clear tags table + tags = db.session.query(Tag).delete() + db.session.commit() tags = [] for cx in range(TAGS_FIXTURE_COUNT): tags.append( @@ -82,13 +97,16 @@ def test_get_tag(self): rv = self.client.get(uri) self.assertEqual(rv.status_code, 200) expected_result = { + "changed_by": None, + "changed_on_delta_humanized": "now", + "created_by": None, "id": tag.id, "name": "test get tag", - "type": 'custom' + "type": TagTypes.custom.value } data = json.loads(rv.data.decode("utf-8")) - for key, value in data["result"].items(): - self.assertEqual(value, expected_result[key]) + for key, value in expected_result.items(): + self.assertEqual(value, data["result"][key]) # rollback changes db.session.delete(tag) db.session.commit() @@ -117,10 +135,6 @@ def test_get_list_tag(self): rv = self.client.get(uri) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode("utf-8")) - assert data["count"] == TAGS_FIXTURE_COUNT + assert data['count'] == TAGS_FIXTURE_COUNT # check expected columns - assert sorted(list(data["result"][0].keys())) == [ - "id", - "name", - "type", - ] + assert data["list_columns"] == TAGS_LIST_COLUMNS From 6311bd2bc42da0fb1cea08d3480751dd0d3d514b Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:18:12 -0500 Subject: [PATCH 55/76] precommit fixes --- superset/charts/api.py | 8 +-- superset/charts/filters.py | 4 +- superset/charts/schemas.py | 45 ++++++----------- superset/dashboards/api.py | 36 +++++++++----- superset/dashboards/filters.py | 4 +- superset/dashboards/schemas.py | 15 ++---- superset/initialization/__init__.py | 13 ++--- superset/models/dashboard.py | 4 +- superset/models/slice.py | 4 +- superset/models/sql_lab.py | 4 +- superset/queries/saved_queries/api.py | 6 +-- superset/queries/saved_queries/filters.py | 4 +- superset/tags/api.py | 59 ++++++++++------------- superset/tags/commands/create.py | 10 ++-- superset/tags/commands/exceptions.py | 6 +-- superset/tags/dao.py | 16 ++---- superset/tags/models.py | 15 +++--- superset/tags/schemas.py | 3 +- superset/views/all_entities.py | 4 +- superset/views/base_api.py | 8 ++- superset/views/tags.py | 20 +++----- tests/integration_tests/tags/api_tests.py | 26 +++++----- 22 files changed, 138 insertions(+), 176 deletions(-) diff --git a/superset/charts/api.py b/superset/charts/api.py index 20eb5cda502f4..e0c01c18749ca 100644 --- a/superset/charts/api.py +++ b/superset/charts/api.py @@ -52,9 +52,9 @@ ChartCertifiedFilter, ChartCreatedByMeFilter, ChartFavoriteFilter, - ChartTagFilter, ChartFilter, ChartHasCreatedByFilter, + ChartTagFilter, ) from superset.charts.schemas import ( CHART_SCHEMAS, @@ -141,7 +141,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "is_managed_externally", ] if is_feature_enabled("TAGGING_SYSTEM"): - show_columns += ["tags.id","tags.name","tags.type"] + show_columns += ["tags.id", "tags.name", "tags.type"] show_select_columns = show_columns + ["table.id"] list_columns = [ @@ -218,7 +218,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "viz_type", ] if is_feature_enabled("TAGGING_SYSTEM"): - search_columns += ['tags'] + search_columns += ["tags"] base_order = ("changed_on", "desc") base_filters = [["id", ChartFilter, lambda: []]] search_filters = { @@ -227,7 +227,7 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "created_by": [ChartHasCreatedByFilter, ChartCreatedByMeFilter], } if is_feature_enabled("TAGGING_SYSTEM"): - search_filters['tags'] = [ChartTagFilter] + search_filters["tags"] = [ChartTagFilter] # Will just affect _info endpoint edit_columns = ["slice_name"] diff --git a/superset/charts/filters.py b/superset/charts/filters.py index 5efd1e9969a29..ddd1f54b592ce 100644 --- a/superset/charts/filters.py +++ b/superset/charts/filters.py @@ -57,9 +57,7 @@ class ChartFavoriteFilter(BaseFavoriteFilter): # pylint: disable=too-few-public model = Slice -class ChartTagFilter( # pylint: disable=too-few-public-methods - BaseTagFilter -): +class ChartTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods """ Custom filter for the GET list that filters all dashboards that a user has favored """ diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 1195c61e82861..55ee256a76ce9 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -171,8 +171,7 @@ class ChartEntityResponseSchema(Schema): form_data = fields.Dict(description=form_data_description) slice_url = fields.String(description=slice_url_description) certified_by = fields.String(description=certified_by_description) - certification_details = fields.String( - description=certification_details_description) + certification_details = fields.String(description=certification_details_description) class ChartPostSchema(Schema): @@ -183,8 +182,7 @@ class ChartPostSchema(Schema): slice_name = fields.String( description=slice_name_description, required=True, validate=Length(1, 250) ) - description = fields.String( - description=description_description, allow_none=True) + description = fields.String(description=description_description, allow_none=True) viz_type = fields.String( description=viz_type_description, validate=Length(0, 250), @@ -205,8 +203,7 @@ class ChartPostSchema(Schema): cache_timeout = fields.Integer( description=cache_timeout_description, allow_none=True ) - datasource_id = fields.Integer( - description=datasource_id_description, required=True) + datasource_id = fields.Integer(description=datasource_id_description, required=True) datasource_type = fields.String( description=datasource_type_description, validate=validate.OneOf(choices=[ds.value for ds in DatasourceType]), @@ -215,10 +212,8 @@ class ChartPostSchema(Schema): datasource_name = fields.String( description=datasource_name_description, allow_none=True ) - dashboards = fields.List(fields.Integer( - description=dashboards_description)) - certified_by = fields.String( - description=certified_by_description, allow_none=True) + dashboards = fields.List(fields.Integer(description=dashboards_description)) + certified_by = fields.String(description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) @@ -234,8 +229,7 @@ class ChartPutSchema(Schema): slice_name = fields.String( description=slice_name_description, allow_none=True, validate=Length(0, 250) ) - description = fields.String( - description=description_description, allow_none=True) + description = fields.String(description=description_description, allow_none=True) viz_type = fields.String( description=viz_type_description, allow_none=True, @@ -261,10 +255,8 @@ class ChartPutSchema(Schema): validate=validate.OneOf(choices=[ds.value for ds in DatasourceType]), allow_none=True, ) - dashboards = fields.List(fields.Integer( - description=dashboards_description)) - certified_by = fields.String( - description=certified_by_description, allow_none=True) + dashboards = fields.List(fields.Integer(description=dashboards_description)) + certified_by = fields.String(description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) @@ -926,16 +918,14 @@ class AnnotationLayerSchema(Schema): keys=fields.String( desciption="Name of property to be overridden", validate=validate.OneOf( - choices=("granularity", "time_grain_sqla", - "time_range", "time_shift"), + choices=("granularity", "time_grain_sqla", "time_range", "time_shift"), ), ), values=fields.Raw(allow_none=True), description="which properties should be overridable", allow_none=True, ) - show = fields.Boolean( - description="Should the layer be shown", required=True) + show = fields.Boolean(description="Should the layer be shown", required=True) showLabel = fields.Boolean( description="Should the label always be shown", allow_none=True, @@ -1008,8 +998,7 @@ class Meta: # pylint: disable=too-few-public-methods unknown = EXCLUDE datasource = fields.Nested(ChartDataDatasourceSchema, allow_none=True) - result_type = EnumField(ChartDataResultType, - by_value=True, allow_none=True) + result_type = EnumField(ChartDataResultType, by_value=True, allow_none=True) annotation_layers = fields.List( fields.Nested(AnnotationLayerSchema), @@ -1026,8 +1015,7 @@ class Meta: # pylint: disable=too-few-public-methods "if defined in datasource", allow_none=True, ) - filters = fields.List(fields.Nested( - ChartDataFilterSchema), allow_none=True) + filters = fields.List(fields.Nested(ChartDataFilterSchema), allow_none=True) granularity = fields.String( description="Name of temporal column used for time filtering. For legacy Druid " "datasources this defines the time grain.", @@ -1153,8 +1141,7 @@ class Meta: # pylint: disable=too-few-public-methods ( fields.Raw( validate=[ - Length(min=1, error=_( - "orderby column must be populated")) + Length(min=1, error=_("orderby column must be populated")) ], allow_none=False, ), @@ -1325,8 +1312,7 @@ class ChartDataResponseResult(Schema): allow_none=False, ) data = fields.List(fields.Dict(), description="A list with results") - colnames = fields.List( - fields.String(), description="A list of column names") + colnames = fields.List(fields.String(), description="A list of column names") coltypes = fields.List( fields.Integer(), description="A list of generic data types of each column" ) @@ -1390,8 +1376,7 @@ class ImportV1ChartSchema(Schema): slice_name = fields.String(required=True) viz_type = fields.String(required=True) params = fields.Dict() - query_context = fields.String( - allow_none=True, validate=utils.validate_json) + query_context = fields.String(allow_none=True, validate=utils.validate_json) cache_timeout = fields.Integer(allow_none=True) uuid = fields.UUID(required=True) version = fields.String(required=True) diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index f7286c6c224c0..bd28cd69770b9 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -61,8 +61,8 @@ DashboardCreatedByMeFilter, DashboardFavoriteFilter, DashboardHasCreatedByFilter, - DashboardTitleOrSlugFilter, DashboardTagFilter, + DashboardTitleOrSlugFilter, FilterRelatedRoles, ) from superset.dashboards.schemas import ( @@ -210,24 +210,36 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: edit_columns = add_columns search_columns = ( - "created_by", - "changed_by", - "dashboard_title", - "id", - "owners", - "published", - "roles", - "slug", + ( + "created_by", + "changed_by", + "dashboard_title", + "id", + "owners", + "published", + "roles", + "slug", + ) + if not is_feature_enabled("TAGGING_SYSTEM") + else ( + "created_by", + "changed_by", + "dashboard_title", + "id", + "owners", + "published", + "roles", + "slug", + "tags", + ) ) - if is_feature_enabled("TAGGING_SYSTEM"): - search_columns += ("tags",) search_filters = { "dashboard_title": [DashboardTitleOrSlugFilter], "id": [DashboardFavoriteFilter, DashboardCertifiedFilter], "created_by": [DashboardCreatedByMeFilter, DashboardHasCreatedByFilter], } if is_feature_enabled("TAGGING_SYSTEM"): - search_filters['tags'] = [DashboardTagFilter] + search_filters["tags"] = [DashboardTagFilter] base_order = ("changed_on", "desc") diff --git a/superset/dashboards/filters.py b/superset/dashboards/filters.py index 26c465d78feb6..d458671311386 100644 --- a/superset/dashboards/filters.py +++ b/superset/dashboards/filters.py @@ -77,9 +77,7 @@ class DashboardFavoriteFilter( # pylint: disable=too-few-public-methods model = Dashboard -class DashboardTagFilter( # pylint: disable=too-few-public-methods - BaseTagFilter -): +class DashboardTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods """ Custom filter for the GET list that filters all dashboards that a user has favored """ diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index 7bb6c50f068ff..fa2e9d6f87b8e 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -165,8 +165,7 @@ class DashboardGetResponseSchema(Schema): json_metadata = fields.String(description=json_metadata_description) position_json = fields.String(description=position_json_description) certified_by = fields.String(description=certified_by_description) - certification_details = fields.String( - description=certification_details_description) + certification_details = fields.String(description=certification_details_description) changed_by_name = fields.String() changed_by_url = fields.String() changed_by = fields.Nested(UserSchema) @@ -256,8 +255,7 @@ class DashboardPostSchema(BaseDashboardSchema): validate=validate_json_metadata, ) published = fields.Boolean(description=published_description) - certified_by = fields.String( - description=certified_by_description, allow_none=True) + certified_by = fields.String(description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) @@ -277,8 +275,7 @@ class DashboardPutSchema(BaseDashboardSchema): owners = fields.List( fields.Integer(description=owners_description, allow_none=True) ) - roles = fields.List(fields.Integer( - description=roles_description, allow_none=True)) + roles = fields.List(fields.Integer(description=roles_description, allow_none=True)) position_json = fields.String( description=position_json_description, allow_none=True, validate=validate_json ) @@ -288,10 +285,8 @@ class DashboardPutSchema(BaseDashboardSchema): allow_none=True, validate=validate_json_metadata, ) - published = fields.Boolean( - description=published_description, allow_none=True) - certified_by = fields.String( - description=certified_by_description, allow_none=True) + published = fields.Boolean(description=published_description, allow_none=True) + certified_by = fields.String(description=certified_by_description, allow_none=True) certification_details = fields.String( description=certification_details_description, allow_none=True ) diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index d363e5ec1a702..140edaff3c61e 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -150,6 +150,7 @@ def init_views(self) -> None: from superset.tags.api import TagRestApi from superset.views.access_requests import AccessRequestsModelView from superset.views.alerts import AlertView, ReportView + from superset.views.all_entities import TaggedObjectsModelView, TaggedObjectView from superset.views.annotations import AnnotationLayerView from superset.views.api import Api from superset.views.chart.views import SliceAsync, SliceModelView @@ -183,10 +184,8 @@ def init_views(self) -> None: TableSchemaView, TabStateView, ) - from superset.views.users.api import CurrentUserRestApi - from superset.views.all_entities import TaggedObjectsModelView, TaggedObjectView from superset.views.tags import TagModelView, TagView - + from superset.views.users.api import CurrentUserRestApi # # Setup API views @@ -369,9 +368,7 @@ def init_views(self) -> None: label=__("All Entities"), icon="", category_icon="", - menu_cond=lambda: feature_flag_manager.is_feature_enabled( - "TAGGING_SYSTEM" - ), + menu_cond=lambda: feature_flag_manager.is_feature_enabled("TAGGING_SYSTEM"), ) appbuilder.add_view( TagModelView, @@ -379,9 +376,7 @@ def init_views(self) -> None: label=__("Tags"), icon="", category_icon="", - menu_cond=lambda: feature_flag_manager.is_feature_enabled( - "TAGGING_SYSTEM" - ), + menu_cond=lambda: feature_flag_manager.is_feature_enabled("TAGGING_SYSTEM"), ) appbuilder.add_api(LogRestApi) appbuilder.add_view( diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index 299f2c51e756e..203be3ec9157f 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -153,8 +153,8 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin): "Tag", secondary="tagged_object", primaryjoin="and_(Dashboard.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " \ - "TaggedObject.object_type == 'dashboard')", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " + "TaggedObject.object_type == 'dashboard')", ) published = Column(Boolean, default=False) is_managed_externally = Column(Boolean, nullable=False, default=False) diff --git a/superset/models/slice.py b/superset/models/slice.py index ff89ed9b73a08..853c6bc676ef5 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -102,8 +102,8 @@ class Slice( # pylint: disable=too-many-public-methods "Tag", secondary="tagged_object", primaryjoin="and_(Slice.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " \ - "TaggedObject.object_type == 'chart')", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " + "TaggedObject.object_type == 'chart')", ) table = relationship( "SqlaTable", diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py index 7f05d60afe315..52f6548533484 100644 --- a/superset/models/sql_lab.py +++ b/superset/models/sql_lab.py @@ -369,8 +369,8 @@ class SavedQuery(Model, AuditMixinNullable, ExtraJSONMixin, ImportExportMixin): "Tag", secondary="tagged_object", primaryjoin="and_(SavedQuery.id == TaggedObject.object_id)", - secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " \ - "TaggedObject.object_type == 'saved_query')", + secondaryjoin="and_(TaggedObject.tag_id == Tag.id, " + "TaggedObject.object_type == 'saved_query')", ) export_parent = "database" diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py index 343d17fba4e46..a30464bf08cbb 100644 --- a/superset/queries/saved_queries/api.py +++ b/superset/queries/saved_queries/api.py @@ -50,8 +50,8 @@ from superset.queries.saved_queries.filters import ( SavedQueryAllTextFilter, SavedQueryFavoriteFilter, - SavedQueryTagFilter, SavedQueryFilter, + SavedQueryTagFilter, ) from superset.queries.saved_queries.schemas import ( get_delete_ids_schema, @@ -146,13 +146,13 @@ class SavedQueryRestApi(BaseSupersetModelRestApi): search_columns = ["id", "database", "label", "schema", "created_by"] if is_feature_enabled("TAGGING_SYSTEM"): - search_columns += ['tags'] + search_columns += ["tags"] search_filters = { "id": [SavedQueryFavoriteFilter], "label": [SavedQueryAllTextFilter], } if is_feature_enabled("TAGGING_SYSTEM"): - search_filters['tags'] = [SavedQueryTagFilter] + search_filters["tags"] = [SavedQueryTagFilter] apispec_parameter_schemas = { "get_delete_ids_schema": get_delete_ids_schema, diff --git a/superset/queries/saved_queries/filters.py b/superset/queries/saved_queries/filters.py index 119b8276bdcab..a9e7006b63f4f 100644 --- a/superset/queries/saved_queries/filters.py +++ b/superset/queries/saved_queries/filters.py @@ -58,9 +58,7 @@ class SavedQueryFavoriteFilter( model = SavedQuery -class SavedQueryTagFilter( # pylint: disable=too-few-public-methods - BaseTagFilter -): +class SavedQueryTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods """ Custom filter for the GET list that filters all dashboards that a user has favored """ diff --git a/superset/tags/api.py b/superset/tags/api.py index 03599236bd932..d89a24da3a680 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -23,21 +23,15 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.extensions import event_logger -from superset.tags.models import ObjectTypes, Tag from superset.tags.commands.create import CreateTagCommand -from superset.tags.commands.exceptions import ( - TagCreateFailedError, - TagInvalidError, -) +from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError +from superset.tags.models import ObjectTypes, Tag from superset.tags.schemas import ( + openapi_spec_methods_override, TagGetResponseSchema, TagPostSchema, - openapi_spec_methods_override, -) -from superset.views.base_api import ( - BaseSupersetModelRestApi, - statsd_metrics, ) +from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics logger = logging.getLogger(__name__) @@ -54,28 +48,28 @@ class TagRestApi(BaseSupersetModelRestApi): method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP list_columns = [ - "id", - "name", - "type", - "changed_by.first_name", - "changed_by.last_name", - "changed_on_delta_humanized", - "created_by.first_name", - "created_by.last_name", + "id", + "name", + "type", + "changed_by.first_name", + "changed_by.last_name", + "changed_on_delta_humanized", + "created_by.first_name", + "created_by.last_name", ] list_select_columns = list_columns show_columns = [ - "id", - "name", - "type", - "changed_by.first_name", - "changed_by.last_name", - "changed_on_delta_humanized", - "created_by.first_name", - "created_by.last_name", - "created_by" + "id", + "name", + "type", + "changed_by.first_name", + "changed_by.last_name", + "changed_on_delta_humanized", + "created_by.first_name", + "created_by.last_name", + "created_by", ] add_model_schema = TagPostSchema() @@ -83,18 +77,17 @@ class TagRestApi(BaseSupersetModelRestApi): openapi_spec_tag = "Tags" """ Override the name set for this collection of endpoints """ - openapi_spec_component_schemas = ( - TagGetResponseSchema, - ) - apispec_parameter_schemas = {} + openapi_spec_component_schemas = (TagGetResponseSchema,) openapi_spec_methods = openapi_spec_methods_override """ Overrides GET methods OpenApi descriptions """ def __repr__(self) -> str: """Deterministic string representation of the API instance for etag_cache.""" - return 'Superset.tags.api.TagRestApi@v' \ - f'{self.appbuilder.app.config["VERSION_STRING"]}' \ + return ( + "Superset.tags.api.TagRestApi@v" + f'{self.appbuilder.app.config["VERSION_STRING"]}' f'{self.appbuilder.app.config["VERSION_SHA"]}' + ) @expose("///", methods=["POST"]) @protect() diff --git a/superset/tags/commands/create.py b/superset/tags/commands/create.py index be4ad99c04b4b..66b31876ed68b 100644 --- a/superset/tags/commands/create.py +++ b/superset/tags/commands/create.py @@ -21,12 +21,9 @@ from superset.commands.base import BaseCommand, CreateMixin from superset.dao.exceptions import DAOCreateFailedError -from superset.tags.models import ObjectTypes -from superset.tags.commands.exceptions import ( - TagCreateFailedError, - TagInvalidError, -) +from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError from superset.tags.dao import TagDAO +from superset.tags.models import ObjectTypes logger = logging.getLogger(__name__) @@ -41,7 +38,8 @@ def run(self) -> Model: self.validate() try: tag = TagDAO.create_tagged_objects( - self._object_type, self._object_id, self._properties) + self._object_type, self._object_id, self._properties + ) except DAOCreateFailedError as ex: logger.exception(ex.exception) raise TagCreateFailedError() from ex diff --git a/superset/tags/commands/exceptions.py b/superset/tags/commands/exceptions.py index ddc241df3f592..5e218fed73f1d 100644 --- a/superset/tags/commands/exceptions.py +++ b/superset/tags/commands/exceptions.py @@ -16,14 +16,12 @@ # under the License. from flask_babel import lazy_gettext as _ -from superset.commands.exceptions import ( - CommandInvalidError, - CreateFailedError, -) +from superset.commands.exceptions import CommandInvalidError, CreateFailedError class TagInvalidError(CommandInvalidError): message = _("Tag parameters are invalid.") + class TagCreateFailedError(CreateFailedError): message = _("Tag could not be created.") diff --git a/superset/tags/dao.py b/superset/tags/dao.py index aff05551c7bf6..959cc6c07a634 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -30,10 +30,8 @@ class TagDAO(BaseDAO): @staticmethod def create_tagged_objects( - object_type: ObjectTypes, - object_id: int, - properties: Dict[str, Any] - ) -> None: + object_type: ObjectTypes, object_id: int, properties: Dict[str, Any] + ) -> None: tag_names = properties["tags"] tagged_objects = [] @@ -46,19 +44,15 @@ def create_tagged_objects( tag = TagDAO.get_by_name(name, type_) tagged_objects.append( - TaggedObject(object_id=object_id, - object_type=object_type, tag=tag) + TaggedObject(object_id=object_id, object_type=object_type, tag=tag) ) db.session.add_all(tagged_objects) db.session.commit() - return tag - @staticmethod - def get_by_name(name: str, type_: str) -> Tag: - tag = db.session.query(Tag).filter( - Tag.name == name, Tag.type == type_).first() + def get_by_name(name: str, type_: TagTypes) -> Tag: + tag = db.session.query(Tag).filter(Tag.name == name, Tag.type == type_).first() if not tag: tag = Tag(name=name, type=type_) # security_manager.raise_for_tag_access(tag) diff --git a/superset/tags/models.py b/superset/tags/models.py index 1258f3b96110c..f454507a29fa9 100644 --- a/superset/tags/models.py +++ b/superset/tags/models.py @@ -39,7 +39,6 @@ from superset.models.dashboard import Dashboard from superset.models.slice import Slice from superset.models.sql_lab import Query - from superset.connectors.sqla.models import SqlaTable Session = sessionmaker(autoflush=False) @@ -84,6 +83,7 @@ class Tag(Model, AuditMixinNullable): name = Column(String(250), unique=True) type = Column(Enum(TagTypes)) + class TaggedObject(Model, AuditMixinNullable): """An association between an object and a tag.""" @@ -91,8 +91,12 @@ class TaggedObject(Model, AuditMixinNullable): __tablename__ = "tagged_object" id = Column(Integer, primary_key=True) tag_id = Column(Integer, ForeignKey("tag.id")) - object_id = Column(Integer, ForeignKey("dashboards.id"), - ForeignKey("slices.id"), ForeignKey("saved_query.id")) + object_id = Column( + Integer, + ForeignKey("dashboards.id"), + ForeignKey("slices.id"), + ForeignKey("saved_query.id"), + ) object_type = Column(Enum(ObjectTypes)) tag = relationship("Tag", backref="objects") @@ -157,8 +161,7 @@ def after_insert( cls._add_owners(session, target) # add `type:` tags - tag = get_tag("type:{0}".format(cls.object_type), - session, TagTypes.type) + tag = get_tag("type:{0}".format(cls.object_type), session, TagTypes.type) tagged_object = TaggedObject( tag_id=tag.id, object_id=target.id, object_type=cls.object_type ) @@ -271,7 +274,7 @@ def after_delete( cls, _mapper: Mapper, connection: Connection, target: FavStar ) -> None: session = Session(bind=connection) - name = f'favorited_by:{target.user_id}' + name = f"favorited_by:{target.user_id}" query = ( session.query(TaggedObject.id) .join(Tag) diff --git a/superset/tags/schemas.py b/superset/tags/schemas.py index de44d85505fee..e823a9c2aa749 100644 --- a/superset/tags/schemas.py +++ b/superset/tags/schemas.py @@ -29,8 +29,7 @@ }, "info": { "get": { - "description": "Several metadata information about tag API " - "endpoints.", + "description": "Several metadata information about tag API " "endpoints.", } }, } diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index 17f292a9bd449..5142039475744 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -33,8 +33,8 @@ from superset.models.dashboard import Dashboard from superset.models.slice import Slice from superset.models.sql_lab import SavedQuery -from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes from superset.superset_typing import FlaskResponse +from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success @@ -49,6 +49,7 @@ def process_template(content: str) -> str: } return template.render(context) + class TaggedObjectsModelView(SupersetModelView): route_base = "/superset/all_entities" datamodel = SQLAInterface(Tag) @@ -62,6 +63,7 @@ def list(self) -> FlaskResponse: return super().render_app_template() + class TaggedObjectView(BaseSupersetView): @staticmethod def is_enabled() -> bool: diff --git a/superset/views/base_api.py b/superset/views/base_api.py index ad4299762e0c4..a161cc8622651 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -28,18 +28,18 @@ from marshmallow import fields, Schema from sqlalchemy import and_, distinct, func from sqlalchemy.orm.query import Query -from superset.connectors.sqla.models import SqlaTable +from superset.connectors.sqla.models import SqlaTable from superset.exceptions import InvalidPayloadFormatError from superset.extensions import db, event_logger, security_manager from superset.models.core import FavStar from superset.models.dashboard import Dashboard from superset.models.slice import Slice -from superset.tags.models import Tag from superset.schemas import error_payload_content from superset.sql_lab import Query as SqllabQuery from superset.stats_logger import BaseStatsLogger from superset.superset_typing import FlaskResponse +from superset.tags.models import Tag from superset.utils.core import get_user_id, time_function from superset.views.base import handle_api_exception @@ -175,9 +175,7 @@ def apply(self, query: Query, value: Any) -> Query: tags_query = ( db.session.query(self.model.id) .join(self.model.tags) - .filter( - Tag.name.ilike(ilike_value) - ) + .filter(Tag.name.ilike(ilike_value)) ) return query.filter(self.model.id.in_(tags_query)) diff --git a/superset/views/tags.py b/superset/views/tags.py index fd4535f275b7d..bd2db86e77a18 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -35,8 +35,8 @@ from superset.models.slice import Slice from superset.models.sql_lab import SavedQuery from superset.superset_typing import FlaskResponse -from superset.views.base import SupersetModelView from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success @@ -93,7 +93,8 @@ def tags(self) -> FlaskResponse: # pylint: disable=no-self-use "changed_on": obj.changed_on, "changed_by": obj.changed_by_fk, "created_by": obj.created_by_fk, - } for obj in query + } + for obj in query ] return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) @@ -146,14 +147,12 @@ def post( # pylint: disable=no-self-use else: type_ = TagTypes.custom - tag = db.session.query(Tag).filter_by( - name=name, type=type_).first() + tag = db.session.query(Tag).filter_by(name=name, type=type_).first() if not tag: tag = Tag(name=name, type=type_) tagged_objects.append( - TaggedObject(object_id=object_id, - object_type=object_type, tag=tag) + TaggedObject(object_id=object_id, object_type=object_type, tag=tag) ) db.session.add_all(tagged_objects) @@ -184,10 +183,8 @@ def delete_tagged_objects( # pylint: disable=no-self-use @has_access_api @expose("/tags", methods=["DELETE"]) - def delete_tags( # pylint: disable=no-self-use - self - ) -> FlaskResponse: - """Remove tags, and all tagged objects with that tag """ + def delete_tags(self) -> FlaskResponse: # pylint: disable=no-self-use + """Remove tags, and all tagged objects with that tag""" tag_names = request.get_json(force=True) if not tag_names: return Response(status=403) @@ -214,8 +211,7 @@ def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use return json_success(json.dumps([])) # filter types - types = [type_ for type_ in request.args.get( - "types", "").split(",") if type_] + types = [type_ for type_ in request.args.get("types", "").split(",") if type_] results: List[Dict[str, Any]] = [] diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index cc02abe62941b..a97146405efc6 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -37,14 +37,14 @@ TAGS_FIXTURE_COUNT = 10 TAGS_LIST_COLUMNS = [ - 'id', - 'name', - 'type', - 'changed_by.first_name', - 'changed_by.last_name', - 'changed_on_delta_humanized', - 'created_by.first_name', - 'created_by.last_name' + "id", + "name", + "type", + "changed_by.first_name", + "changed_by.last_name", + "changed_on_delta_humanized", + "created_by.first_name", + "created_by.last_name", ] @@ -73,7 +73,7 @@ def create_tags(self): tags.append( self.insert_tag( name=f"example_tag_{cx}", - tag_type='custom', + tag_type="custom", ) ) @@ -90,7 +90,7 @@ def test_get_tag(self): """ tag = self.insert_tag( name="test get tag", - tag_type='custom', + tag_type="custom", ) self.login(username="admin") uri = f"api/v1/tag/{tag.id}" @@ -102,7 +102,7 @@ def test_get_tag(self): "created_by": None, "id": tag.id, "name": "test get tag", - "type": TagTypes.custom.value + "type": TagTypes.custom.value, } data = json.loads(rv.data.decode("utf-8")) for key, value in expected_result.items(): @@ -115,7 +115,7 @@ def test_get_tag_not_found(self): """ Query API: Test get query not found """ - tag = self.insert_tag(name="test tag", tag_type='custom') + tag = self.insert_tag(name="test tag", tag_type="custom") max_id = db.session.query(func.max(Tag.id)).scalar() self.login(username="admin") uri = f"api/v1/tag/{max_id + 1}" @@ -135,6 +135,6 @@ def test_get_list_tag(self): rv = self.client.get(uri) self.assertEqual(rv.status_code, 200) data = json.loads(rv.data.decode("utf-8")) - assert data['count'] == TAGS_FIXTURE_COUNT + assert data["count"] == TAGS_FIXTURE_COUNT # check expected columns assert data["list_columns"] == TAGS_LIST_COLUMNS From 0b30e486bf8f9c6afa82de6d3c7d242b70cc845c Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 13 Dec 2022 09:58:22 -0500 Subject: [PATCH 56/76] feedback changes --- .../src/components/Tags/Tag.test.tsx | 4 +- superset-frontend/src/components/Tags/Tag.tsx | 14 ++-- .../src/components/Tags/TagsList.tsx | 2 +- .../components/PropertiesModal/index.tsx | 76 +++++++++--------- .../components/PropertiesModal/index.tsx | 78 +++++++++---------- superset-frontend/src/types/TagType.ts | 6 +- .../views/CRUD/allentities/AllEntities.tsx | 40 +++++----- .../CRUD/allentities/AllEntitiesTable.tsx | 11 ++- .../src/views/CRUD/tags/TagList.tsx | 1 - 9 files changed, 121 insertions(+), 111 deletions(-) diff --git a/superset-frontend/src/components/Tags/Tag.test.tsx b/superset-frontend/src/components/Tags/Tag.test.tsx index b797665b13c4f..0ff7b2e85a3f3 100644 --- a/superset-frontend/src/components/Tags/Tag.test.tsx +++ b/superset-frontend/src/components/Tags/Tag.test.tsx @@ -24,9 +24,9 @@ import Tag from './Tag'; const mockedProps: TagType = { name: 'example-tag', id: 1, - onDelete: null, + onDelete: undefined, editable: false, - onClick: null, + onClick: undefined, }; test('should render', () => { diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index 4070f9d1c2676..6f793044d05fe 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -24,22 +24,24 @@ import React, { useMemo } from 'react'; import { Tooltip } from 'src/components/Tooltip'; const StyledTag = styled(AntdTag)` - margin-top: ${({ theme }) => theme.gridUnit}px; - margin-bottom: ${({ theme }) => theme.gridUnit}px; - font-size: ${({ theme }) => theme.typography.sizes.s}px; + ${({ theme }) => ` + margin-top: ${theme.gridUnit}px; + margin-bottom: ${theme.gridUnit}px; + font-size: ${theme.typography.sizes.s}px; + `}; `; const Tag = ({ name, id, index = undefined, - onDelete = null, + onDelete = undefined, editable = false, - onClick = null, + onClick = undefined, }: TagType) => { const isLongTag = useMemo(() => name.length > 20, [name]); - const handleClose = () => (index ? onDelete(index) : null); + const handleClose = () => (index ? onDelete?.(index) : null); const tagElem = ( <> diff --git a/superset-frontend/src/components/Tags/TagsList.tsx b/superset-frontend/src/components/Tags/TagsList.tsx index c5c669ee00d61..102e6d2ced31f 100644 --- a/superset-frontend/src/components/Tags/TagsList.tsx +++ b/superset-frontend/src/components/Tags/TagsList.tsx @@ -70,7 +70,7 @@ const TagsList = ({ return ( - {tagsIsLong === true && typeof tempMaxTags === 'number' ? ( + {tagsIsLong && typeof tempMaxTags === 'number' ? ( <> {tags.slice(0, tempMaxTags - 1).map((tag: TagType, index) => ( { + // update the tags for this object + // add tags that are in new tags, but not in old tags + // eslint-disable-next-line array-callback-return + newTags.map((tag: TagType) => { + if (!oldTags.some(t => t.name === tag.name)) { + addTag( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + includeTypes: false, + }, + tag.name, + () => {}, + () => {}, + ); + } + }); + // delete tags that are in old tags, but not in new tags + // eslint-disable-next-line array-callback-return + oldTags.map((tag: TagType) => { + if (!newTags.some(t => t.name === tag.name)) { + deleteTaggedObjects( + { + objectType: OBJECT_TYPES.DASHBOARD, + objectId: dashboardId, + }, + tag, + () => {}, + () => {}, + ); + } + }); + }; + const onFinish = () => { const { title, slug, certifiedBy, certificationDetails } = form.getFieldsValue(); @@ -376,8 +411,8 @@ const PropertiesModal = ({ handleErrorResponse(error); }, ); - } catch (error: any) { - console.log(error); + } catch (error) { + handleErrorResponse(error); } } @@ -577,46 +612,11 @@ const PropertiesModal = ({ addDangerToast(`Error fetching tags: ${error.text}`); }, ); - } catch (error: any) { + } catch (error) { handleErrorResponse(error); } }, [dashboardId]); - const updateTags = (oldTags: TagType[], newTags: TagType[]) => { - // update the tags for this object - // add tags that are in new tags, but not in old tags - // eslint-disable-next-line array-callback-return - newTags.map((tag: TagType) => { - if (!oldTags.some(t => t.name === tag.name)) { - addTag( - { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: dashboardId, - includeTypes: false, - }, - tag.name, - () => {}, - () => {}, - ); - } - }); - // delete tags that are in old tags, but not in new tags - // eslint-disable-next-line array-callback-return - oldTags.map((tag: TagType) => { - if (!newTags.some(t => t.name === tag.name)) { - deleteTaggedObjects( - { - objectType: OBJECT_TYPES.DASHBOARD, - objectId: dashboardId, - }, - tag, - () => {}, - () => {}, - ); - } - }); - }; - const handleChangeTags = (values: { label: string; value: number }[]) => { // triggered whenever a new tag is selected or a tag was deselected // on new tag selected, add the tag diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 9d3f802f1b6c0..d415afd66cbd4 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -138,6 +138,41 @@ function PropertiesModal({ [], ); + const updateTags = (oldTags: TagType[], newTags: TagType[]) => { + // update the tags for this object + // add tags that are in new tags, but not in old tags + // eslint-disable-next-line array-callback-return + newTags.map((tag: TagType) => { + if (!oldTags.some(t => t.name === tag.name)) { + addTag( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + includeTypes: false, + }, + tag.name, + () => {}, + () => {}, + ); + } + }); + // delete tags that are in old tags, but not in new tags + // eslint-disable-next-line array-callback-return + oldTags.map((tag: TagType) => { + if (!newTags.some(t => t.name === tag.name)) { + deleteTaggedObjects( + { + objectType: OBJECT_TYPES.CHART, + objectId: slice.slice_id, + }, + tag, + () => {}, + () => {}, + ); + } + }); + }; + const onSubmit = async (values: { certified_by?: string; certification_details?: string; @@ -181,8 +216,8 @@ function PropertiesModal({ showError(error); }, ); - } catch (error: any) { - console.log(error); + } catch (error) { + showError(error); } } @@ -236,46 +271,11 @@ function PropertiesModal({ showError(error); }, ); - } catch (error: any) { - console.log(error); + } catch (error) { + showError(error); } }, [slice.slice_id]); - const updateTags = (oldTags: TagType[], newTags: TagType[]) => { - // update the tags for this object - // add tags that are in new tags, but not in old tags - // eslint-disable-next-line array-callback-return - newTags.map((tag: TagType) => { - if (!oldTags.some(t => t.name === tag.name)) { - addTag( - { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - includeTypes: false, - }, - tag.name, - () => {}, - () => {}, - ); - } - }); - // delete tags that are in old tags, but not in new tags - // eslint-disable-next-line array-callback-return - oldTags.map((tag: TagType) => { - if (!newTags.some(t => t.name === tag.name)) { - deleteTaggedObjects( - { - objectType: OBJECT_TYPES.CHART, - objectId: slice.slice_id, - }, - tag, - () => {}, - () => {}, - ); - } - }); - }; - const handleChangeTags = (values: { label: string; value: number }[]) => { // triggered whenever a new tag is selected or a tag was deselected // on new tag selected, add the tag diff --git a/superset-frontend/src/types/TagType.ts b/superset-frontend/src/types/TagType.ts index 2be7d96b7c6fa..4c1b85b724915 100644 --- a/superset-frontend/src/types/TagType.ts +++ b/superset-frontend/src/types/TagType.ts @@ -17,12 +17,14 @@ * under the License. */ +import { MouseEventHandler } from 'react'; + export interface TagType { id?: string | number; type?: string | number; editable?: boolean; - onDelete?: any; - onClick?: any; + onDelete?: (index: number) => void; + onClick?: MouseEventHandler; name: string; index?: number | undefined; } diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx index abef9db46155d..2950a544eb93f 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx @@ -17,37 +17,40 @@ * under the License. */ import React, { useEffect, useState } from 'react'; -import { styled } from '@superset-ui/core'; +import { logging, styled, t } from '@superset-ui/core'; import Tag from 'src/types/TagType'; import { StringParam, useQueryParam } from 'use-query-params'; import withToasts from 'src/components/MessageToasts/withToasts'; import SelectControl from 'src/explore/components/controls/SelectControl'; import { fetchSuggestions } from 'src/tags'; +import { addDangerToast } from 'src/components/MessageToasts/actions'; import AllEntitiesTable from './AllEntitiesTable'; const AllEntitiesContainer = styled.div` - background-color: ${({ theme }) => theme.colors.grayscale.light4}; + ${({ theme }) => ` + background-color: ${theme.colors.grayscale.light4}; .select-control { - margin-left: ${({ theme }) => theme.gridUnit * 4}px; - margin-right: ${({ theme }) => theme.gridUnit * 4}px; - margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; + margin-left: ${theme.gridUnit * 4}px; + margin-right: ${theme.gridUnit * 4}px; + margin-bottom: ${theme.gridUnit * 2}px; } .select-control-label { text-transform: uppercase; - font-size: ${({ theme }) => theme.gridUnit * 3}px; - color: ${({ theme }) => theme.colors.grayscale.base}; - margin-bottom: ${({ theme }) => theme.gridUnit * 1}px; - } + font-size: ${theme.gridUnit * 3}px; + color: ${theme.colors.grayscale.base}; + margin-bottom: ${theme.gridUnit * 1}px; + }`} `; const AllEntitiesNav = styled.div` - height: 50px; - background-color: ${({ theme }) => theme.colors.grayscale.light5}; - margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; + ${({ theme }) => ` + height: ${theme.gridUnit * 12.5}px; + background-color: ${theme.colors.grayscale.light5}; + margin-bottom: ${theme.gridUnit * 4}px; .navbar-brand { - margin-left: ${({ theme }) => theme.gridUnit * 2}px; - font-weight: ${({ theme }) => theme.typography.weights.bold}; - } + margin-left: ${theme.gridUnit * 2}px; + font-weight: ${theme.typography.weights.bold}; + }`}; `; function AllEntities() { @@ -62,7 +65,8 @@ function AllEntities() { setTagSuggestions(tagSuggestions); }, (error: Response) => { - console.log(error.json()); + logging.log(error.json()); + addDangerToast(`Error Fetching Suggestions`); }, ); }, [tagsQuery]); @@ -75,10 +79,10 @@ function AllEntities() { return ( - All Entities + {t('All Entities')}
-
search by tags
+
{t('search by tags')}
({ dashboard: [], chart: [], @@ -75,15 +78,15 @@ export default function AllEntitiesTable({ setObjects(objects); }, (error: Response) => { - console.log(error.json()); + addDangerToast('Error Fetching Tagged Objects'); + logging.log(error.json()); }, ); }, [search]); - const renderTable = (type: any) => { + const renderTable = (type: objectType) => { const data = objects[type].map((o: TaggedObject) => ({ [type]: {o.name}, - // eslint-disable-next-line react/no-danger modified: moment.utc(o.changed_on).fromNow(), })); return ( diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx index 8001f3822431c..4561c965f4d90 100644 --- a/superset-frontend/src/views/CRUD/tags/TagList.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -159,7 +159,6 @@ function TagList(props: TagListProps) { className="action-button" onClick={confirmDelete} > - {/* fix icon name */} From 66827a5b03c5a5e6960690274f1defaa6a02b7d4 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 13 Dec 2022 10:41:44 -0500 Subject: [PATCH 57/76] precommit fixes --- superset-frontend/src/components/Tags/Tag.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/components/Tags/Tag.tsx b/superset-frontend/src/components/Tags/Tag.tsx index 6f793044d05fe..ecd2cb135a0b6 100644 --- a/superset-frontend/src/components/Tags/Tag.tsx +++ b/superset-frontend/src/components/Tags/Tag.tsx @@ -25,8 +25,8 @@ import { Tooltip } from 'src/components/Tooltip'; const StyledTag = styled(AntdTag)` ${({ theme }) => ` - margin-top: ${theme.gridUnit}px; - margin-bottom: ${theme.gridUnit}px; + margin-top: ${theme.gridUnit}px; + margin-bottom: ${theme.gridUnit}px; font-size: ${theme.typography.sizes.s}px; `}; `; From 74d78a340c26cb5a51af3a9da11c8129099cd897 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 20 Dec 2022 10:45:46 -0500 Subject: [PATCH 58/76] added tag name as key and value to avoid duplicating tags in the select dropdown --- superset-frontend/src/components/Tags/utils.tsx | 4 +++- .../src/dashboard/components/PropertiesModal/index.tsx | 1 + .../src/explore/components/PropertiesModal/index.tsx | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/components/Tags/utils.tsx b/superset-frontend/src/components/Tags/utils.tsx index af2853c5c0b7b..690a9b44066d0 100644 --- a/superset-frontend/src/components/Tags/utils.tsx +++ b/superset-frontend/src/components/Tags/utils.tsx @@ -38,13 +38,15 @@ const cachedSupersetGet = cacheWrapper( type SelectTagsValue = { value: string | number | undefined; label: string; + key: string | number | undefined; }; export const tagToSelectOption = ( item: Tag & { table_name: string }, ): SelectTagsValue => ({ - value: item.id, + value: item.name, label: item.name, + key: item.name, }); export const loadTags = async ( diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 521ddb5199b94..98cd69a03d19e 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -111,6 +111,7 @@ const PropertiesModal = ({ const selectTags = tags.map(tag => ({ value: tag.name, label: tag.name, + key: tag.name, })); return selectTags; }, [tags.length]); diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index d415afd66cbd4..cc15be0b6c224 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -78,6 +78,7 @@ function PropertiesModal({ const selectTags = tags.map(tag => ({ value: tag.name, label: tag.name, + key: tag.name, })); return selectTags; }, [tags.length]); From 1897e9f194dc5c7c5ddfddf1cc4f679acfa44813 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Mon, 16 Jan 2023 09:21:50 -0500 Subject: [PATCH 59/76] refactored create tag to create custom tag, changed add tag functions to use tagDao --- .../components/PropertiesModal/index.tsx | 12 ++-- superset/tags/api.py | 4 +- superset/tags/commands/create.py | 5 +- superset/tags/dao.py | 20 ++++-- superset/tags/exceptions.py | 39 +++++++++++ superset/views/all_entities.py | 56 +++++++++------- superset/views/tags.py | 56 +++++++++------- tests/integration_tests/tags/dao_tests.py | 66 +++++++++++++++++++ 8 files changed, 196 insertions(+), 62 deletions(-) create mode 100644 superset/tags/exceptions.py create mode 100644 tests/integration_tests/tags/dao_tests.py diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 98cd69a03d19e..b12102d324247 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -724,11 +724,13 @@ const PropertiesModal = ({

- - -

{t('Tags')}

- -
+ {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( + + +

{t('Tags')}

+ +
+ ) : null} {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) ? ( diff --git a/superset/tags/api.py b/superset/tags/api.py index d89a24da3a680..6b2a239c15512 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -23,7 +23,7 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.extensions import event_logger -from superset.tags.commands.create import CreateTagCommand +from superset.tags.commands.create import CreateCustomTagCommand from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError from superset.tags.models import ObjectTypes, Tag from superset.tags.schemas import ( @@ -148,7 +148,7 @@ def add_new_tags(self, object_type: ObjectTypes, object_id: int) -> Response: except ValidationError as error: return self.response_400(message=error.messages) try: - new_model = CreateTagCommand(object_type, object_id, item).run() + new_model = CreateCustomTagCommand(object_type, object_id, item).run() return self.response(201, id=new_model.id, result=item) except TagInvalidError as ex: return self.response_422(message=ex.normalized_messages()) diff --git a/superset/tags/commands/create.py b/superset/tags/commands/create.py index 66b31876ed68b..06d6026eac401 100644 --- a/superset/tags/commands/create.py +++ b/superset/tags/commands/create.py @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__) -class CreateTagCommand(CreateMixin, BaseCommand): +class CreateCustomTagCommand(CreateMixin, BaseCommand): def __init__(self, object_type: ObjectTypes, object_id: int, data: Dict[str, Any]): self._object_type = object_type self._object_id = object_id @@ -37,7 +37,7 @@ def __init__(self, object_type: ObjectTypes, object_id: int, data: Dict[str, Any def run(self) -> Model: self.validate() try: - tag = TagDAO.create_tagged_objects( + tag = TagDAO.create_custom_tagged_objects( self._object_type, self._object_id, self._properties ) except DAOCreateFailedError as ex: @@ -51,7 +51,6 @@ def validate(self) -> None: # Validate object_id if self._object_id == 0: exceptions.append(TagCreateFailedError()) - if exceptions: exception = TagInvalidError() exception.add_list(exceptions) diff --git a/superset/tags/dao.py b/superset/tags/dao.py index 39ddc7dcfec93..f36fee720ab8d 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -18,7 +18,9 @@ from typing import Any, Dict from superset.dao.base import BaseDAO +from superset.dao.exceptions import DAOCreateFailedError from superset.extensions import db +from superset.tags.exceptions import InvalidTagNameError from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes logger = logging.getLogger(__name__) @@ -27,20 +29,26 @@ class TagDAO(BaseDAO): model_cls = Tag # base_filter = TagAccessFilter + @staticmethod + def validate_tag_name(tag_name: str) -> bool: + if ":" in tag_name: + return False + return True @staticmethod - def create_tagged_objects( + def create_custom_tagged_objects( object_type: ObjectTypes, object_id: int, properties: Dict[str, Any] ) -> None: tag_names = properties["tags"] tagged_objects = [] for name in tag_names: - if ":" in name: - type_name = name.split(":", 1)[0] - type_ = TagTypes[type_name] - else: - type_ = TagTypes.custom + if not TagDAO.validate_tag_name(name): + logger.error(f"failed create tag: {name}") + raise DAOCreateFailedError( + message="Invalid Tag Name (cannot contain ':')" + ) + type_ = TagTypes.custom tag_name = name.strip() tag = TagDAO.get_by_name(tag_name, type_) tagged_objects.append( diff --git a/superset/tags/exceptions.py b/superset/tags/exceptions.py new file mode 100644 index 0000000000000..e3aaa5d235a4e --- /dev/null +++ b/superset/tags/exceptions.py @@ -0,0 +1,39 @@ +# 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. +from flask_babel import lazy_gettext as _ +from marshmallow.validate import ValidationError + +from superset.commands.exceptions import ( + CommandException, + CommandInvalidError, + CreateFailedError, + DeleteFailedError, + ForbiddenError, + ImportFailedError, + UpdateFailedError, +) + + +class InvalidTagNameError(ValidationError): + """ + Marshmallow validation error for invalid Tag name + """ + + def __init__(self) -> None: + super().__init__( + [_("Tag name is invalid (cannot contain ':')")], field_name="name" + ) diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index f278092113f1d..b9831a82ffe60 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -16,6 +16,7 @@ # under the License. from __future__ import absolute_import, division, print_function, unicode_literals +import logging from typing import Any, Dict, List import simplejson as json @@ -25,6 +26,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.security.decorators import has_access, has_access_api from jinja2.sandbox import SandboxedEnvironment +from marshmallow import ValidationError from sqlalchemy import and_, func from werkzeug.exceptions import NotFound @@ -34,11 +36,16 @@ from superset.models.slice import Slice from superset.models.sql_lab import SavedQuery from superset.superset_typing import FlaskResponse +from superset.tags.commands.create import CreateCustomTagCommand +from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.tags.schemas import TagPostSchema from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success +logger = logging.getLogger(__name__) + def process_template(content: str) -> str: env = SandboxedEnvironment() @@ -111,30 +118,33 @@ def get( # pylint: disable=no-self-use def post( # pylint: disable=no-self-use self, object_type: ObjectTypes, object_id: int ) -> FlaskResponse: - """Add new tags to an object.""" - if object_id == 0: - return Response(status=404) - - tagged_objects = [] - for name in request.get_json(force=True): - if ":" in name: - type_name = name.split(":", 1)[0] - type_ = TagTypes[type_name] - else: - type_ = TagTypes.custom - tag_name = name.strip() - tag = db.session.query(Tag).filter_by(name=tag_name, type=type_).first() - if not tag: - tag = Tag(name=tag_name, type=type_) - - tagged_objects.append( - TaggedObject(object_id=object_id, object_type=object_type, tag=tag) + """ + --- + post: + description: >- + Add new tags to an object.. + requestBody: + array of tag names + """ + data = dict() + data["tags"] = request.get_json(force=True) + try: + CreateCustomTagCommand(object_type, object_id, data).run() + return Response(status=201) + except TagInvalidError as ex: + logger.error( + "Invalid tag: %s", + str(ex), ) - - db.session.add_all(tagged_objects) - db.session.commit() - - return Response(status=201) # 201 CREATED + return Response(response="tag invalid", status=422) + except TagCreateFailedError as ex: + logger.error( + "Error creating model %s: %s", + self.__class__.__name__, + str(ex), + exc_info=True, + ) + return Response(response="Error creating tag", status=422) @has_access_api @expose("/tags///", methods=["DELETE"]) diff --git a/superset/views/tags.py b/superset/views/tags.py index fdf7fecf86a15..7ea86391a87be 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -16,6 +16,7 @@ # under the License. from __future__ import absolute_import, division, print_function, unicode_literals +import logging from typing import Any, Dict, List import simplejson as json @@ -25,6 +26,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.security.decorators import has_access, has_access_api from jinja2.sandbox import SandboxedEnvironment +from marshmallow import ValidationError from sqlalchemy import and_, func from werkzeug.exceptions import NotFound @@ -35,11 +37,16 @@ from superset.models.slice import Slice from superset.models.sql_lab import SavedQuery from superset.superset_typing import FlaskResponse +from superset.tags.commands.create import CreateCustomTagCommand +from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.tags.schemas import TagPostSchema from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success +logger = logging.getLogger(__name__) + def process_template(content: str) -> str: env = SandboxedEnvironment() @@ -135,30 +142,33 @@ def get( # pylint: disable=no-self-use def post( # pylint: disable=no-self-use self, object_type: ObjectTypes, object_id: int ) -> FlaskResponse: - """Add new tags to an object.""" - if object_id == 0: - return Response(status=404) - - tagged_objects = [] - for name in request.get_json(force=True): - if ":" in name: - type_name = name.split(":", 1)[0] - type_ = TagTypes[type_name] - else: - type_ = TagTypes.custom - tag_name = name.strip() - tag = db.session.query(Tag).filter_by(name=tag_name, type=type_).first() - if not tag: - tag = Tag(name=tag_name, type=type_) - - tagged_objects.append( - TaggedObject(object_id=object_id, object_type=object_type, tag=tag) + """ + --- + post: + description: >- + Add new tags to an object.. + requestBody: + array of tag names + """ + data = dict() + data["tags"] = request.get_json(force=True) + try: + CreateCustomTagCommand(object_type, object_id, data).run() + return Response(status=201) + except TagInvalidError as ex: + logger.error( + "Invalid tag: %s", + str(ex), ) - - db.session.add_all(tagged_objects) - db.session.commit() - - return Response(status=201) # 201 CREATED + return Response(response="tag invalid", status=422) + except TagCreateFailedError as ex: + logger.error( + "Error creating model %s: %s", + self.__class__.__name__, + str(ex), + exc_info=True, + ) + return Response(response="Error creating model", status=422) @has_access_api @expose("/tags///", methods=["DELETE"]) diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py new file mode 100644 index 0000000000000..2a3a13ae0d73a --- /dev/null +++ b/tests/integration_tests/tags/dao_tests.py @@ -0,0 +1,66 @@ +# 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. +# isort:skip_file +import copy +import json +import time +from unittest.mock import patch +import pytest +from superset.dao.exceptions import DAOCreateFailedError +from superset.tags.dao import TagDAO +from superset.tags.exceptions import InvalidTagNameError +from superset.tags.models import ObjectTypes, Tag + +import tests.integration_tests.test_app # pylint: disable=unused-import +from superset import db, security_manager +from superset.dashboards.dao import DashboardDAO +from superset.models.dashboard import Dashboard +from tests.integration_tests.base_tests import SupersetTestCase +from tests.integration_tests.fixtures.world_bank_dashboard import ( + load_world_bank_dashboard_with_slices, + load_world_bank_data, +) +from tests.integration_tests.fixtures.tags import with_tagging_system_feature + + +class TestTagsDAO(SupersetTestCase): + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + def test_create_tag(self): + # test that a tag cannot be added if it has ':' in it + try: + TagDAO.create_custom_tagged_objects( + object_type=ObjectTypes.dashboard, + object_id=1, + properties={"tags": ["invalid:example tag 1"]}, + ) + except Exception as e: + assert type(e) is DAOCreateFailedError + + # test that a tag can be added if it has a valid name + try: + TagDAO.create_custom_tagged_objects( + object_type=ObjectTypes.dashboard, + object_id=1, + properties={"tags": ["example tag 1"]}, + ) + except Exception as e: + # should not be an exception + assert e is None + + # check if tag exists + assert db.session.query(Tag).filter(Tag.name == "example tag 1").first() From 230b3d47d799a9be3afe7fb4877d189413b9e4a8 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Mon, 16 Jan 2023 09:52:51 -0500 Subject: [PATCH 60/76] lint fixes --- superset/tags/dao.py | 2 -- superset/tags/exceptions.py | 11 ----------- superset/views/all_entities.py | 6 ++---- superset/views/tags.py | 6 ++---- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/superset/tags/dao.py b/superset/tags/dao.py index f36fee720ab8d..a2f4eb6d7f018 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -20,7 +20,6 @@ from superset.dao.base import BaseDAO from superset.dao.exceptions import DAOCreateFailedError from superset.extensions import db -from superset.tags.exceptions import InvalidTagNameError from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes logger = logging.getLogger(__name__) @@ -44,7 +43,6 @@ def create_custom_tagged_objects( tagged_objects = [] for name in tag_names: if not TagDAO.validate_tag_name(name): - logger.error(f"failed create tag: {name}") raise DAOCreateFailedError( message="Invalid Tag Name (cannot contain ':')" ) diff --git a/superset/tags/exceptions.py b/superset/tags/exceptions.py index e3aaa5d235a4e..f7800ed72b2c0 100644 --- a/superset/tags/exceptions.py +++ b/superset/tags/exceptions.py @@ -17,17 +17,6 @@ from flask_babel import lazy_gettext as _ from marshmallow.validate import ValidationError -from superset.commands.exceptions import ( - CommandException, - CommandInvalidError, - CreateFailedError, - DeleteFailedError, - ForbiddenError, - ImportFailedError, - UpdateFailedError, -) - - class InvalidTagNameError(ValidationError): """ Marshmallow validation error for invalid Tag name diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index b9831a82ffe60..b1e6d1f510d4f 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -26,7 +26,6 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.security.decorators import has_access, has_access_api from jinja2.sandbox import SandboxedEnvironment -from marshmallow import ValidationError from sqlalchemy import and_, func from werkzeug.exceptions import NotFound @@ -38,8 +37,7 @@ from superset.superset_typing import FlaskResponse from superset.tags.commands.create import CreateCustomTagCommand from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError -from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes -from superset.tags.schemas import TagPostSchema +from superset.tags.models import ObjectTypes, Tag, TaggedObject from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success @@ -115,7 +113,7 @@ def get( # pylint: disable=no-self-use @has_access_api @expose("/tags///", methods=["POST"]) - def post( # pylint: disable=no-self-use + def post( self, object_type: ObjectTypes, object_id: int ) -> FlaskResponse: """ diff --git a/superset/views/tags.py b/superset/views/tags.py index 7ea86391a87be..68a7cd6111237 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -26,7 +26,6 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.security.decorators import has_access, has_access_api from jinja2.sandbox import SandboxedEnvironment -from marshmallow import ValidationError from sqlalchemy import and_, func from werkzeug.exceptions import NotFound @@ -39,8 +38,7 @@ from superset.superset_typing import FlaskResponse from superset.tags.commands.create import CreateCustomTagCommand from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError -from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes -from superset.tags.schemas import TagPostSchema +from superset.tags.models import ObjectTypes, Tag, TaggedObject from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success @@ -139,7 +137,7 @@ def get( # pylint: disable=no-self-use @has_access_api @expose("/tags///", methods=["POST"]) - def post( # pylint: disable=no-self-use + def post( self, object_type: ObjectTypes, object_id: int ) -> FlaskResponse: """ From 7bf806f8b06a429bc14f661f36e72ec9654ef273 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Thu, 19 Jan 2023 14:15:57 -0500 Subject: [PATCH 61/76] precommit fixes --- superset/tags/exceptions.py | 1 + superset/views/all_entities.py | 4 +--- superset/views/tags.py | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/superset/tags/exceptions.py b/superset/tags/exceptions.py index f7800ed72b2c0..d1d9005bb962c 100644 --- a/superset/tags/exceptions.py +++ b/superset/tags/exceptions.py @@ -17,6 +17,7 @@ from flask_babel import lazy_gettext as _ from marshmallow.validate import ValidationError + class InvalidTagNameError(ValidationError): """ Marshmallow validation error for invalid Tag name diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index b1e6d1f510d4f..f8eec6d9521c2 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -113,9 +113,7 @@ def get( # pylint: disable=no-self-use @has_access_api @expose("/tags///", methods=["POST"]) - def post( - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: + def post(self, object_type: ObjectTypes, object_id: int) -> FlaskResponse: """ --- post: diff --git a/superset/views/tags.py b/superset/views/tags.py index 68a7cd6111237..bbb338b0932a9 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -137,9 +137,7 @@ def get( # pylint: disable=no-self-use @has_access_api @expose("/tags///", methods=["POST"]) - def post( - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: + def post(self, object_type: ObjectTypes, object_id: int) -> FlaskResponse: """ --- post: From 137d4d6f8a618cdf99da0acb5e97792b2edf2a4a Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Fri, 20 Jan 2023 09:35:12 -0500 Subject: [PATCH 62/76] fixing properties modal tests to not include tags --- .../components/PropertiesModal/PropertiesModal.test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx index e5d0cbf1b16ff..882c9daac088a 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx @@ -178,7 +178,7 @@ test('should render - FeatureFlag disabled', async () => { expect( screen.getByRole('heading', { name: 'Certification' }), ).toBeInTheDocument(); - expect(screen.getAllByRole('heading')).toHaveLength(6); + expect(screen.getAllByRole('heading')).toHaveLength(5); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Advanced' })).toBeInTheDocument(); @@ -217,7 +217,6 @@ test('should render - FeatureFlag enabled', async () => { expect( screen.getByRole('heading', { name: 'Certification' }), ).toBeInTheDocument(); - expect(screen.getByRole('heading', { name: 'Tags' })).toBeInTheDocument(); expect(screen.getAllByRole('heading')).toHaveLength(5); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); From e34360945d42be7cb2addcaa19c7c061afd56886 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Fri, 20 Jan 2023 10:13:06 -0500 Subject: [PATCH 63/76] added check for tags heading --- .../components/PropertiesModal/PropertiesModal.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx index 882c9daac088a..2ef1fe2358956 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx @@ -217,6 +217,10 @@ test('should render - FeatureFlag enabled', async () => { expect( screen.getByRole('heading', { name: 'Certification' }), ).toBeInTheDocument(); + // Tags will be included since isFeatureFlag always returns true in this test + expect( + screen.getByRole('heading', { name: 'Tags' }), + ).toBeInTheDocument(); expect(screen.getAllByRole('heading')).toHaveLength(5); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); From 7ca0509a93e87ebdd42d7f45e402ec52c1b00d00 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Fri, 20 Jan 2023 13:09:20 -0500 Subject: [PATCH 64/76] lint fix --- .../components/PropertiesModal/PropertiesModal.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx index 2ef1fe2358956..3fde5e8ca6e9c 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx @@ -218,9 +218,7 @@ test('should render - FeatureFlag enabled', async () => { screen.getByRole('heading', { name: 'Certification' }), ).toBeInTheDocument(); // Tags will be included since isFeatureFlag always returns true in this test - expect( - screen.getByRole('heading', { name: 'Tags' }), - ).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Tags' })).toBeInTheDocument(); expect(screen.getAllByRole('heading')).toHaveLength(5); expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); From 2ebaaccd8bb00f8fbeb875f271cb87a47feab955 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Wed, 1 Feb 2023 15:34:22 -0500 Subject: [PATCH 65/76] moved endpoints to api and added test --- superset-frontend/src/tags.ts | 88 +++++-- .../views/CRUD/allentities/AllEntities.tsx | 47 ++-- .../CRUD/allentities/AllEntitiesTable.tsx | 7 +- .../src/views/CRUD/tags/TagList.tsx | 2 +- superset/constants.py | 4 + superset/dashboards/api.py | 4 +- superset/tags/api.py | 242 +++++++++++++++-- superset/tags/commands/create.py | 28 +- superset/tags/commands/delete.py | 114 ++++++++ superset/tags/commands/exceptions.py | 40 ++- superset/tags/commands/utils.py | 28 ++ superset/tags/dao.py | 206 ++++++++++++++- superset/tags/schemas.py | 12 + superset/views/all_entities.py | 199 +------------- superset/views/tags.py | 247 +----------------- tests/integration_tests/tags/api_tests.py | 215 ++++++++++++++- .../integration_tests/tags/commands_tests.py | 158 +++++++++++ tests/integration_tests/tags/dao_tests.py | 228 ++++++++++++++-- 18 files changed, 1319 insertions(+), 550 deletions(-) create mode 100644 superset/tags/commands/delete.py create mode 100644 superset/tags/commands/utils.py create mode 100644 tests/integration_tests/tags/commands_tests.py diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index cf5a3432623ad..d13be2f0ffa90 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -19,17 +19,37 @@ import { JsonObject, SupersetClient } from '@superset-ui/core'; import Tag from 'src/types/TagType'; +export const OBJECT_TYPES_VALUES = Object.freeze([ + 'dashboard', + 'chart', + 'saved_query', +]); + export const OBJECT_TYPES = Object.freeze({ DASHBOARD: 'dashboard', CHART: 'chart', - QUERY: 'query', + QUERY: 'saved_query', }); +const OBJECT_TYPE_ID_MAP = { + saved_query: 1, + chart: 2, + dashboard: 3, +}; + +const map_object_type_to_id = (objectType: string) => { + if (!OBJECT_TYPES_VALUES.includes(objectType)) { + const msg = `objectType ${objectType} is invalid`; + throw new Error(msg); + } + return OBJECT_TYPE_ID_MAP[objectType]; +}; + export function fetchAllTags( callback: (json: JsonObject) => void, error: (response: Response) => void, ) { - const url = `/tagview/tags/`; + const url = `/api/v1/tag`; SupersetClient.get({ endpoint: url }) .then(({ json }) => callback(json)) .catch(response => error(response)); @@ -40,19 +60,29 @@ export function fetchTags( objectType, objectId, includeTypes = false, - }: { objectType: string; objectId: number; includeTypes: boolean }, + }: { + objectType: string; + objectId: number; + includeTypes: boolean; + }, callback: (json: JsonObject) => void, error: (response: Response) => void, ) { if (objectType === undefined || objectId === undefined) { throw new Error('Need to specify objectType and objectId'); } + if (!OBJECT_TYPES_VALUES.includes(objectType)) { + const msg = `objectType ${objectType} is invalid`; + throw new Error(msg); + } SupersetClient.get({ - endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/`, + endpoint: `/api/v1/${objectType}/${objectId}`, }) .then(({ json }) => callback( - json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), + json['result']['tags'].filter( + (tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes, + ), ), ) .catch(response => error(response)); @@ -81,13 +111,20 @@ export function deleteTaggedObjects( if (objectType === undefined || objectId === undefined) { throw new Error('Need to specify objectType and objectId'); } + if (!OBJECT_TYPES_VALUES.includes(objectType)) { + const msg = `objectType ${objectType} is invalid`; + throw new Error(msg); + } SupersetClient.delete({ - endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/`, - body: JSON.stringify([tag.name]), - parseMethod: 'text', + endpoint: `/api/v1/tag/${map_object_type_to_id(objectType)}/${objectId}/`, + body: JSON.stringify({ tag: tag.name }), + parseMethod: 'json', + headers: { 'Content-Type': 'application/json' }, }) - .then(({ text }) => - text ? callback(text) : callback('Successfully Deleted Tagged Objects'), + .then(({ json }) => + json + ? callback(JSON.stringify(json)) + : callback('Successfully Deleted Tagged Objects'), ) .catch(response => { const err_str = response.message; @@ -102,12 +139,15 @@ export function deleteTags( ) { const tags_str = JSON.stringify(tags.map(tag => tag.name) as string[]); SupersetClient.delete({ - endpoint: `/tagview/tags`, + endpoint: `/api/v1/tag/`, body: tags_str, - parseMethod: 'text', + parseMethod: 'json', + headers: { 'Content-Type': 'application/json' }, }) - .then(({ text }) => - text ? callback(text) : callback('Successfully Deleted Tag'), + .then(({ json }) => + json.message + ? callback(json.message) + : callback('Successfully Deleted Tag'), ) .catch(response => { const err_str = response.message; @@ -120,7 +160,11 @@ export function addTag( objectType, objectId, includeTypes = false, - }: { objectType: string; objectId: number; includeTypes: boolean }, + }: { + objectType: string; + objectId: number; + includeTypes: boolean; + }, tag: string, callback: (text: string) => void, error: (response: Response) => void, @@ -131,12 +175,14 @@ export function addTag( if (tag.indexOf(':') !== -1 && !includeTypes) { return; } + const objectTypeId = map_object_type_to_id(objectType); SupersetClient.post({ - endpoint: `/taggedobjectview/tags/${objectType}/${objectId}/`, - body: JSON.stringify([tag]), - parseMethod: 'text', + endpoint: `/api/v1/tag/${objectTypeId}/${objectId}/`, + body: JSON.stringify({ tags: [tag] }), + parseMethod: 'json', + headers: { 'Content-Type': 'application/json' }, }) - .then(({ text }) => callback(text)) + .then(({ json }) => callback(JSON.stringify(json))) .catch(response => error(response)); } @@ -145,11 +191,11 @@ export function fetchObjects( callback: (json: JsonObject) => void, error: (response: Response) => void, ) { - let url = `/taggedobjectview/tagged_objects/?tags=${tags}`; + let url = `/api/v1/tag/get_objects/?tags=${tags}`; if (types) { url += `&types=${types}`; } SupersetClient.get({ endpoint: url }) - .then(({ json }) => callback(json)) + .then(({ json }) => callback(json['result'])) .catch(response => error(response)); } diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx index 2950a544eb93f..6c85c676eb9dc 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx @@ -16,15 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useState } from 'react'; -import { logging, styled, t } from '@superset-ui/core'; -import Tag from 'src/types/TagType'; +import React, { useMemo } from 'react'; +import { ensureIsArray, styled, t } from '@superset-ui/core'; import { StringParam, useQueryParam } from 'use-query-params'; import withToasts from 'src/components/MessageToasts/withToasts'; -import SelectControl from 'src/explore/components/controls/SelectControl'; -import { fetchSuggestions } from 'src/tags'; -import { addDangerToast } from 'src/components/MessageToasts/actions'; import AllEntitiesTable from './AllEntitiesTable'; +import { AsyncSelect } from 'src/components'; +import { SelectValue } from 'antd/lib/select'; +import { loadTags } from 'src/components/Tags/utils'; +import { getValue } from 'src/components/Select/utils'; const AllEntitiesContainer = styled.div` ${({ theme }) => ` @@ -54,28 +54,20 @@ const AllEntitiesNav = styled.div` `; function AllEntities() { - const [tagSuggestions, setTagSuggestions] = useState(); const [tagsQuery, setTagsQuery] = useQueryParam('tags', StringParam); - useEffect(() => { - fetchSuggestions( - { includeTypes: false }, - (suggestions: Tag[]) => { - const tagSuggestions = [...suggestions.map(tag => tag.name)]; - setTagSuggestions(tagSuggestions); - }, - (error: Response) => { - logging.log(error.json()); - addDangerToast(`Error Fetching Suggestions`); - }, - ); - }, [tagsQuery]); - - const onTagSearchChange = (tags: Tag[]) => { + const onTagSearchChange = (value: SelectValue) => { + const tags = ensureIsArray(value).map(tag => getValue(tag)); const tagSearch = tags.join(','); setTagsQuery(tagSearch); }; + const tagsValue = useMemo(() => { + return tagsQuery + ? tagsQuery.split(',').map(tag => ({ value: tag, label: tag })) + : []; + }, [tagsQuery]); + return ( @@ -83,12 +75,13 @@ function AllEntities() {
{t('search by tags')}
-
diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx index 391e910de7e85..648644ba1570b 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntitiesTable.tsx @@ -72,14 +72,15 @@ export default function AllEntitiesTable({ { tags: search, types: null }, (data: TaggedObject[]) => { const objects = { dashboard: [], chart: [], query: [] }; - data.forEach(object => { - objects[object.type].push(object); + data.forEach(function (object) { + const object_type = object.type; + objects[object_type].push(object); }); setObjects(objects); }, (error: Response) => { addDangerToast('Error Fetching Tagged Objects'); - logging.log(error.json()); + logging.log(error.text); }, ); }, [search]); diff --git a/superset-frontend/src/views/CRUD/tags/TagList.tsx b/superset-frontend/src/views/CRUD/tags/TagList.tsx index 4561c965f4d90..92028168f4558 100644 --- a/superset-frontend/src/views/CRUD/tags/TagList.tsx +++ b/superset-frontend/src/views/CRUD/tags/TagList.tsx @@ -204,7 +204,7 @@ function TagList(props: TagListProps) { Header: t('Search'), id: 'name', input: 'search', - operator: FilterOperator.titleOrSlug, + operator: FilterOperator.contains, }, ] as Filters; return filters_list; diff --git a/superset/constants.py b/superset/constants.py index 10de5c52f04a7..1a38bf51a6c60 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -141,6 +141,10 @@ class RouteMethod: # pylint: disable=too-few-public-methods "samples": "read", "delete_ssh_tunnel": "write", "stop_query": "read", + "get_objects": "read", + "get_all_objects": "read", + "add_tagged_objects": "write", + "delete_tagged_object": "write", } EXTRA_FORM_DATA_APPEND_KEYS = { diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index a8b186f91beb6..f930da4290237 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -224,8 +224,9 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "published", "roles", "slug", + "tags", ) - if not is_feature_enabled("TAGGING_SYSTEM") + if is_feature_enabled("TAGGING_SYSTEM") else ( "created_by", "changed_by", @@ -235,7 +236,6 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]: "published", "roles", "slug", - "tags", ) ) search_filters = { diff --git a/superset/tags/api.py b/superset/tags/api.py index 6b2a239c15512..4036fac2811e1 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -19,27 +19,47 @@ from flask import request, Response from flask_appbuilder.api import expose, protect, safe from flask_appbuilder.models.sqla.interface import SQLAInterface -from marshmallow import ValidationError from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.extensions import event_logger from superset.tags.commands.create import CreateCustomTagCommand -from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError +from superset.tags.commands.delete import DeleteTaggedObjectCommand, DeleteTagsCommand +from superset.tags.commands.exceptions import ( + TagCreateFailedError, + TagDeleteFailedError, + TaggedObjectDeleteFailedError, + TaggedObjectNotFoundError, + TagInvalidError, + TagNotFoundError, +) +from superset.tags.dao import TagDAO from superset.tags.models import ObjectTypes, Tag from superset.tags.schemas import ( openapi_spec_methods_override, + TaggedObjectEntityResponseSchema, TagGetResponseSchema, TagPostSchema, ) -from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics +from superset.views.base_api import ( + BaseSupersetModelRestApi, + RelatedFieldFilter, + statsd_metrics, +) +from superset.views.filters import BaseFilterRelatedUsers, FilterRelatedOwners logger = logging.getLogger(__name__) class TagRestApi(BaseSupersetModelRestApi): datamodel = SQLAInterface(Tag) - - include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET + include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | { + RouteMethod.RELATED, + "bulk_delete", + "get_objects", + "get_all_objects", + "add_tagged_objects", + "delete_tagged_object", + } resource_name = "tag" allow_browser_login = True @@ -72,8 +92,18 @@ class TagRestApi(BaseSupersetModelRestApi): "created_by", ] + base_related_field_filters = { + "created_by": [["id", BaseFilterRelatedUsers, lambda: []]], + } + + related_field_filters = { + "created_by": RelatedFieldFilter("first_name", FilterRelatedOwners), + } + allowed_rel_fields = {"created_by"} + add_model_schema = TagPostSchema() tag_get_response_schema = TagGetResponseSchema() + object_entity_response_schema = TaggedObjectEntityResponseSchema() openapi_spec_tag = "Tags" """ Override the name set for this collection of endpoints """ @@ -94,15 +124,15 @@ def __repr__(self) -> str: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.post", + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.add_tagged_objects", log_to_statsd=False, ) - def add_new_tags(self, object_type: ObjectTypes, object_id: int) -> Response: - """Adds new tags to an object. + def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Response: + """Adds tags to an object. --- post: description: >- - Add new tags to an object.. + Add tags to an object.. requestBody: description: Tag schema required: true @@ -143,13 +173,195 @@ def add_new_tags(self, object_type: ObjectTypes, object_id: int) -> Response: $ref: '#/components/responses/500' """ try: - item = self.add_model_schema.load(request.json) - # This validates custom Schema with custom validations - except ValidationError as error: - return self.response_400(message=error.messages) + tags = request.json["tags"] + # This validates custom Schema with custom validations + CreateCustomTagCommand(object_type, object_id, tags).run() + return self.response(201) + except KeyError: + return self.response( + 400, message="Missing required 'tags' field in request body" + ) + except TagInvalidError: + return self.response(422, message="Invalid tag") + except TagCreateFailedError as ex: + logger.error( + "Error creating model %s: %s", + self.__class__.__name__, + str(ex), + exc_info=True, + ) + return self.response_422(message=str(ex)) + + @expose("///", methods=["DELETE"]) + @protect() + @safe + @statsd_metrics + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.delete_tagged_object", + log_to_statsd=True, + ) + def delete_tagged_object( + self, object_type: ObjectTypes, object_id: int + ) -> Response: + """Deletes a Tagged Object + --- + delete: + description: >- + Deletes a Tagged Object. + requestBody: + description: Tag name + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/{{self.__class__.__name__}}.delete' + parameters: + - in: path + schema: + type: integer + name: object_type + - in: path + schema: + type: integer + name: object_id + responses: + 200: + description: Chart delete + content: + application/json: + schema: + type: object + properties: + message: + type: string + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 404: + $ref: '#/components/responses/404' + 422: + $ref: '#/components/responses/422' + 500: + $ref: '#/components/responses/500' + """ + try: + tag = request.json["tag"] + DeleteTaggedObjectCommand(object_type, object_id, tag).run() + return self.response(200, message="OK") + except TagInvalidError: + return self.response_422() + except TagNotFoundError: + return self.response_404() + except TaggedObjectNotFoundError: + return self.response_404() + except TaggedObjectDeleteFailedError as ex: + logger.error( + "Error deleting tagged object %s: %s", + self.__class__.__name__, + str(ex), + exc_info=True, + ) + return self.response_422(message=str(ex)) + + @expose("/", methods=["DELETE"]) + @protect() + @safe + @statsd_metrics + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.bulk_delete", + log_to_statsd=False, + ) + def bulk_delete(self) -> Response: + """Delete Tags + --- + delete: + description: >- + Deletes multiple Tags. + requestBody: + description: List of Tag names + required: true + name: tags + responses: + 200: + description: Deletes multiple Tags + content: + application/json: + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 404: + $ref: '#/components/responses/404' + 422: + $ref: '#/components/responses/422' + 500: + $ref: '#/components/responses/500' + """ + tags = request.json + try: + DeleteTagsCommand(tags).run() + return self.response(200, message=f"Deleted {len(tags)} tags") + except TagNotFoundError: + return self.response_404() + except TagInvalidError as ex: + return self.response(422, message=f"Invalid tag parameters: {tags}. {ex}") + except TagDeleteFailedError as ex: + return self.response_422(message=str(ex)) + + @expose("/get_objects/", methods=["GET"]) + @protect() + @safe + @statsd_metrics + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get_objects", + log_to_statsd=False, + ) + def get_objects(self) -> Response: + """Gets all objects associated with a Tag + --- + get: + description: >- + Gets all objects associated with a Tag. + parameters: + - in: path + schema: + type: integer + name: tag_id + responses: + 200: + description: Objects fetched + content: + application/json: + schema: + type: object + properties: + id: + type: number + result: + $ref: '#/components/schemas/{{self.__class__.__name__}}.post' + 302: + description: Redirects to the current digest + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + """ + tags = [tag for tag in request.args.get("tags", "").split(",") if tag] + # filter types + types = [type_ for type_ in request.args.get("types", "").split(",") if type_] + try: - new_model = CreateCustomTagCommand(object_type, object_id, item).run() - return self.response(201, id=new_model.id, result=item) + tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types) + result = [ + self.object_entity_response_schema.dump(tagged_object) + for tagged_object in tagged_objects + ] + return self.response(200, result=result) except TagInvalidError as ex: return self.response_422(message=ex.normalized_messages()) except TagCreateFailedError as ex: diff --git a/superset/tags/commands/create.py b/superset/tags/commands/create.py index 06d6026eac401..e9afe4a38d4a9 100644 --- a/superset/tags/commands/create.py +++ b/superset/tags/commands/create.py @@ -15,13 +15,12 @@ # specific language governing permissions and limitations # under the License. import logging -from typing import Any, Dict - -from flask_appbuilder.models.sqla import Model +from typing import List from superset.commands.base import BaseCommand, CreateMixin from superset.dao.exceptions import DAOCreateFailedError from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError +from superset.tags.commands.utils import to_object_type from superset.tags.dao import TagDAO from superset.tags.models import ObjectTypes @@ -29,28 +28,37 @@ class CreateCustomTagCommand(CreateMixin, BaseCommand): - def __init__(self, object_type: ObjectTypes, object_id: int, data: Dict[str, Any]): + def __init__(self, object_type: ObjectTypes, object_id: int, tags: List[str]): self._object_type = object_type self._object_id = object_id - self._properties = data.copy() + self._tags = tags - def run(self) -> Model: + def run(self) -> None: self.validate() try: - tag = TagDAO.create_custom_tagged_objects( - self._object_type, self._object_id, self._properties + object_type = to_object_type(self._object_type) + if object_type is None: + raise TagCreateFailedError(f"invalid object type {self._object_type}") + TagDAO.create_custom_tagged_objects( + object_type=object_type, + object_id=self._object_id, + tag_names=self._tags, ) except DAOCreateFailedError as ex: logger.exception(ex.exception) raise TagCreateFailedError() from ex - return tag def validate(self) -> None: exceptions = [] - # Validate object_id if self._object_id == 0: exceptions.append(TagCreateFailedError()) + # Validate object type + object_type = to_object_type(self._object_type) + if not object_type: + exceptions.append( + TagCreateFailedError(f"invalid object type {self._object_type}") + ) if exceptions: exception = TagInvalidError() exception.add_list(exceptions) diff --git a/superset/tags/commands/delete.py b/superset/tags/commands/delete.py new file mode 100644 index 0000000000000..2113c576f6bec --- /dev/null +++ b/superset/tags/commands/delete.py @@ -0,0 +1,114 @@ +# 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 logging +from typing import List + + +from superset.commands.base import BaseCommand +from superset.dao.exceptions import DAODeleteFailedError +from superset.tags.commands.exceptions import ( + TagDeleteFailedError, + TagInvalidError, + TagNotFoundError, + TaggedObjectDeleteFailedError, + TaggedObjectNotFoundError +) +from superset.tags.commands.utils import to_object_type +from superset.tags.dao import TagDAO +from superset.tags.models import ObjectTypes +from superset.views.base import DeleteMixin + +logger = logging.getLogger(__name__) + + +class DeleteTaggedObjectCommand(DeleteMixin, BaseCommand): + def __init__(self, object_type: ObjectTypes, object_id: int, tag: str): + self._object_type = object_type + self._object_id = object_id + self._tag = tag + + def run(self) -> None: + self.validate() + try: + object_type = to_object_type(self._object_type) + if object_type is None: + raise TaggedObjectDeleteFailedError( + f'invalid object type {self._object_type}') + TagDAO.delete_tagged_object( + object_type, self._object_id, self._tag + ) + except DAODeleteFailedError as ex: + logger.exception(ex.exception) + raise TaggedObjectDeleteFailedError() from ex + + def validate(self) -> None: + exceptions = [] + # Validate required arguments provided + if not (self._object_id and self._object_type): + exceptions.append(TaggedObjectDeleteFailedError()) + # Validate tagged object exists + tag = TagDAO.find_by_name(self._tag) + if not tag: + exceptions.append(TaggedObjectDeleteFailedError( + f'could not find tag: {self._tag}' + )) + else: + # Validate object type + object_type = to_object_type(self._object_type) + if object_type is None: + exceptions.append(TaggedObjectDeleteFailedError( + f'invalid object type {self._object_type}')) + else: + tagged_object = TagDAO.find_tagged_object( + object_type=object_type, + object_id=self._object_id, + tag_id=tag.id + ) + if tagged_object is None: + exceptions.append(TaggedObjectNotFoundError( + object_id=self._object_id, + object_type=object_type.name, + tag_name=self._tag + )) + if exceptions: + exception = TagInvalidError() + exception.add_list(exceptions) + raise exception + + +class DeleteTagsCommand(DeleteMixin, BaseCommand): + def __init__(self, tags: List[str]): + self._tags = tags + + def run(self) -> None: + self.validate() + try: + TagDAO.delete_tags(self._tags) + except DAODeleteFailedError as ex: + logger.exception(ex.exception) + raise TagDeleteFailedError() from ex + + def validate(self) -> None: + exceptions = [] + # Validate tag exists + for tag in self._tags: + if not TagDAO.find_by_name(tag): + exceptions.append(TagNotFoundError(tag)) + if exceptions: + exception = TagInvalidError() + exception.add_list(exceptions) + raise exception diff --git a/superset/tags/commands/exceptions.py b/superset/tags/commands/exceptions.py index 5e218fed73f1d..9847c949bf7ec 100644 --- a/superset/tags/commands/exceptions.py +++ b/superset/tags/commands/exceptions.py @@ -14,9 +14,16 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +from typing import Optional + from flask_babel import lazy_gettext as _ -from superset.commands.exceptions import CommandInvalidError, CreateFailedError +from superset.commands.exceptions import ( + CommandException, + CommandInvalidError, + CreateFailedError, + DeleteFailedError, +) class TagInvalidError(CommandInvalidError): @@ -25,3 +32,34 @@ class TagInvalidError(CommandInvalidError): class TagCreateFailedError(CreateFailedError): message = _("Tag could not be created.") + + +class TagDeleteFailedError(DeleteFailedError): + message = _("Tag could not be deleted.") + + +class TaggedObjectDeleteFailedError(DeleteFailedError): + message = _("Tagged Object could not be deleted.") + + +class TagNotFoundError(CommandException): + def __init__(self, tag_name: Optional[str] = None) -> None: + message = "Tag not found." + if tag_name: + message = f"Tag with name {tag_name} not found." + super().__init__(message) + + +class TaggedObjectNotFoundError(CommandException): + def __init__( + self, + object_id: Optional[int] = None, + object_type: Optional[str] = None, + tag_name: Optional[str] = None, + ) -> None: + message = "Tagged Object not found." + if object_id and object_type and tag_name: + message = f'Tagged object with object_id: {object_id} \ + object_type: {object_type} \ + and tag name: "{tag_name}" could not be found' + super().__init__(message) diff --git a/superset/tags/commands/utils.py b/superset/tags/commands/utils.py new file mode 100644 index 0000000000000..89035503a391a --- /dev/null +++ b/superset/tags/commands/utils.py @@ -0,0 +1,28 @@ +# 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. + +from typing import Optional, Union +from superset.tags.models import ObjectTypes + + +def to_object_type(object_type: Union[ObjectTypes, int, str]) -> Optional[ObjectTypes]: + if isinstance(object_type, ObjectTypes): + return object_type + for type_ in ObjectTypes: + if (object_type in [type_.value, type_.name]): + return type_ + return None diff --git a/superset/tags/dao.py b/superset/tags/dao.py index a2f4eb6d7f018..3d68098a6a1c7 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -15,12 +15,16 @@ # specific language governing permissions and limitations # under the License. import logging -from typing import Any, Dict +from operator import and_ +from typing import Any, Dict, List, Optional from superset.dao.base import BaseDAO -from superset.dao.exceptions import DAOCreateFailedError +from superset.dao.exceptions import DAOCreateFailedError, DAODeleteFailedError from superset.extensions import db -from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes +from superset.models.dashboard import Dashboard +from superset.models.slice import Slice +from superset.models.sql_lab import SavedQuery +from superset.tags.models import get_tag, ObjectTypes, Tag, TaggedObject, TagTypes logger = logging.getLogger(__name__) @@ -28,6 +32,7 @@ class TagDAO(BaseDAO): model_cls = Tag # base_filter = TagAccessFilter + @staticmethod def validate_tag_name(tag_name: str) -> bool: if ":" in tag_name: @@ -36,10 +41,8 @@ def validate_tag_name(tag_name: str) -> bool: @staticmethod def create_custom_tagged_objects( - object_type: ObjectTypes, object_id: int, properties: Dict[str, Any] + object_type: ObjectTypes, object_id: int, tag_names: List[str] ) -> None: - tag_names = properties["tags"] - tagged_objects = [] for name in tag_names: if not TagDAO.validate_tag_name(name): @@ -57,8 +60,193 @@ def create_custom_tagged_objects( db.session.commit() @staticmethod - def get_by_name(name: str, type_: TagTypes) -> Tag: - tag = db.session.query(Tag).filter(Tag.name == name, Tag.type == type_).first() + def delete_tagged_object( + object_type: ObjectTypes, object_id: int, tag_name: str + ) -> None: + """ + deletes a tagged object by the object_id, object_type, and tag_name + """ + tag = TagDAO.find_by_name(tag_name.strip()) + if not tag: + raise DAODeleteFailedError( + message=f"Tag with name {tag_name} does not exist." + ) + + tagged_object = db.session.query(TaggedObject).filter( + TaggedObject.tag_id == tag.id, + TaggedObject.object_type == object_type, + TaggedObject.object_id == object_id, + ) + if not tagged_object: + raise DAODeleteFailedError( + message=f'Tagged object with object_id: {object_id} \ + object_type: {object_type} \ + and tag name: "{tag_name}" could not be found' + ) + deleted = tagged_object.delete() + if not deleted: + raise DAODeleteFailedError( + message=f'Tagged object with object_id: {object_id} \ + object_type: {object_type} \ + and tag name: "{tag_name}" could not be deleted' + ) + db.session.commit() + + @staticmethod + def delete_tags(tag_names: List[str]) -> None: + """ + deletes tags from a list of tag names + """ + tags_to_delete = [] + for name in tag_names: + tag_name = name.strip() + if not TagDAO.find_by_name(tag_name): + raise DAODeleteFailedError( + message=f"Tag with name {tag_name} does not exist." + ) + tags_to_delete.append(tag_name) + db.session.query(Tag).filter(Tag.name.in_(tags_to_delete)).delete() + db.session.commit() + + @staticmethod + def get_by_name(name: str, type_: TagTypes = TagTypes.custom) -> Tag: + """ + returns a tag if one exists by that name, none otherwise. + important!: Creates a tag by that name if the tag is not found. + """ + tag = ( + db.session.query(Tag) + .filter(Tag.name == name, Tag.type == type_.name) + .first() + ) if not tag: - tag = Tag(name=name, type=type_) + tag = get_tag(name, db.session, type_) return tag + + @staticmethod + def find_by_name(name: str) -> Tag: + """ + returns a tag if one exists by that name, none otherwise. + Does NOT create a tag if the tag is not found. + """ + return db.session.query(Tag).filter(Tag.name == name).first() + + @staticmethod + def find_tagged_object( + object_type: ObjectTypes, object_id: int, tag_id: int + ) -> TaggedObject: + """ + returns a tagged object if one exists by that name, none otherwise. + """ + return ( + db.session.query(TaggedObject) + .filter( + TaggedObject.tag_id == tag_id, + TaggedObject.object_id == object_id, + TaggedObject.object_type == object_type, + ) + .first() + ) + + @staticmethod + def get_tagged_objects_for_tags( + tags: Optional[List[str]] = None, obj_types: Optional[List[str]] = None + ) -> List[Dict[str, Any]]: + """ + returns a list of tagged objects filtered by tag names and object types + if no filters applied returns all tagged objects + """ + # id = fields.Int() + # type = fields.String() + # name = fields.String() + # url = fields.String() + # changed_on = fields.DateTime() + # created_by = fields.Nested(UserSchema) + # creator = fields.String( + + # filter types + + results: List[Dict[str, Any]] = [] + + # dashboards + if not obj_types or "dashboard" in obj_types: + dashboards = ( + db.session.query(Dashboard) + .join( + TaggedObject, + and_( + TaggedObject.object_id == Dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard, + ), + ) + .join(Tag, TaggedObject.tag_id == Tag.id) + .filter(not tags or Tag.name.in_(tags)) + ) + + results.extend( + { + "id": obj.id, + "type": ObjectTypes.dashboard.name, + "name": obj.dashboard_title, + "url": obj.url, + "changed_on": obj.changed_on, + "created_by": obj.created_by_fk, + "creator": obj.creator(), + } + for obj in dashboards + ) + + # charts + if not obj_types or "chart" in obj_types: + charts = ( + db.session.query(Slice) + .join( + TaggedObject, + and_( + TaggedObject.object_id == Slice.id, + TaggedObject.object_type == ObjectTypes.chart, + ), + ) + .join(Tag, TaggedObject.tag_id == Tag.id) + .filter(not tags or Tag.name.in_(tags)) + ) + results.extend( + { + "id": obj.id, + "type": ObjectTypes.chart.name, + "name": obj.slice_name, + "url": obj.url, + "changed_on": obj.changed_on, + "created_by": obj.created_by_fk, + "creator": obj.creator(), + } + for obj in charts + ) + + # saved queries + if not obj_types or "query" in obj_types: + saved_queries = ( + db.session.query(SavedQuery) + .join( + TaggedObject, + and_( + TaggedObject.object_id == SavedQuery.id, + TaggedObject.object_type == ObjectTypes.query, + ), + ) + .join(Tag, TaggedObject.tag_id == Tag.id) + .filter(not tags or Tag.name.in_(tags)) + ) + results.extend( + { + "id": obj.id, + "type": ObjectTypes.query.name, + "name": obj.label, + "url": obj.url(), + "changed_on": obj.changed_on, + "created_by": obj.created_by_fk, + "creator": obj.creator(), + } + for obj in saved_queries + ) + return results diff --git a/superset/tags/schemas.py b/superset/tags/schemas.py index e823a9c2aa749..0496eef916afb 100644 --- a/superset/tags/schemas.py +++ b/superset/tags/schemas.py @@ -16,6 +16,8 @@ # under the License. from marshmallow import fields, Schema +from superset.dashboards.schemas import UserSchema + object_type_description = "A title for the tag." openapi_spec_methods_override = { @@ -35,6 +37,16 @@ } +class TaggedObjectEntityResponseSchema(Schema): + id = fields.Int() + type = fields.String() + name = fields.String() + url = fields.String() + changed_on = fields.DateTime() + created_by = fields.Nested(UserSchema) + creator = fields.String() + + class TagGetResponseSchema(Schema): id = fields.Int() name = fields.String() diff --git a/superset/views/all_entities.py b/superset/views/all_entities.py index f8eec6d9521c2..4031d81d2129e 100644 --- a/superset/views/all_entities.py +++ b/superset/views/all_entities.py @@ -17,30 +17,21 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging -from typing import Any, Dict, List -import simplejson as json -from flask import request, Response from flask_appbuilder import expose from flask_appbuilder.hooks import before_request from flask_appbuilder.models.sqla.interface import SQLAInterface -from flask_appbuilder.security.decorators import has_access, has_access_api +from flask_appbuilder.security.decorators import has_access from jinja2.sandbox import SandboxedEnvironment -from sqlalchemy import and_, func from werkzeug.exceptions import NotFound -from superset import db, is_feature_enabled, utils +from superset import is_feature_enabled from superset.jinja_context import ExtraCache -from superset.models.dashboard import Dashboard -from superset.models.slice import Slice -from superset.models.sql_lab import SavedQuery from superset.superset_typing import FlaskResponse -from superset.tags.commands.create import CreateCustomTagCommand -from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError -from superset.tags.models import ObjectTypes, Tag, TaggedObject +from superset.tags.models import Tag from superset.views.base import SupersetModelView -from .base import BaseSupersetView, json_success +from .base import BaseSupersetView logger = logging.getLogger(__name__) @@ -78,185 +69,3 @@ def is_enabled() -> bool: def ensure_enabled(self) -> None: if not self.is_enabled(): raise NotFound() - - @has_access_api - @expose("/tags/suggestions/", methods=["GET"]) - def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use - query = ( - db.session.query(TaggedObject) - .join(Tag) - .with_entities(TaggedObject.tag_id, Tag.name) - .group_by(TaggedObject.tag_id, Tag.name) - .order_by(func.count().desc()) - .all() - ) - tags = [{"id": id, "name": name} for id, name in query] - return json_success(json.dumps(tags)) - - @has_access_api - @expose("/tags///", methods=["GET"]) - def get( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """List all tags a given object has.""" - if object_id == 0: - return json_success(json.dumps([])) - - query = db.session.query(TaggedObject).filter( - and_( - TaggedObject.object_type == object_type, - TaggedObject.object_id == object_id, - ) - ) - tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query] - return json_success(json.dumps(tags)) - - @has_access_api - @expose("/tags///", methods=["POST"]) - def post(self, object_type: ObjectTypes, object_id: int) -> FlaskResponse: - """ - --- - post: - description: >- - Add new tags to an object.. - requestBody: - array of tag names - """ - data = dict() - data["tags"] = request.get_json(force=True) - try: - CreateCustomTagCommand(object_type, object_id, data).run() - return Response(status=201) - except TagInvalidError as ex: - logger.error( - "Invalid tag: %s", - str(ex), - ) - return Response(response="tag invalid", status=422) - except TagCreateFailedError as ex: - logger.error( - "Error creating model %s: %s", - self.__class__.__name__, - str(ex), - exc_info=True, - ) - return Response(response="Error creating tag", status=422) - - @has_access_api - @expose("/tags///", methods=["DELETE"]) - def delete( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """Remove tags from an object.""" - tag_names = request.get_json(force=True) - if not tag_names: - return Response(status=403) - - db.session.query(TaggedObject).filter( - and_( - TaggedObject.object_type == object_type, - TaggedObject.object_id == object_id, - ), - TaggedObject.tag.has(Tag.name.in_(tag_names)), - ).delete(synchronize_session=False) - db.session.commit() - - return Response(status=204) # 204 NO CONTENT - - @has_access_api - @expose("/tagged_objects/", methods=["GET", "POST"]) - def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use - tags = [ - process_template(tag) - for tag in request.args.get("tags", "").split(",") - if tag - ] - - # filter types - types = [type_ for type_ in request.args.get("types", "").split(",") if type_] - - results: List[Dict[str, Any]] = [] - - # dashboards - if not types or "dashboard" in types: - dashboards = ( - db.session.query(Dashboard) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Dashboard.id, - TaggedObject.object_type == ObjectTypes.dashboard, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(not tags or Tag.name.in_(tags)) - ) - - results.extend( - { - "id": obj.id, - "type": ObjectTypes.dashboard.name, - "name": obj.dashboard_title, - "url": obj.url, - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in dashboards - ) - - # charts - if not types or "chart" in types: - charts = ( - db.session.query(Slice) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Slice.id, - TaggedObject.object_type == ObjectTypes.chart, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(not tags or Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.chart.name, - "name": obj.slice_name, - "url": obj.url, - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in charts - ) - - # saved queries - if not types or "query" in types: - saved_queries = ( - db.session.query(SavedQuery) - .join( - TaggedObject, - and_( - TaggedObject.object_id == SavedQuery.id, - TaggedObject.object_type == ObjectTypes.query, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(not tags or Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.query.name, - "name": obj.label, - "url": obj.url(), - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in saved_queries - ) - - return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) diff --git a/superset/views/tags.py b/superset/views/tags.py index bbb338b0932a9..44027823cbce6 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -17,28 +17,19 @@ from __future__ import absolute_import, division, print_function, unicode_literals import logging -from typing import Any, Dict, List import simplejson as json -from flask import request, Response from flask_appbuilder import expose from flask_appbuilder.hooks import before_request from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_appbuilder.security.decorators import has_access, has_access_api from jinja2.sandbox import SandboxedEnvironment -from sqlalchemy import and_, func from werkzeug.exceptions import NotFound from superset import db, is_feature_enabled, utils -from superset.connectors.sqla.models import SqlaTable from superset.jinja_context import ExtraCache -from superset.models.dashboard import Dashboard -from superset.models.slice import Slice -from superset.models.sql_lab import SavedQuery from superset.superset_typing import FlaskResponse -from superset.tags.commands.create import CreateCustomTagCommand -from superset.tags.commands.exceptions import TagCreateFailedError, TagInvalidError -from superset.tags.models import ObjectTypes, Tag, TaggedObject +from superset.tags.models import Tag from superset.views.base import SupersetModelView from .base import BaseSupersetView, json_success @@ -83,13 +74,7 @@ def ensure_enabled(self) -> None: @has_access_api @expose("/tags/", methods=["GET"]) def tags(self) -> FlaskResponse: # pylint: disable=no-self-use - query = ( - db.session.query(Tag) - # .with_entities(Tag.name, Tag.id) - # .group_by(TaggedObject.tag_id, Tag.name) - # .order_by(func.count().desc()) - .all() - ) + query = db.session.query(Tag).all() results = [ { "id": obj.id, @@ -102,231 +87,3 @@ def tags(self) -> FlaskResponse: # pylint: disable=no-self-use for obj in query ] return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) - - @has_access_api - @expose("/tags/suggestions/", methods=["GET"]) - def suggestions(self) -> FlaskResponse: # pylint: disable=no-self-use - query = ( - db.session.query(TaggedObject) - .join(Tag) - .with_entities(TaggedObject.tag_id, Tag.name) - .group_by(TaggedObject.tag_id, Tag.name) - .order_by(func.count().desc()) - .all() - ) - tags = [{"id": id, "name": name} for id, name in query] - return json_success(json.dumps(tags)) - - @has_access_api - @expose("/tags///", methods=["GET"]) - def get( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """List all tags a given object has.""" - if object_id == 0: - return json_success(json.dumps([])) - - query = db.session.query(TaggedObject).filter( - and_( - TaggedObject.object_type == object_type, - TaggedObject.object_id == object_id, - ) - ) - tags = [{"id": obj.tag.id, "name": obj.tag.name} for obj in query] - return json_success(json.dumps(tags)) - - @has_access_api - @expose("/tags///", methods=["POST"]) - def post(self, object_type: ObjectTypes, object_id: int) -> FlaskResponse: - """ - --- - post: - description: >- - Add new tags to an object.. - requestBody: - array of tag names - """ - data = dict() - data["tags"] = request.get_json(force=True) - try: - CreateCustomTagCommand(object_type, object_id, data).run() - return Response(status=201) - except TagInvalidError as ex: - logger.error( - "Invalid tag: %s", - str(ex), - ) - return Response(response="tag invalid", status=422) - except TagCreateFailedError as ex: - logger.error( - "Error creating model %s: %s", - self.__class__.__name__, - str(ex), - exc_info=True, - ) - return Response(response="Error creating model", status=422) - - @has_access_api - @expose("/tags///", methods=["DELETE"]) - def delete_tagged_objects( # pylint: disable=no-self-use - self, object_type: ObjectTypes, object_id: int - ) -> FlaskResponse: - """Remove tags from an object.""" - tag_names = request.get_json(force=True) - if not tag_names: - return Response(status=403) - - db.session.query(TaggedObject).filter( - and_( - TaggedObject.object_type == object_type, - TaggedObject.object_id == object_id, - ), - TaggedObject.tag.has(Tag.name.in_(tag_names)), - ).delete(synchronize_session=False) - db.session.commit() - - return Response(status=204) # 204 NO CONTENT - - @has_access_api - @expose("/tags", methods=["DELETE"]) - def delete_tags(self) -> FlaskResponse: # pylint: disable=no-self-use - """Remove tags, and all tagged objects with that tag""" - tag_names = request.get_json(force=True) - if not tag_names: - return Response(status=403) - db.session() - db.session.query(TaggedObject).filter( - TaggedObject.tag.has(Tag.name.in_(tag_names)), - ).delete(synchronize_session=False) - db.session.query(Tag).filter( - Tag.name.in_(tag_names), - ).delete(synchronize_session=False) - db.session.commit() - - return Response(status=204) # 204 NO CONTENT - - @has_access_api - @expose("/tagged_objects/", methods=["GET", "POST"]) - def tagged_objects(self) -> FlaskResponse: # pylint: disable=no-self-use - tags = [ - process_template(tag) - for tag in request.args.get("tags", "").split(",") - if tag - ] - if not tags: - return json_success(json.dumps([])) - - # filter types - types = [type_ for type_ in request.args.get("types", "").split(",") if type_] - - results: List[Dict[str, Any]] = [] - - # dashboards - if not types or "dashboard" in types: - dashboards = ( - db.session.query(Dashboard) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Dashboard.id, - TaggedObject.object_type == ObjectTypes.dashboard, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.dashboard.name, - "name": obj.dashboard_title, - "url": obj.url, - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in dashboards - ) - - # charts - if not types or "chart" in types: - charts = ( - db.session.query(Slice) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Slice.id, - TaggedObject.object_type == ObjectTypes.chart, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.chart.name, - "name": obj.slice_name, - "url": obj.url, - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in charts - ) - - # saved queries - if not types or "query" in types: - saved_queries = ( - db.session.query(SavedQuery) - .join( - TaggedObject, - and_( - TaggedObject.object_id == SavedQuery.id, - TaggedObject.object_type == ObjectTypes.query, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.query.name, - "name": obj.label, - "url": obj.url(), - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in saved_queries - ) - - # datasets - if not types or "dataset" in types: - datasets = ( - db.session.query(SqlaTable) - .join( - TaggedObject, - and_( - TaggedObject.object_id == SqlaTable.id, - TaggedObject.object_type == ObjectTypes.dataset, - ), - ) - .join(Tag, TaggedObject.tag_id == Tag.id) - .filter(Tag.name.in_(tags)) - ) - results.extend( - { - "id": obj.id, - "type": ObjectTypes.dataset.name, - "name": obj.table_name, - "url": obj.sql_url(), - "changed_on": obj.changed_on, - "created_by": obj.created_by_fk, - "creator": obj.creator(), - } - for obj in datasets - ) - - return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser)) diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 2afce53ef2f1c..088346cdc4f85 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -24,14 +24,21 @@ import pytest import prison from sqlalchemy.sql import func +from superset.models.dashboard import Dashboard +from superset.models.slice import Slice +from superset.models.sql_lab import SavedQuery import tests.integration_tests.test_app from superset import db, security_manager from superset.common.db_query_status import QueryStatus from superset.models.core import Database from superset.utils.database import get_example_database, get_main_database -from superset.tags.models import Tag, TagTypes - +from superset.tags.models import ObjectTypes, Tag, TagTypes, TaggedObject +from tests.integration_tests.fixtures.world_bank_dashboard import ( + load_world_bank_dashboard_with_slices, + load_world_bank_data, +) +from tests.integration_tests.fixtures.tags import with_tagging_system_feature from tests.integration_tests.base_tests import SupersetTestCase TAGS_FIXTURE_COUNT = 10 @@ -63,6 +70,20 @@ def insert_tag( db.session.commit() return tag + def insert_tagged_object( + self, + tag_id: int, + object_id: int, + object_type: ObjectTypes, + ) -> TaggedObject: + tag = db.session.query(Tag).filter(Tag.id == tag_id).first() + tagged_object = TaggedObject( + tag=tag, object_id=object_id, object_type=object_type.name + ) + db.session.add(tagged_object) + db.session.commit() + return tagged_object + @pytest.fixture() def create_tags(self): with self.create_app().app_context(): @@ -77,7 +98,6 @@ def create_tags(self): tag_type="custom", ) ) - yield tags # rollback changes @@ -139,3 +159,192 @@ def test_get_list_tag(self): assert data["count"] == TAGS_FIXTURE_COUNT # check expected columns assert data["list_columns"] == TAGS_LIST_COLUMNS + + # test add tagged objects + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + def test_add_tagged_objects(self): + self.login(username="admin") + dashboard_id = 1 + dashboard_type = ObjectTypes.dashboard.value + uri = f"api/v1/tag/{dashboard_type}/{dashboard_id}/" + example_tag_names = ["example_tag_1", "example_tag_2"] + data = {"tags": example_tag_names} + rv = self.client.post(uri, json=data, follow_redirects=True) + # successful request + self.assertEqual(rv.status_code, 201) + # check that tags were created in database + tags = db.session.query(Tag).filter(Tag.name.in_(example_tag_names)) + self.assertEqual(tags.count(), 2) + # check that tagged objects were created + tag_ids = [tags[0].id, tags[1].id] + tagged_objects = db.session.query(TaggedObject).filter( + TaggedObject.tag_id.in_(tag_ids) + ) + self.assertEqual(tagged_objects.count(), 2) + self.assertEqual(tagged_objects.first().object_id, 1) + self.assertEqual(tagged_objects.first().object_type, ObjectTypes.dashboard) + self.assertEqual(tagged_objects[1].object_id, 1) + self.assertEqual(tagged_objects[1].object_type, ObjectTypes.dashboard) + # clean up tags and tagged objects + tagged_objects.delete() + tags.delete() + db.session.commit() + + # test delete tagged object + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("create_tags") + def test_delete_tagged_objects(self): + self.login(username="admin") + dashboard_id = 1 + dashboard_type = ObjectTypes.dashboard + tag_names = ["example_tag_1", "example_tag_2"] + tags = db.session.query(Tag).filter(Tag.name.in_(tag_names)) + assert tags.count() == 2 + self.insert_tagged_object( + tag_id=tags.first().id, object_id=dashboard_id, object_type=dashboard_type + ) + self.insert_tagged_object( + tag_id=tags[1].id, object_id=dashboard_id, object_type=dashboard_type + ) + tagged_object = ( + db.session.query(TaggedObject) + .filter( + TaggedObject.tag_id == tags.first().id, + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == dashboard_type.name, + ) + .first() + ) + other_tagged_object = ( + db.session.query(TaggedObject) + .filter( + TaggedObject.tag_id == tags[1].id, + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == dashboard_type.name, + ) + .first() + ) + assert tagged_object is not None + uri = f"api/v1/tag/{dashboard_type.value}/{dashboard_id}/" + data = {"tag": tags.first().name} + rv = self.client.delete(uri, json=data, follow_redirects=True) + # successful request + self.assertEqual(rv.status_code, 200) + # ensure that tagged object no longer exists + tagged_object = ( + db.session.query(TaggedObject) + .filter( + TaggedObject.tag_id == tags.first().id, + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == dashboard_type.name, + ) + .first() + ) + assert not tagged_object + # ensure the other tagged objects still exist + other_tagged_object = ( + db.session.query(TaggedObject) + .filter( + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == dashboard_type.name, + TaggedObject.tag_id == tags[1].id, + ) + .first() + ) + assert other_tagged_object is not None + # clean up tagged object + db.session.delete(other_tagged_object) + + # test get objects + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("create_tags") + def test_get_objects_by_tag(self): + self.login(username="admin") + dashboard_id = 1 + dashboard_type = ObjectTypes.dashboard + tag_names = ["example_tag_1", "example_tag_2"] + tags = db.session.query(Tag).filter(Tag.name.in_(tag_names)) + for tag in tags: + self.insert_tagged_object( + tag_id=tag.id, object_id=dashboard_id, object_type=dashboard_type + ) + tagged_objects = db.session.query(TaggedObject).filter( + TaggedObject.tag_id.in_([tag.id for tag in tags]), + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == dashboard_type.name, + ) + self.assertEqual(tagged_objects.count(), 2) + uri = f'api/v1/tag/get_objects/?tags={",".join(tag_names)}' + rv = self.client.get(uri) + # successful request + self.assertEqual(rv.status_code, 200) + fetched_objects = rv.json["result"] + self.assertEqual(len(fetched_objects), 1) + self.assertEqual(fetched_objects[0]["id"], 1) + # clean up tagged object + tagged_objects.delete() + + # test get all objects + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("create_tags") + def test_get_all_objects(self): + self.login(username="admin") + dashboard_id = 1 + dashboard_type = ObjectTypes.dashboard + tag_names = ["example_tag_1", "example_tag_2"] + tags = db.session.query(Tag).filter(Tag.name.in_(tag_names)) + for tag in tags: + self.insert_tagged_object( + tag_id=tag.id, object_id=dashboard_id, object_type=dashboard_type + ) + num_objects = ( + db.session.query(Dashboard).count() + + db.session.query(Slice).count() + + db.session.query(SavedQuery).count() + ) + + tagged_objects = db.session.query(TaggedObject).filter( + TaggedObject.tag_id.in_([tag.id for tag in tags]), + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == dashboard_type.name, + ) + self.assertEqual(tagged_objects.count(), 2) + uri = "api/v1/tag/get_objects/" + rv = self.client.get(uri) + # successful request + self.assertEqual(rv.status_code, 200) + fetched_objects = rv.json["result"] + # check that all tagged objects are fetched + # when tagging system is false, there will only be the one dashboard + self.assertEqual(len(fetched_objects), 1) + self.assertEqual(fetched_objects[0]["id"], 1) + # clean up tagged object + tagged_objects.delete() + + # test delete tags + @pytest.mark.usefixtures("create_tags") + def test_delete_tags(self): + self.login(username="admin") + # check that tags exist in the database + example_tag_names = ["example_tag_1", "example_tag_2", "example_tag_3"] + tags = db.session.query(Tag).filter(Tag.name.in_(example_tag_names)) + self.assertEqual(tags.count(), 3) + # delete the first tag + uri = "api/v1/tag/" + data = example_tag_names[:1] + rv = self.client.delete(uri, json=data, follow_redirects=True) + # successful request + self.assertEqual(rv.status_code, 200) + # check that tag does not exist in the database + tag = db.session.query(Tag).filter(Tag.name == example_tag_names[0]).first() + assert tag is None + tags = db.session.query(Tag).filter(Tag.name.in_(example_tag_names)) + self.assertEqual(tags.count(), 2) + # delete multiple tags + data = example_tag_names[1:] + rv = self.client.delete(uri, json=data, follow_redirects=True) + # successful request + self.assertEqual(rv.status_code, 200) + # check that tags are all gone + tags = db.session.query(Tag).filter(Tag.name.in_(example_tag_names)) + self.assertEqual(tags.count(), 0) diff --git a/tests/integration_tests/tags/commands_tests.py b/tests/integration_tests/tags/commands_tests.py new file mode 100644 index 0000000000000..76c4f75694b86 --- /dev/null +++ b/tests/integration_tests/tags/commands_tests.py @@ -0,0 +1,158 @@ +# 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 itertools +import json +from unittest.mock import MagicMock, patch + +import pytest +import yaml +from superset.tags.commands.create import CreateCustomTagCommand +from superset.tags.commands.delete import DeleteTaggedObjectCommand, DeleteTagsCommand +from superset.tags.models import ObjectTypes, Tag, TagTypes, TaggedObject +from werkzeug.utils import secure_filename + +from superset import db, security_manager +from superset.commands.exceptions import CommandInvalidError +from superset.commands.importers.exceptions import IncorrectVersionError +from superset.connectors.sqla.models import SqlaTable +from superset.dashboards.commands.exceptions import DashboardNotFoundError +from superset.dashboards.commands.export import ( + append_charts, + ExportDashboardsCommand, + get_default_position, +) +from superset.dashboards.commands.importers import v0, v1 +from superset.models.core import Database +from superset.models.dashboard import Dashboard +from superset.models.slice import Slice +from tests.integration_tests.base_tests import SupersetTestCase +from tests.integration_tests.fixtures.importexport import ( + chart_config, + dashboard_config, + dashboard_export, + dashboard_metadata_config, + database_config, + dataset_config, + dataset_metadata_config, +) +from tests.integration_tests.fixtures.world_bank_dashboard import ( + load_world_bank_dashboard_with_slices, + load_world_bank_data, +) +from tests.integration_tests.fixtures.tags import with_tagging_system_feature + +# test create command +class TestCreateCustomTagCommand(SupersetTestCase): + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + def test_create_custom_tag_command(self): + example_dashboard = ( + db.session.query(Dashboard).filter_by(slug="world_health").one() + ) + example_tags = ['create custom tag example 1', 'create custom tag example 2'] + command = CreateCustomTagCommand( + ObjectTypes.dashboard.value, + example_dashboard.id, + example_tags + ) + command.run() + + created_tags = db.session.query(Tag).join(TaggedObject).filter( + TaggedObject.object_id == example_dashboard.id, + Tag.type == TagTypes.custom + ).all() + assert example_tags == [tag.name for tag in created_tags] + + # cleanup + tags = db.session.query(Tag).filter(Tag.name.in_(example_tags)) + db.session.query(TaggedObject).filter( + TaggedObject.tag_id.in_([tag.id for tag in tags]) + ).delete() + tags.delete() + db.session.commit() + +# test delete tags command +class TestDeleteTagsCommand(SupersetTestCase): + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + def test_delete_tags_command(self): + example_dashboard = ( + db.session.query(Dashboard).filter_by(slug="world_health").one() + ) + example_tags = ['create custom tag example 1', 'create custom tag example 2'] + command = CreateCustomTagCommand( + ObjectTypes.dashboard.value, + example_dashboard.id, + example_tags + ) + command.run() + + created_tags = db.session.query(Tag).join(TaggedObject).filter( + TaggedObject.object_id == example_dashboard.id, + Tag.type == TagTypes.custom + ).all() + assert example_tags == [tag.name for tag in created_tags] + + command = DeleteTagsCommand(example_tags) + command.run() + tags = db.session.query(Tag).filter(Tag.name.in_(example_tags)) + assert tags.count() == 0 + +# test delete tagged objects command +class TestDeleteTaggedObjectCommand(SupersetTestCase): + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + def test_delete_tags_command(self): + # create tagged objects + example_dashboard = ( + db.session.query(Dashboard).filter_by(slug="world_health").one() + ) + example_tags = ['create custom tag example 1', 'create custom tag example 2'] + command = CreateCustomTagCommand( + ObjectTypes.dashboard.value, + example_dashboard.id, + example_tags + ) + command.run() + + tagged_objects = db.session.query(TaggedObject).join(Tag).filter( + TaggedObject.object_id == example_dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard.name, + Tag.name.in_(example_tags) + ) + assert tagged_objects.count() == 2 + # delete one of the tagged objects + command = DeleteTaggedObjectCommand( + object_type=ObjectTypes.dashboard.value, + object_id=example_dashboard.id, + tag=example_tags[0] + ) + command.run() + tagged_objects = db.session.query(TaggedObject).join(Tag).filter( + TaggedObject.object_id == example_dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard.name, + Tag.name.in_(example_tags) + ) + assert tagged_objects.count() == 1 + + # cleanup + tags = db.session.query(Tag).filter(Tag.name.in_(example_tags)) + db.session.query(TaggedObject).filter( + TaggedObject.tag_id.in_([tag.id for tag in tags]) + ).delete() + tags.delete() + db.session.commit() diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py index 2a3a13ae0d73a..0ad2c927d176c 100644 --- a/tests/integration_tests/tags/dao_tests.py +++ b/tests/integration_tests/tags/dao_tests.py @@ -20,10 +20,13 @@ import time from unittest.mock import patch import pytest -from superset.dao.exceptions import DAOCreateFailedError +from superset.dao.exceptions import DAOCreateFailedError, DAOException +from superset.models.slice import Slice +from superset.models.sql_lab import SavedQuery from superset.tags.dao import TagDAO from superset.tags.exceptions import InvalidTagNameError -from superset.tags.models import ObjectTypes, Tag +from superset.tags.models import ObjectTypes, Tag, TaggedObject +from tests.integration_tests.tags.api_tests import TAGS_FIXTURE_COUNT import tests.integration_tests.test_app # pylint: disable=unused-import from superset import db, security_manager @@ -38,29 +41,218 @@ class TestTagsDAO(SupersetTestCase): + def insert_tag( + self, + name: str, + tag_type: str, + ) -> Tag: + tag_name = name.strip() + tag = Tag( + name=tag_name, + type=tag_type, + ) + db.session.add(tag) + db.session.commit() + return tag + + def insert_tagged_object( + self, + tag_id: int, + object_id: int, + object_type: ObjectTypes, + ) -> TaggedObject: + tag = db.session.query(Tag).filter(Tag.id == tag_id).first() + tagged_object = TaggedObject( + tag=tag, object_id=object_id, object_type=object_type.name + ) + db.session.add(tagged_object) + db.session.commit() + return tagged_object + + @pytest.fixture() + def create_tags(self): + with self.create_app().app_context(): + # clear tags table + tags = db.session.query(Tag).delete() + db.session.commit() + tags = [] + for cx in range(TAGS_FIXTURE_COUNT): + tags.append( + self.insert_tag( + name=f"example_tag_{cx}", + tag_type="custom", + ) + ) + yield tags + db.session.commit() + + @pytest.fixture() + def create_tagged_objects(self): + with self.create_app().app_context(): + # clear tags table + tags = db.session.query(Tag).delete() + tags = [] + for cx in range(TAGS_FIXTURE_COUNT): + tags.append( + self.insert_tag( + name=f"example_tag_{cx}", + tag_type="custom", + ) + ) + # clear tagged objects table + tagged_objects = db.session.query(TaggedObject).delete() + tagged_objects = [] + for tag in tags: + tagged_objects.append( + self.insert_tagged_object( + object_id=1, object_type=ObjectTypes.dashboard, tag_id=tag.id + ) + ) + yield tagged_objects + db.session.commit() + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") @pytest.mark.usefixtures("with_tagging_system_feature") - def test_create_tag(self): + # test create tag + def test_create_tagged_objects(self): # test that a tag cannot be added if it has ':' in it - try: + with pytest.raises(DAOCreateFailedError): TagDAO.create_custom_tagged_objects( - object_type=ObjectTypes.dashboard, + object_type=ObjectTypes.dashboard.name, object_id=1, - properties={"tags": ["invalid:example tag 1"]}, + tag_names=["invalid:example tag 1"], ) - except Exception as e: - assert type(e) is DAOCreateFailedError # test that a tag can be added if it has a valid name - try: - TagDAO.create_custom_tagged_objects( - object_type=ObjectTypes.dashboard, - object_id=1, - properties={"tags": ["example tag 1"]}, - ) - except Exception as e: - # should not be an exception - assert e is None - + TagDAO.create_custom_tagged_objects( + object_type=ObjectTypes.dashboard.name, + object_id=1, + tag_names=["example tag 1"], + ) # check if tag exists assert db.session.query(Tag).filter(Tag.name == "example tag 1").first() + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + @pytest.mark.usefixtures("create_tagged_objects") + # test get objects from tag + def test_get_objects_from_tag(self): + # get objects + tagged_objects = TagDAO.get_tagged_objects_for_tags( + ["example_tag_1", "example_tag_2"] + ) + assert len(tagged_objects) == 1 + + # test get objects from tag with type + tagged_objects = TagDAO.get_tagged_objects_for_tags( + ["example_tag_1", "example_tag_2"], obj_types=["dashboard", "chart"] + ) + assert len(tagged_objects) == 1 + tagged_objects = TagDAO.get_tagged_objects_for_tags( + ["example_tag_1", "example_tag_2"], obj_types=["chart"] + ) + assert len(tagged_objects) == 0 + # test get all objects + num_charts = db.session.query(Slice).count() + num_objects = ( + db.session.query(Dashboard).count() + + num_charts + + db.session.query(SavedQuery).count() + ) + tagged_objects = TagDAO.get_tagged_objects_for_tags() + assert len(tagged_objects) == num_objects + # test get all objects by type + tagged_objects = TagDAO.get_tagged_objects_for_tags( + obj_types=["dashboard", "chart", "saved_queries"] + ) + assert len(tagged_objects) == num_objects + # test objects are retrieved by type + tagged_objects = TagDAO.get_tagged_objects_for_tags(obj_types=["chart"]) + assert len(tagged_objects) == num_charts + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + @pytest.mark.usefixtures("create_tagged_objects") + def test_find_tagged_object(self): + tag = db.session.query(Tag).filter(Tag.name == "example_tag_1").first() + tagged_object = TagDAO.find_tagged_object( + object_id=1, object_type=ObjectTypes.dashboard.name, tag_id=tag.id + ) + assert tagged_object is not None + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + @pytest.mark.usefixtures("create_tagged_objects") + def test_find_by_name(self): + # test tag can be found + tag = TagDAO.find_by_name("example_tag_1") + assert tag is not None + # tag that doesnt exist + tag = TagDAO.find_by_name("invalid_tag_1") + assert tag is None + # tag was not created + assert db.session.query(Tag).filter(Tag.name == "invalid_tag_1").first() is None + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + @pytest.mark.usefixtures("create_tagged_objects") + def test_get_by_name(self): + # test tag can be found + tag = TagDAO.get_by_name("example_tag_1") + assert tag is not None + # tag that doesnt exist is added + tag = TagDAO.get_by_name("invalid_tag_1") + assert tag is not None + # tag was created + tag = db.session.query(Tag).filter(Tag.name == "invalid_tag_1").first() + assert tag is not None + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + @pytest.mark.usefixtures("create_tags") + def test_delete_tags(self): + tag_names = ["example_tag_1", "example_tag_2"] + for tag_name in tag_names: + tag = db.session.query(Tag).filter(Tag.name == tag_name).first() + assert tag is not None + + TagDAO.delete_tags(tag_names) + + for tag_name in tag_names: + tag = db.session.query(Tag).filter(Tag.name == tag_name).first() + assert tag is None + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + @pytest.mark.usefixtures("create_tagged_objects") + def test_delete_tagged_object(self): + tag = db.session.query(Tag).filter(Tag.name == "example_tag_1").first() + tagged_object = ( + db.session.query(TaggedObject) + .filter( + TaggedObject.tag_id == tag.id, + TaggedObject.object_id == 1, + TaggedObject.object_type == ObjectTypes.dashboard.name, + ) + .first() + ) + assert tagged_object is not None + TagDAO.delete_tagged_object( + object_type=ObjectTypes.dashboard.name, object_id=1, tag_name=tag.name + ) + tagged_object = ( + db.session.query(TaggedObject) + .filter( + TaggedObject.tag_id == tag.id, + TaggedObject.object_id == 1, + TaggedObject.object_type == ObjectTypes.dashboard.name, + ) + .first() + ) + assert tagged_object is None + + @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("with_tagging_system_feature") + def test_validate_tag_name(self): + assert TagDAO.validate_tag_name("example_tag_name") is True + assert TagDAO.validate_tag_name("invalid:tag_name") is False From a8974e7c39499825c642717934fef073bc81349b Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Thu, 2 Feb 2023 13:01:18 -0500 Subject: [PATCH 66/76] fixed lint and apispec errors --- superset-frontend/src/tags.ts | 25 ++-- .../views/CRUD/allentities/AllEntities.tsx | 16 +-- superset/initialization/__init__.py | 2 +- superset/tags/api.py | 84 ++++++++------ superset/tags/commands/delete.py | 41 +++---- superset/tags/commands/utils.py | 3 +- superset/tags/schemas.py | 2 + tests/integration_tests/tags/api_tests.py | 22 ++-- .../integration_tests/tags/commands_tests.py | 109 ++++++++++-------- 9 files changed, 165 insertions(+), 139 deletions(-) diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index d13be2f0ffa90..718cd4551c7f2 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -17,6 +17,7 @@ * under the License. */ import { JsonObject, SupersetClient } from '@superset-ui/core'; +import rison from 'rison'; import Tag from 'src/types/TagType'; export const OBJECT_TYPES_VALUES = Object.freeze([ @@ -80,7 +81,7 @@ export function fetchTags( }) .then(({ json }) => callback( - json['result']['tags'].filter( + json.result.tags.filter( (tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes, ), ), @@ -116,10 +117,9 @@ export function deleteTaggedObjects( throw new Error(msg); } SupersetClient.delete({ - endpoint: `/api/v1/tag/${map_object_type_to_id(objectType)}/${objectId}/`, - body: JSON.stringify({ tag: tag.name }), - parseMethod: 'json', - headers: { 'Content-Type': 'application/json' }, + endpoint: `/api/v1/tag/${map_object_type_to_id(objectType)}/${objectId}/${ + tag.name + }`, }) .then(({ json }) => json @@ -137,12 +137,9 @@ export function deleteTags( callback: (text: string) => void, error: (response: string) => void, ) { - const tags_str = JSON.stringify(tags.map(tag => tag.name) as string[]); + const tag_names = tags.map(tag => tag.name) as string[]; SupersetClient.delete({ - endpoint: `/api/v1/tag/`, - body: tags_str, - parseMethod: 'json', - headers: { 'Content-Type': 'application/json' }, + endpoint: `/api/v1/tag/?q=${rison.encode(tag_names)}`, }) .then(({ json }) => json.message @@ -178,7 +175,11 @@ export function addTag( const objectTypeId = map_object_type_to_id(objectType); SupersetClient.post({ endpoint: `/api/v1/tag/${objectTypeId}/${objectId}/`, - body: JSON.stringify({ tags: [tag] }), + body: JSON.stringify({ + properties: { + tags: [tag], + }, + }), parseMethod: 'json', headers: { 'Content-Type': 'application/json' }, }) @@ -196,6 +197,6 @@ export function fetchObjects( url += `&types=${types}`; } SupersetClient.get({ endpoint: url }) - .then(({ json }) => callback(json['result'])) + .then(({ json }) => callback(json.result)) .catch(response => error(response)); } diff --git a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx index 6c85c676eb9dc..995c12734cf41 100644 --- a/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx +++ b/superset-frontend/src/views/CRUD/allentities/AllEntities.tsx @@ -20,11 +20,11 @@ import React, { useMemo } from 'react'; import { ensureIsArray, styled, t } from '@superset-ui/core'; import { StringParam, useQueryParam } from 'use-query-params'; import withToasts from 'src/components/MessageToasts/withToasts'; -import AllEntitiesTable from './AllEntitiesTable'; -import { AsyncSelect } from 'src/components'; +import AsyncSelect from 'src/components/Select/AsyncSelect'; import { SelectValue } from 'antd/lib/select'; import { loadTags } from 'src/components/Tags/utils'; import { getValue } from 'src/components/Select/utils'; +import AllEntitiesTable from './AllEntitiesTable'; const AllEntitiesContainer = styled.div` ${({ theme }) => ` @@ -62,11 +62,13 @@ function AllEntities() { setTagsQuery(tagSearch); }; - const tagsValue = useMemo(() => { - return tagsQuery - ? tagsQuery.split(',').map(tag => ({ value: tag, label: tag })) - : []; - }, [tagsQuery]); + const tagsValue = useMemo( + () => + tagsQuery + ? tagsQuery.split(',').map(tag => ({ value: tag, label: tag })) + : [], + [tagsQuery], + ); return ( diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 2583add85b26f..cda0651456b9f 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -150,8 +150,8 @@ def init_views(self) -> None: from superset.reports.api import ReportScheduleRestApi from superset.reports.logs.api import ReportExecutionLogRestApi from superset.security.api import SecurityRestApi - from superset.tags.api import TagRestApi from superset.sqllab.api import SqlLabRestApi + from superset.tags.api import TagRestApi from superset.views.access_requests import AccessRequestsModelView from superset.views.alerts import AlertView, ReportView from superset.views.all_entities import TaggedObjectsModelView, TaggedObjectView diff --git a/superset/tags/api.py b/superset/tags/api.py index 4036fac2811e1..828c08ad21226 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -15,9 +15,10 @@ # specific language governing permissions and limitations # under the License. import logging +from typing import Any from flask import request, Response -from flask_appbuilder.api import expose, protect, safe +from flask_appbuilder.api import expose, protect, rison, safe from flask_appbuilder.models.sqla.interface import SQLAInterface from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod @@ -35,6 +36,7 @@ from superset.tags.dao import TagDAO from superset.tags.models import ObjectTypes, Tag from superset.tags.schemas import ( + delete_tags_schema, openapi_spec_methods_override, TaggedObjectEntityResponseSchema, TagGetResponseSchema, @@ -107,7 +109,13 @@ class TagRestApi(BaseSupersetModelRestApi): openapi_spec_tag = "Tags" """ Override the name set for this collection of endpoints """ - openapi_spec_component_schemas = (TagGetResponseSchema,) + openapi_spec_component_schemas = ( + TagGetResponseSchema, + TaggedObjectEntityResponseSchema, + ) + apispec_parameter_schemas = { + "delete_tags_schema": delete_tags_schema, + } openapi_spec_methods = openapi_spec_methods_override """ Overrides GET methods OpenApi descriptions """ @@ -128,7 +136,7 @@ def __repr__(self) -> str: log_to_statsd=False, ) def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Response: - """Adds tags to an object. + """Adds tags to an object. Creates new tags if they do not already exist --- post: description: >- @@ -139,7 +147,13 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon content: application/json: schema: - $ref: '#/components/schemas/{{self.__class__.__name__}}.post' + type: object + properties: + tags: + description: list of tag names to add to object + type: array + items: + type: string parameters: - in: path schema: @@ -152,15 +166,6 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon responses: 201: description: Tag added - content: - application/json: - schema: - type: object - properties: - id: - type: number - result: - $ref: '#/components/schemas/{{self.__class__.__name__}}.post' 302: description: Redirects to the current digest 400: @@ -173,13 +178,14 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon $ref: '#/components/responses/500' """ try: - tags = request.json["tags"] + tags = request.json["properties"]["tags"] # This validates custom Schema with custom validations CreateCustomTagCommand(object_type, object_id, tags).run() return self.response(201) except KeyError: return self.response( - 400, message="Missing required 'tags' field in request body" + 400, + message="Missing required 'tags' field under 'properties' in request body", ) except TagInvalidError: return self.response(422, message="Invalid tag") @@ -192,7 +198,7 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon ) return self.response_422(message=str(ex)) - @expose("///", methods=["DELETE"]) + @expose("////", methods=["DELETE"]) @protect() @safe @statsd_metrics @@ -201,21 +207,18 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon log_to_statsd=True, ) def delete_tagged_object( - self, object_type: ObjectTypes, object_id: int + self, object_type: ObjectTypes, object_id: int, tag: str ) -> Response: """Deletes a Tagged Object --- delete: description: >- Deletes a Tagged Object. - requestBody: - description: Tag name - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/{{self.__class__.__name__}}.delete' parameters: + - in: path + schema: + type: string + name: tag - in: path schema: type: integer @@ -246,7 +249,6 @@ def delete_tagged_object( $ref: '#/components/responses/500' """ try: - tag = request.json["tag"] DeleteTaggedObjectCommand(object_type, object_id, tag).run() return self.response(200, message="OK") except TagInvalidError: @@ -268,25 +270,35 @@ def delete_tagged_object( @protect() @safe @statsd_metrics + @rison(delete_tags_schema) @event_logger.log_this_with_context( action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.bulk_delete", log_to_statsd=False, ) - def bulk_delete(self) -> Response: + def bulk_delete(self, **kwargs: Any) -> Response: """Delete Tags --- delete: description: >- - Deletes multiple Tags. - requestBody: - description: List of Tag names - required: true - name: tags + Deletes multiple Tags. This will remove all tagged objects with this tag + parameters: + - in: query + name: q + content: + application/json: + schema: + $ref: '#/components/schemas/delete_tags_schema' + responses: 200: description: Deletes multiple Tags content: application/json: + schema: + type: object + properties: + message: + type: string 401: $ref: '#/components/responses/401' 403: @@ -298,7 +310,7 @@ def bulk_delete(self) -> Response: 500: $ref: '#/components/responses/500' """ - tags = request.json + tags = kwargs["rison"] try: DeleteTagsCommand(tags).run() return self.response(200, message=f"Deleted {len(tags)} tags") @@ -330,16 +342,16 @@ def get_objects(self) -> Response: name: tag_id responses: 200: - description: Objects fetched + description: List of tagged objects associated with a Tag content: application/json: schema: type: object properties: - id: - type: number result: - $ref: '#/components/schemas/{{self.__class__.__name__}}.post' + type: array + items: + $ref: '#/components/schemas/TaggedObjectEntityResponseSchema' 302: description: Redirects to the current digest 400: diff --git a/superset/tags/commands/delete.py b/superset/tags/commands/delete.py index 2113c576f6bec..63a514e5996d7 100644 --- a/superset/tags/commands/delete.py +++ b/superset/tags/commands/delete.py @@ -17,15 +17,14 @@ import logging from typing import List - from superset.commands.base import BaseCommand from superset.dao.exceptions import DAODeleteFailedError from superset.tags.commands.exceptions import ( TagDeleteFailedError, + TaggedObjectDeleteFailedError, + TaggedObjectNotFoundError, TagInvalidError, TagNotFoundError, - TaggedObjectDeleteFailedError, - TaggedObjectNotFoundError ) from superset.tags.commands.utils import to_object_type from superset.tags.dao import TagDAO @@ -47,10 +46,9 @@ def run(self) -> None: object_type = to_object_type(self._object_type) if object_type is None: raise TaggedObjectDeleteFailedError( - f'invalid object type {self._object_type}') - TagDAO.delete_tagged_object( - object_type, self._object_id, self._tag - ) + f"invalid object type {self._object_type}" + ) + TagDAO.delete_tagged_object(object_type, self._object_id, self._tag) except DAODeleteFailedError as ex: logger.exception(ex.exception) raise TaggedObjectDeleteFailedError() from ex @@ -63,27 +61,30 @@ def validate(self) -> None: # Validate tagged object exists tag = TagDAO.find_by_name(self._tag) if not tag: - exceptions.append(TaggedObjectDeleteFailedError( - f'could not find tag: {self._tag}' - )) + exceptions.append( + TaggedObjectDeleteFailedError(f"could not find tag: {self._tag}") + ) else: # Validate object type object_type = to_object_type(self._object_type) if object_type is None: - exceptions.append(TaggedObjectDeleteFailedError( - f'invalid object type {self._object_type}')) + exceptions.append( + TaggedObjectDeleteFailedError( + f"invalid object type {self._object_type}" + ) + ) else: tagged_object = TagDAO.find_tagged_object( - object_type=object_type, - object_id=self._object_id, - tag_id=tag.id + object_type=object_type, object_id=self._object_id, tag_id=tag.id ) if tagged_object is None: - exceptions.append(TaggedObjectNotFoundError( - object_id=self._object_id, - object_type=object_type.name, - tag_name=self._tag - )) + exceptions.append( + TaggedObjectNotFoundError( + object_id=self._object_id, + object_type=object_type.name, + tag_name=self._tag, + ) + ) if exceptions: exception = TagInvalidError() exception.add_list(exceptions) diff --git a/superset/tags/commands/utils.py b/superset/tags/commands/utils.py index 89035503a391a..2993365b7ac75 100644 --- a/superset/tags/commands/utils.py +++ b/superset/tags/commands/utils.py @@ -16,6 +16,7 @@ # under the License. from typing import Optional, Union + from superset.tags.models import ObjectTypes @@ -23,6 +24,6 @@ def to_object_type(object_type: Union[ObjectTypes, int, str]) -> Optional[Object if isinstance(object_type, ObjectTypes): return object_type for type_ in ObjectTypes: - if (object_type in [type_.value, type_.name]): + if object_type in [type_.value, type_.name]: return type_ return None diff --git a/superset/tags/schemas.py b/superset/tags/schemas.py index 0496eef916afb..2081adf69fa80 100644 --- a/superset/tags/schemas.py +++ b/superset/tags/schemas.py @@ -18,6 +18,8 @@ from superset.dashboards.schemas import UserSchema +delete_tags_schema = {"type": "array", "items": {"type": "string"}} + object_type_description = "A title for the tag." openapi_spec_methods_override = { diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 088346cdc4f85..30f7382af1148 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -168,7 +168,7 @@ def test_add_tagged_objects(self): dashboard_type = ObjectTypes.dashboard.value uri = f"api/v1/tag/{dashboard_type}/{dashboard_id}/" example_tag_names = ["example_tag_1", "example_tag_2"] - data = {"tags": example_tag_names} + data = {"properties": {"tags": example_tag_names}} rv = self.client.post(uri, json=data, follow_redirects=True) # successful request self.assertEqual(rv.status_code, 201) @@ -225,9 +225,8 @@ def test_delete_tagged_objects(self): .first() ) assert tagged_object is not None - uri = f"api/v1/tag/{dashboard_type.value}/{dashboard_id}/" - data = {"tag": tags.first().name} - rv = self.client.delete(uri, json=data, follow_redirects=True) + uri = f"api/v1/tag/{dashboard_type.value}/{dashboard_id}/{tags.first().name}" + rv = self.client.delete(uri, follow_redirects=True) # successful request self.assertEqual(rv.status_code, 200) # ensure that tagged object no longer exists @@ -297,12 +296,6 @@ def test_get_all_objects(self): self.insert_tagged_object( tag_id=tag.id, object_id=dashboard_id, object_type=dashboard_type ) - num_objects = ( - db.session.query(Dashboard).count() - + db.session.query(Slice).count() - + db.session.query(SavedQuery).count() - ) - tagged_objects = db.session.query(TaggedObject).filter( TaggedObject.tag_id.in_([tag.id for tag in tags]), TaggedObject.object_id == dashboard_id, @@ -330,9 +323,8 @@ def test_delete_tags(self): tags = db.session.query(Tag).filter(Tag.name.in_(example_tag_names)) self.assertEqual(tags.count(), 3) # delete the first tag - uri = "api/v1/tag/" - data = example_tag_names[:1] - rv = self.client.delete(uri, json=data, follow_redirects=True) + uri = f"api/v1/tag/?q={prison.dumps(example_tag_names[:1])}" + rv = self.client.delete(uri, follow_redirects=True) # successful request self.assertEqual(rv.status_code, 200) # check that tag does not exist in the database @@ -341,8 +333,8 @@ def test_delete_tags(self): tags = db.session.query(Tag).filter(Tag.name.in_(example_tag_names)) self.assertEqual(tags.count(), 2) # delete multiple tags - data = example_tag_names[1:] - rv = self.client.delete(uri, json=data, follow_redirects=True) + uri = f"api/v1/tag/?q={prison.dumps(example_tag_names[1:])}" + rv = self.client.delete(uri, follow_redirects=True) # successful request self.assertEqual(rv.status_code, 200) # check that tags are all gone diff --git a/tests/integration_tests/tags/commands_tests.py b/tests/integration_tests/tags/commands_tests.py index 76c4f75694b86..43fe4836e0d80 100644 --- a/tests/integration_tests/tags/commands_tests.py +++ b/tests/integration_tests/tags/commands_tests.py @@ -20,9 +20,6 @@ import pytest import yaml -from superset.tags.commands.create import CreateCustomTagCommand -from superset.tags.commands.delete import DeleteTaggedObjectCommand, DeleteTagsCommand -from superset.tags.models import ObjectTypes, Tag, TagTypes, TaggedObject from werkzeug.utils import secure_filename from superset import db, security_manager @@ -39,6 +36,9 @@ from superset.models.core import Database from superset.models.dashboard import Dashboard from superset.models.slice import Slice +from superset.tags.commands.create import CreateCustomTagCommand +from superset.tags.commands.delete import DeleteTaggedObjectCommand, DeleteTagsCommand +from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes from tests.integration_tests.base_tests import SupersetTestCase from tests.integration_tests.fixtures.importexport import ( chart_config, @@ -49,11 +49,12 @@ dataset_config, dataset_metadata_config, ) +from tests.integration_tests.fixtures.tags import with_tagging_system_feature from tests.integration_tests.fixtures.world_bank_dashboard import ( load_world_bank_dashboard_with_slices, load_world_bank_data, ) -from tests.integration_tests.fixtures.tags import with_tagging_system_feature + # test create command class TestCreateCustomTagCommand(SupersetTestCase): @@ -63,28 +64,32 @@ def test_create_custom_tag_command(self): example_dashboard = ( db.session.query(Dashboard).filter_by(slug="world_health").one() ) - example_tags = ['create custom tag example 1', 'create custom tag example 2'] + example_tags = ["create custom tag example 1", "create custom tag example 2"] command = CreateCustomTagCommand( - ObjectTypes.dashboard.value, - example_dashboard.id, - example_tags - ) + ObjectTypes.dashboard.value, example_dashboard.id, example_tags + ) command.run() - created_tags = db.session.query(Tag).join(TaggedObject).filter( - TaggedObject.object_id == example_dashboard.id, - Tag.type == TagTypes.custom - ).all() + created_tags = ( + db.session.query(Tag) + .join(TaggedObject) + .filter( + TaggedObject.object_id == example_dashboard.id, + Tag.type == TagTypes.custom, + ) + .all() + ) assert example_tags == [tag.name for tag in created_tags] - + # cleanup tags = db.session.query(Tag).filter(Tag.name.in_(example_tags)) db.session.query(TaggedObject).filter( - TaggedObject.tag_id.in_([tag.id for tag in tags]) - ).delete() + TaggedObject.tag_id.in_([tag.id for tag in tags]) + ).delete() tags.delete() db.session.commit() + # test delete tags command class TestDeleteTagsCommand(SupersetTestCase): @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") @@ -93,25 +98,29 @@ def test_delete_tags_command(self): example_dashboard = ( db.session.query(Dashboard).filter_by(slug="world_health").one() ) - example_tags = ['create custom tag example 1', 'create custom tag example 2'] + example_tags = ["create custom tag example 1", "create custom tag example 2"] command = CreateCustomTagCommand( - ObjectTypes.dashboard.value, - example_dashboard.id, - example_tags - ) + ObjectTypes.dashboard.value, example_dashboard.id, example_tags + ) command.run() - created_tags = db.session.query(Tag).join(TaggedObject).filter( - TaggedObject.object_id == example_dashboard.id, - Tag.type == TagTypes.custom - ).all() + created_tags = ( + db.session.query(Tag) + .join(TaggedObject) + .filter( + TaggedObject.object_id == example_dashboard.id, + Tag.type == TagTypes.custom, + ) + .all() + ) assert example_tags == [tag.name for tag in created_tags] - + command = DeleteTagsCommand(example_tags) command.run() tags = db.session.query(Tag).filter(Tag.name.in_(example_tags)) assert tags.count() == 0 + # test delete tagged objects command class TestDeleteTaggedObjectCommand(SupersetTestCase): @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") @@ -121,38 +130,44 @@ def test_delete_tags_command(self): example_dashboard = ( db.session.query(Dashboard).filter_by(slug="world_health").one() ) - example_tags = ['create custom tag example 1', 'create custom tag example 2'] + example_tags = ["create custom tag example 1", "create custom tag example 2"] command = CreateCustomTagCommand( - ObjectTypes.dashboard.value, - example_dashboard.id, - example_tags - ) + ObjectTypes.dashboard.value, example_dashboard.id, example_tags + ) command.run() - tagged_objects = db.session.query(TaggedObject).join(Tag).filter( - TaggedObject.object_id == example_dashboard.id, - TaggedObject.object_type == ObjectTypes.dashboard.name, - Tag.name.in_(example_tags) - ) + tagged_objects = ( + db.session.query(TaggedObject) + .join(Tag) + .filter( + TaggedObject.object_id == example_dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard.name, + Tag.name.in_(example_tags), + ) + ) assert tagged_objects.count() == 2 # delete one of the tagged objects command = DeleteTaggedObjectCommand( - object_type=ObjectTypes.dashboard.value, - object_id=example_dashboard.id, - tag=example_tags[0] - ) + object_type=ObjectTypes.dashboard.value, + object_id=example_dashboard.id, + tag=example_tags[0], + ) command.run() - tagged_objects = db.session.query(TaggedObject).join(Tag).filter( - TaggedObject.object_id == example_dashboard.id, - TaggedObject.object_type == ObjectTypes.dashboard.name, - Tag.name.in_(example_tags) - ) + tagged_objects = ( + db.session.query(TaggedObject) + .join(Tag) + .filter( + TaggedObject.object_id == example_dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard.name, + Tag.name.in_(example_tags), + ) + ) assert tagged_objects.count() == 1 # cleanup tags = db.session.query(Tag).filter(Tag.name.in_(example_tags)) db.session.query(TaggedObject).filter( - TaggedObject.tag_id.in_([tag.id for tag in tags]) - ).delete() + TaggedObject.tag_id.in_([tag.id for tag in tags]) + ).delete() tags.delete() db.session.commit() From ddaa260ff880f2851adb257cb5aff124da3a2e70 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Thu, 2 Feb 2023 14:40:40 -0500 Subject: [PATCH 67/76] fixed lint errors and old tagging tests --- superset-frontend/src/tags.ts | 15 --------------- superset/tags/api.py | 17 +++++++++++------ tests/integration_tests/tagging_tests.py | 12 ------------ 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index 718cd4551c7f2..2cb6e27412697 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -88,21 +88,6 @@ export function fetchTags( ) .catch(response => error(response)); } - -export function fetchSuggestions( - { includeTypes = false }, - callback: (json: JsonObject) => void, - error: (response: Response) => void, -) { - SupersetClient.get({ endpoint: '/taggedobjectview/tags/suggestions/' }) - .then(({ json }) => - callback( - json.filter((tag: Tag) => tag.name.indexOf(':') === -1 || includeTypes), - ), - ) - .catch(response => error(response)); -} - export function deleteTaggedObjects( { objectType, objectId }: { objectType: string; objectId: number }, tag: Tag, diff --git a/superset/tags/api.py b/superset/tags/api.py index 828c08ad21226..178e0e3765143 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -132,7 +132,8 @@ def __repr__(self) -> str: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.add_tagged_objects", + action=lambda self, * + args, **kwargs: f"{self.__class__.__name__}.add_tagged_objects", log_to_statsd=False, ) def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Response: @@ -185,7 +186,7 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon except KeyError: return self.response( 400, - message="Missing required 'tags' field under 'properties' in request body", + message="Missing required field 'tags' in 'properties'", ) except TagInvalidError: return self.response(422, message="Invalid tag") @@ -203,7 +204,8 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.delete_tagged_object", + action=lambda self, * + args, **kwargs: f"{self.__class__.__name__}.delete_tagged_object", log_to_statsd=True, ) def delete_tagged_object( @@ -272,7 +274,8 @@ def delete_tagged_object( @statsd_metrics @rison(delete_tags_schema) @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.bulk_delete", + action=lambda self, * + args, **kwargs: f"{self.__class__.__name__}.bulk_delete", log_to_statsd=False, ) def bulk_delete(self, **kwargs: Any) -> Response: @@ -326,7 +329,8 @@ def bulk_delete(self, **kwargs: Any) -> Response: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get_objects", + action=lambda self, * + args, **kwargs: f"{self.__class__.__name__}.get_objects", log_to_statsd=False, ) def get_objects(self) -> Response: @@ -365,7 +369,8 @@ def get_objects(self) -> Response: """ tags = [tag for tag in request.args.get("tags", "").split(",") if tag] # filter types - types = [type_ for type_ in request.args.get("types", "").split(",") if type_] + types = [type_ for type_ in request.args.get( + "types", "").split(",") if type_] try: tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types) diff --git a/tests/integration_tests/tagging_tests.py b/tests/integration_tests/tagging_tests.py index 4ee10041d2c53..71fb7e4e4e89d 100644 --- a/tests/integration_tests/tagging_tests.py +++ b/tests/integration_tests/tagging_tests.py @@ -42,18 +42,6 @@ def clear_tagged_object_table(self): db.session.query(TaggedObject).delete() db.session.commit() - @with_feature_flags(TAGGING_SYSTEM=False) - def test_tag_view_disabled(self): - self.login("admin") - response = self.client.get("/tagview/tags/suggestions/") - self.assertEqual(404, response.status_code) - - @with_feature_flags(TAGGING_SYSTEM=True) - def test_tag_view_enabled(self): - self.login("admin") - response = self.client.get("/tagview/tags/suggestions/") - self.assertNotEqual(404, response.status_code) - @pytest.mark.usefixtures("with_tagging_system_feature") def test_dataset_tagging(self): """ From 8e157e30b40f62a3359b657a5b89ded84e1c3174 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Thu, 2 Feb 2023 15:01:40 -0500 Subject: [PATCH 68/76] fixed precommit error --- superset/tags/api.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/superset/tags/api.py b/superset/tags/api.py index 178e0e3765143..5fa63af5f622b 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -132,8 +132,7 @@ def __repr__(self) -> str: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, * - args, **kwargs: f"{self.__class__.__name__}.add_tagged_objects", + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.add_tagged_objects", log_to_statsd=False, ) def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Response: @@ -204,8 +203,7 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, * - args, **kwargs: f"{self.__class__.__name__}.delete_tagged_object", + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.delete_tagged_object", log_to_statsd=True, ) def delete_tagged_object( @@ -274,8 +272,7 @@ def delete_tagged_object( @statsd_metrics @rison(delete_tags_schema) @event_logger.log_this_with_context( - action=lambda self, * - args, **kwargs: f"{self.__class__.__name__}.bulk_delete", + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.bulk_delete", log_to_statsd=False, ) def bulk_delete(self, **kwargs: Any) -> Response: @@ -329,8 +326,7 @@ def bulk_delete(self, **kwargs: Any) -> Response: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, * - args, **kwargs: f"{self.__class__.__name__}.get_objects", + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get_objects", log_to_statsd=False, ) def get_objects(self) -> Response: @@ -369,8 +365,7 @@ def get_objects(self) -> Response: """ tags = [tag for tag in request.args.get("tags", "").split(",") if tag] # filter types - types = [type_ for type_ in request.args.get( - "types", "").split(",") if type_] + types = [type_ for type_ in request.args.get("types", "").split(",") if type_] try: tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types) From cfa1c6c47d8ee516eac4305fbac9871cdb7e18d4 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Thu, 2 Feb 2023 15:18:39 -0500 Subject: [PATCH 69/76] fixed pylint error --- superset/tags/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/superset/tags/api.py b/superset/tags/api.py index 5fa63af5f622b..44b729ea38d40 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -132,7 +132,7 @@ def __repr__(self) -> str: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.add_tagged_objects", + action=lambda self, *args, **kwargs: Any, log_to_statsd=False, ) def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Response: @@ -203,7 +203,7 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.delete_tagged_object", + action=lambda self, *args, **kwargs: Any, log_to_statsd=True, ) def delete_tagged_object( From 2c22fba9e77c599cf74ab254f0e39ccaee47acaf Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Fri, 3 Feb 2023 09:01:30 -0500 Subject: [PATCH 70/76] fixed pre-commit errors --- superset/constants.py | 4 ++-- superset/tags/api.py | 12 ++++++------ tests/integration_tests/tags/api_tests.py | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/superset/constants.py b/superset/constants.py index d781276340e68..07bbe6c07cea5 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -145,8 +145,8 @@ class RouteMethod: # pylint: disable=too-few-public-methods "stop_query": "read", "get_objects": "read", "get_all_objects": "read", - "add_tagged_objects": "write", - "delete_tagged_object": "write", + "add_objects": "write", + "delete_object": "write", } EXTRA_FORM_DATA_APPEND_KEYS = { diff --git a/superset/tags/api.py b/superset/tags/api.py index 44b729ea38d40..d7c57c0320705 100644 --- a/superset/tags/api.py +++ b/superset/tags/api.py @@ -59,8 +59,8 @@ class TagRestApi(BaseSupersetModelRestApi): "bulk_delete", "get_objects", "get_all_objects", - "add_tagged_objects", - "delete_tagged_object", + "add_objects", + "delete_object", } resource_name = "tag" @@ -132,10 +132,10 @@ def __repr__(self) -> str: @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: Any, + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.add_objects", log_to_statsd=False, ) - def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Response: + def add_objects(self, object_type: ObjectTypes, object_id: int) -> Response: """Adds tags to an object. Creates new tags if they do not already exist --- post: @@ -203,10 +203,10 @@ def add_tagged_objects(self, object_type: ObjectTypes, object_id: int) -> Respon @safe @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: Any, + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.delete_object", log_to_statsd=True, ) - def delete_tagged_object( + def delete_object( self, object_type: ObjectTypes, object_id: int, tag: str ) -> Response: """Deletes a Tagged Object diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 30f7382af1148..468025062e623 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -98,6 +98,7 @@ def create_tags(self): tag_type="custom", ) ) + db.session.commit() yield tags # rollback changes From 27d6b6a7c0b6db7dc4667d683155cc34f76d20dd Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Fri, 3 Feb 2023 09:01:56 -0500 Subject: [PATCH 71/76] removed unnecessary commit --- tests/integration_tests/tags/api_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 468025062e623..30f7382af1148 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -98,7 +98,6 @@ def create_tags(self): tag_type="custom", ) ) - db.session.commit() yield tags # rollback changes From f19ee774ff76861621619875b3c36d454fe5f54b Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Mon, 6 Feb 2023 09:19:04 -0500 Subject: [PATCH 72/76] fixed api tests and dao tests --- superset/tags/dao.py | 6 ++--- tests/integration_tests/tags/api_tests.py | 18 ++++++++++---- tests/integration_tests/tags/dao_tests.py | 29 ++++++++++++++++------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/superset/tags/dao.py b/superset/tags/dao.py index 3d68098a6a1c7..297cd4ea8d864 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -169,7 +169,7 @@ def get_tagged_objects_for_tags( results: List[Dict[str, Any]] = [] # dashboards - if not obj_types or "dashboard" in obj_types: + if (not obj_types) or ("dashboard" in obj_types): dashboards = ( db.session.query(Dashboard) .join( @@ -197,7 +197,7 @@ def get_tagged_objects_for_tags( ) # charts - if not obj_types or "chart" in obj_types: + if (not obj_types) or ("chart" in obj_types): charts = ( db.session.query(Slice) .join( @@ -224,7 +224,7 @@ def get_tagged_objects_for_tags( ) # saved queries - if not obj_types or "query" in obj_types: + if (not obj_types) or ("query" in obj_types): saved_queries = ( db.session.query(SavedQuery) .join( diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 30f7382af1148..5a27aa8a967ce 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -34,6 +34,10 @@ from superset.models.core import Database from superset.utils.database import get_example_database, get_main_database from superset.tags.models import ObjectTypes, Tag, TagTypes, TaggedObject +from tests.integration_tests.fixtures.birth_names_dashboard import ( + load_birth_names_dashboard_with_slices, + load_birth_names_data +) from tests.integration_tests.fixtures.world_bank_dashboard import ( load_world_bank_dashboard_with_slices, load_world_bank_data, @@ -98,7 +102,7 @@ def create_tags(self): tag_type="custom", ) ) - yield tags + yield # rollback changes for tag in tags: @@ -162,6 +166,7 @@ def test_get_list_tag(self): # test add tagged objects @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_add_tagged_objects(self): self.login(username="admin") dashboard_id = 1 @@ -192,6 +197,7 @@ def test_add_tagged_objects(self): # test delete tagged object @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") @pytest.mark.usefixtures("create_tags") def test_delete_tagged_objects(self): self.login(username="admin") @@ -256,6 +262,7 @@ def test_delete_tagged_objects(self): # test get objects @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") @pytest.mark.usefixtures("create_tags") def test_get_objects_by_tag(self): self.login(username="admin") @@ -285,9 +292,11 @@ def test_get_objects_by_tag(self): # test get all objects @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") @pytest.mark.usefixtures("create_tags") def test_get_all_objects(self): self.login(username="admin") + # tag the dashboard with id 1 dashboard_id = 1 dashboard_type = ObjectTypes.dashboard tag_names = ["example_tag_1", "example_tag_2"] @@ -302,15 +311,14 @@ def test_get_all_objects(self): TaggedObject.object_type == dashboard_type.name, ) self.assertEqual(tagged_objects.count(), 2) + self.assertEqual(tagged_objects.first().object_id, dashboard_id) uri = "api/v1/tag/get_objects/" rv = self.client.get(uri) # successful request self.assertEqual(rv.status_code, 200) fetched_objects = rv.json["result"] - # check that all tagged objects are fetched - # when tagging system is false, there will only be the one dashboard - self.assertEqual(len(fetched_objects), 1) - self.assertEqual(fetched_objects[0]["id"], 1) + # check that the dashboard object was fetched + assert dashboard_id in [obj['id'] for obj in fetched_objects] # clean up tagged object tagged_objects.delete() diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py index 0ad2c927d176c..165cbd1f9077e 100644 --- a/tests/integration_tests/tags/dao_tests.py +++ b/tests/integration_tests/tags/dao_tests.py @@ -17,6 +17,7 @@ # isort:skip_file import copy import json +from operator import and_ import time from unittest.mock import patch import pytest @@ -153,19 +154,29 @@ def test_get_objects_from_tag(self): ) assert len(tagged_objects) == 0 # test get all objects - num_charts = db.session.query(Slice).count() - num_objects = ( - db.session.query(Dashboard).count() + num_charts = db.session.query(Slice).join( + TaggedObject, + and_( + TaggedObject.object_id == Slice.id, + TaggedObject.object_type == ObjectTypes.chart, + ), + ).distinct(Slice.id).count() + num_charts_and_dashboards = ( + db.session.query(Dashboard) + .join( + TaggedObject, + and_( + TaggedObject.object_id == Dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard, + ), + ).distinct(Slice.id).count() + num_charts - + db.session.query(SavedQuery).count() ) - tagged_objects = TagDAO.get_tagged_objects_for_tags() - assert len(tagged_objects) == num_objects - # test get all objects by type + # gets all tagged objects of type dashboard and chart tagged_objects = TagDAO.get_tagged_objects_for_tags( - obj_types=["dashboard", "chart", "saved_queries"] + obj_types=["dashboard", "chart"] ) - assert len(tagged_objects) == num_objects + assert len(tagged_objects) == num_charts_and_dashboards # test objects are retrieved by type tagged_objects = TagDAO.get_tagged_objects_for_tags(obj_types=["chart"]) assert len(tagged_objects) == num_charts From 14e2159c2d2876c150192b3a176d30adae5f2b11 Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Mon, 6 Feb 2023 09:52:34 -0500 Subject: [PATCH 73/76] fixed precommit error --- tests/integration_tests/tags/api_tests.py | 4 +-- tests/integration_tests/tags/dao_tests.py | 35 ++++++++++++++--------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 5a27aa8a967ce..4a1e04b5e602e 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -36,7 +36,7 @@ from superset.tags.models import ObjectTypes, Tag, TagTypes, TaggedObject from tests.integration_tests.fixtures.birth_names_dashboard import ( load_birth_names_dashboard_with_slices, - load_birth_names_data + load_birth_names_data, ) from tests.integration_tests.fixtures.world_bank_dashboard import ( load_world_bank_dashboard_with_slices, @@ -318,7 +318,7 @@ def test_get_all_objects(self): self.assertEqual(rv.status_code, 200) fetched_objects = rv.json["result"] # check that the dashboard object was fetched - assert dashboard_id in [obj['id'] for obj in fetched_objects] + assert dashboard_id in [obj["id"] for obj in fetched_objects] # clean up tagged object tagged_objects.delete() diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py index 165cbd1f9077e..054916f764f56 100644 --- a/tests/integration_tests/tags/dao_tests.py +++ b/tests/integration_tests/tags/dao_tests.py @@ -154,22 +154,29 @@ def test_get_objects_from_tag(self): ) assert len(tagged_objects) == 0 # test get all objects - num_charts = db.session.query(Slice).join( - TaggedObject, - and_( - TaggedObject.object_id == Slice.id, - TaggedObject.object_type == ObjectTypes.chart, - ), - ).distinct(Slice.id).count() + num_charts = ( + db.session.query(Slice) + .join( + TaggedObject, + and_( + TaggedObject.object_id == Slice.id, + TaggedObject.object_type == ObjectTypes.chart, + ), + ) + .distinct(Slice.id) + .count() + ) num_charts_and_dashboards = ( db.session.query(Dashboard) - .join( - TaggedObject, - and_( - TaggedObject.object_id == Dashboard.id, - TaggedObject.object_type == ObjectTypes.dashboard, - ), - ).distinct(Slice.id).count() + .join( + TaggedObject, + and_( + TaggedObject.object_id == Dashboard.id, + TaggedObject.object_type == ObjectTypes.dashboard, + ), + ) + .distinct(Slice.id) + .count() + num_charts ) # gets all tagged objects of type dashboard and chart From ecb02fc64ec3e8aaeea65069cefcbbfb06e6eb9e Mon Sep 17 00:00:00 2001 From: GITHUB_USERNAME Date: Mon, 6 Feb 2023 14:37:50 -0500 Subject: [PATCH 74/76] fixed tagging tests for postrges and mysql --- superset/tags/dao.py | 34 +++++++---- tests/integration_tests/tags/api_tests.py | 59 ++++++++++++++----- .../integration_tests/tags/commands_tests.py | 4 +- tests/integration_tests/tags/dao_tests.py | 35 +++++++++-- 4 files changed, 96 insertions(+), 36 deletions(-) diff --git a/superset/tags/dao.py b/superset/tags/dao.py index 297cd4ea8d864..c676b4ab3c25c 100644 --- a/superset/tags/dao.py +++ b/superset/tags/dao.py @@ -18,6 +18,8 @@ from operator import and_ from typing import Any, Dict, List, Optional +from sqlalchemy.exc import SQLAlchemyError + from superset.dao.base import BaseDAO from superset.dao.exceptions import DAOCreateFailedError, DAODeleteFailedError from superset.extensions import db @@ -35,8 +37,10 @@ class TagDAO(BaseDAO): @staticmethod def validate_tag_name(tag_name: str) -> bool: - if ":" in tag_name: - return False + invalid_characters = [":", ","] + for invalid_character in invalid_characters: + if invalid_character in tag_name: + return False return True @staticmethod @@ -47,7 +51,7 @@ def create_custom_tagged_objects( for name in tag_names: if not TagDAO.validate_tag_name(name): raise DAOCreateFailedError( - message="Invalid Tag Name (cannot contain ':')" + message="Invalid Tag Name (cannot contain ':' or ',')" ) type_ = TagTypes.custom tag_name = name.strip() @@ -83,14 +87,12 @@ def delete_tagged_object( object_type: {object_type} \ and tag name: "{tag_name}" could not be found' ) - deleted = tagged_object.delete() - if not deleted: - raise DAODeleteFailedError( - message=f'Tagged object with object_id: {object_id} \ - object_type: {object_type} \ - and tag name: "{tag_name}" could not be deleted' - ) - db.session.commit() + try: + db.session.delete(tagged_object.one()) + db.session.commit() + except SQLAlchemyError as ex: # pragma: no cover + db.session.rollback() + raise DAODeleteFailedError(exception=ex) from ex @staticmethod def delete_tags(tag_names: List[str]) -> None: @@ -105,8 +107,14 @@ def delete_tags(tag_names: List[str]) -> None: message=f"Tag with name {tag_name} does not exist." ) tags_to_delete.append(tag_name) - db.session.query(Tag).filter(Tag.name.in_(tags_to_delete)).delete() - db.session.commit() + tag_objects = db.session.query(Tag).filter(Tag.name.in_(tags_to_delete)) + for tag in tag_objects: + try: + db.session.delete(tag) + db.session.commit() + except SQLAlchemyError as ex: # pragma: no cover + db.session.rollback() + raise DAODeleteFailedError(exception=ex) from ex @staticmethod def get_by_name(name: str, type_: TagTypes = TagTypes.custom) -> Tag: diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py index 4a1e04b5e602e..7bf21da4fcd71 100644 --- a/tests/integration_tests/tags/api_tests.py +++ b/tests/integration_tests/tags/api_tests.py @@ -92,8 +92,10 @@ def insert_tagged_object( def create_tags(self): with self.create_app().app_context(): # clear tags table - tags = db.session.query(Tag).delete() - db.session.commit() + tags = db.session.query(Tag) + for tag in tags: + db.session.delete(tag) + db.session.commit() tags = [] for cx in range(TAGS_FIXTURE_COUNT): tags.append( @@ -107,7 +109,7 @@ def create_tags(self): # rollback changes for tag in tags: db.session.delete(tag) - db.session.commit() + db.session.commit() def test_get_tag(self): """ @@ -169,7 +171,21 @@ def test_get_list_tag(self): @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_add_tagged_objects(self): self.login(username="admin") - dashboard_id = 1 + # clean up tags and tagged objects + tags = db.session.query(Tag) + for tag in tags: + db.session.delete(tag) + db.session.commit() + tagged_objects = db.session.query(TaggedObject) + for tagged_object in tagged_objects: + db.session.delete(tagged_object) + db.session.commit() + dashboard = ( + db.session.query(Dashboard) + .filter(Dashboard.dashboard_title == "World Bank's Data") + .first() + ) + dashboard_id = dashboard.id dashboard_type = ObjectTypes.dashboard.value uri = f"api/v1/tag/{dashboard_type}/{dashboard_id}/" example_tag_names = ["example_tag_1", "example_tag_2"] @@ -183,17 +199,18 @@ def test_add_tagged_objects(self): # check that tagged objects were created tag_ids = [tags[0].id, tags[1].id] tagged_objects = db.session.query(TaggedObject).filter( - TaggedObject.tag_id.in_(tag_ids) + TaggedObject.tag_id.in_(tag_ids), + TaggedObject.object_id == dashboard_id, + TaggedObject.object_type == ObjectTypes.dashboard, ) - self.assertEqual(tagged_objects.count(), 2) - self.assertEqual(tagged_objects.first().object_id, 1) - self.assertEqual(tagged_objects.first().object_type, ObjectTypes.dashboard) - self.assertEqual(tagged_objects[1].object_id, 1) - self.assertEqual(tagged_objects[1].object_type, ObjectTypes.dashboard) + assert tagged_objects.count() == 2 # clean up tags and tagged objects - tagged_objects.delete() - tags.delete() - db.session.commit() + for tagged_object in tagged_objects: + db.session.delete(tagged_object) + db.session.commit() + for tag in tags: + db.session.delete(tag) + db.session.commit() # test delete tagged object @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") @@ -266,7 +283,12 @@ def test_delete_tagged_objects(self): @pytest.mark.usefixtures("create_tags") def test_get_objects_by_tag(self): self.login(username="admin") - dashboard_id = 1 + dashboard = ( + db.session.query(Dashboard) + .filter(Dashboard.dashboard_title == "World Bank's Data") + .first() + ) + dashboard_id = dashboard.id dashboard_type = ObjectTypes.dashboard tag_names = ["example_tag_1", "example_tag_2"] tags = db.session.query(Tag).filter(Tag.name.in_(tag_names)) @@ -286,7 +308,7 @@ def test_get_objects_by_tag(self): self.assertEqual(rv.status_code, 200) fetched_objects = rv.json["result"] self.assertEqual(len(fetched_objects), 1) - self.assertEqual(fetched_objects[0]["id"], 1) + self.assertEqual(fetched_objects[0]["id"], dashboard_id) # clean up tagged object tagged_objects.delete() @@ -297,7 +319,12 @@ def test_get_objects_by_tag(self): def test_get_all_objects(self): self.login(username="admin") # tag the dashboard with id 1 - dashboard_id = 1 + dashboard = ( + db.session.query(Dashboard) + .filter(Dashboard.dashboard_title == "World Bank's Data") + .first() + ) + dashboard_id = dashboard.id dashboard_type = ObjectTypes.dashboard tag_names = ["example_tag_1", "example_tag_2"] tags = db.session.query(Tag).filter(Tag.name.in_(tag_names)) diff --git a/tests/integration_tests/tags/commands_tests.py b/tests/integration_tests/tags/commands_tests.py index 43fe4836e0d80..8f44d2ebda0dd 100644 --- a/tests/integration_tests/tags/commands_tests.py +++ b/tests/integration_tests/tags/commands_tests.py @@ -96,7 +96,9 @@ class TestDeleteTagsCommand(SupersetTestCase): @pytest.mark.usefixtures("with_tagging_system_feature") def test_delete_tags_command(self): example_dashboard = ( - db.session.query(Dashboard).filter_by(slug="world_health").one() + db.session.query(Dashboard) + .filter_by(dashboard_title="World Bank's Data") + .one() ) example_tags = ["create custom tag example 1", "create custom tag example 2"] command = CreateCustomTagCommand( diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py index 054916f764f56..0234b2c8c2dfd 100644 --- a/tests/integration_tests/tags/dao_tests.py +++ b/tests/integration_tests/tags/dao_tests.py @@ -74,7 +74,10 @@ def insert_tagged_object( def create_tags(self): with self.create_app().app_context(): # clear tags table - tags = db.session.query(Tag).delete() + tags = db.session.query(Tag) + for tag in tags: + db.session.delete(tag) + db.session.commit() db.session.commit() tags = [] for cx in range(TAGS_FIXTURE_COUNT): @@ -91,7 +94,10 @@ def create_tags(self): def create_tagged_objects(self): with self.create_app().app_context(): # clear tags table - tags = db.session.query(Tag).delete() + tags = db.session.query(Tag) + for tag in tags: + db.session.delete(tag) + db.session.commit() tags = [] for cx in range(TAGS_FIXTURE_COUNT): tags.append( @@ -101,12 +107,18 @@ def create_tagged_objects(self): ) ) # clear tagged objects table - tagged_objects = db.session.query(TaggedObject).delete() + tagged_objects = db.session.query(TaggedObject) + for tagged_obj in tagged_objects: + db.session.delete(tagged_obj) + db.session.commit() tagged_objects = [] + dashboard_id = 1 for tag in tags: tagged_objects.append( self.insert_tagged_object( - object_id=1, object_type=ObjectTypes.dashboard, tag_id=tag.id + object_id=dashboard_id, + object_type=ObjectTypes.dashboard, + tag_id=tag.id, ) ) yield tagged_objects @@ -135,9 +147,20 @@ def test_create_tagged_objects(self): @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") @pytest.mark.usefixtures("with_tagging_system_feature") - @pytest.mark.usefixtures("create_tagged_objects") + @pytest.mark.usefixtures("create_tags") # test get objects from tag def test_get_objects_from_tag(self): + # create tagged objects + dashboard = ( + db.session.query(Dashboard) + .filter(Dashboard.dashboard_title == "World Bank's Data") + .first() + ) + dashboard_id = dashboard.id + tag = db.session.query(Tag).filter_by(name="example_tag_1").one() + self.insert_tagged_object( + object_id=dashboard_id, object_type=ObjectTypes.dashboard, tag_id=tag.id + ) # get objects tagged_objects = TagDAO.get_tagged_objects_for_tags( ["example_tag_1", "example_tag_2"] @@ -175,7 +198,7 @@ def test_get_objects_from_tag(self): TaggedObject.object_type == ObjectTypes.dashboard, ), ) - .distinct(Slice.id) + .distinct(Dashboard.id) .count() + num_charts ) From 876cf4da0f33f865bb2bcef5c50564b5237721a0 Mon Sep 17 00:00:00 2001 From: cccs-RyanK Date: Mon, 13 Feb 2023 11:22:34 -0500 Subject: [PATCH 75/76] post merge fixes --- superset-frontend/src/pages/ChartList/index.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx index e7e9239afb371..e02d848a5dcfd 100644 --- a/superset-frontend/src/pages/ChartList/index.tsx +++ b/superset-frontend/src/pages/ChartList/index.tsx @@ -151,11 +151,6 @@ interface ChartListProps { }; } -type ChartLinkedDashboard = { - id: number; - dashboard_title: string; -}; - const StyledActions = styled.div` color: ${({ theme }) => theme.colors.grayscale.base}; `; From c1080fcd62232541e3c0da205057dc8e5e7ac0fd Mon Sep 17 00:00:00 2001 From: cccs-RyanK Date: Thu, 16 Feb 2023 07:52:06 -0500 Subject: [PATCH 76/76] small frontend changes --- .../src/explore/components/PropertiesModal/index.tsx | 2 +- superset-frontend/src/tags.ts | 3 +-- .../src/views/CRUD/dashboard/DashboardList.tsx | 8 +++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index cc15be0b6c224..9465fc87b9184 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -422,7 +422,7 @@ function PropertiesModal({ {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && ( -

{t('Tags')}

+

{t('Tags')}

)} {isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && ( diff --git a/superset-frontend/src/tags.ts b/superset-frontend/src/tags.ts index 2cb6e27412697..ff0b8f3a339d3 100644 --- a/superset-frontend/src/tags.ts +++ b/superset-frontend/src/tags.ts @@ -50,8 +50,7 @@ export function fetchAllTags( callback: (json: JsonObject) => void, error: (response: Response) => void, ) { - const url = `/api/v1/tag`; - SupersetClient.get({ endpoint: url }) + SupersetClient.get({ endpoint: `/api/v1/tag` }) .then(({ json }) => callback(json)) .catch(response => error(response)); } diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index fa90465345bd9..d26900a29d3ff 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -366,7 +366,13 @@ function DashboardList(props: DashboardListProps) { row: { original: { tags = [] }, }, - }: any) => ( + }: { + row: { + original: { + tags: Tag[]; + }; + }; + }) => ( // Only show custom type tags