Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore(tags): Handle tagging as part of asset update call #28570

Merged
merged 2 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions superset-frontend/src/components/Tags/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ import { tagToSelectOption } from 'src/components/Tags/utils';
describe('tagToSelectOption', () => {
test('converts a Tag object with table_name to a SelectTagsValue', () => {
const tag = {
id: '1',
id: 1,
name: 'TagName',
table_name: 'Table1',
};

const expectedSelectTagsValue = {
value: 'TagName',
value: 1,
label: 'TagName',
key: '1',
key: 1,
};

expect(tagToSelectOption(tag)).toEqual(expectedSelectTagsValue);
Expand Down
14 changes: 7 additions & 7 deletions superset-frontend/src/components/Tags/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ const cachedSupersetGet = cacheWrapper(
);

type SelectTagsValue = {
value: string | number | undefined;
label: string;
key: string | number | undefined;
value: number | undefined;
label: string | undefined;
key: number | undefined;
};

export const tagToSelectOption = (
item: Tag & { table_name: string },
tag: Tag & { table_name: string },
): SelectTagsValue => ({
value: item.name,
label: item.name,
key: item.id,
value: tag.id,
label: tag.name,
key: tag.id,
});

export const loadTags = async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
import withToasts from 'src/components/MessageToasts/withToasts';
import TagType from 'src/types/TagType';
import {
addTag,
deleteTaggedObjects,
fetchTags,
OBJECT_TYPES,
} from 'src/features/tags/tags';
import { fetchTags, OBJECT_TYPES } from 'src/features/tags/tags';
import { loadTags } from 'src/components/Tags/utils';

const StyledFormItem = styled(FormItem)`
Expand Down Expand Up @@ -115,10 +110,9 @@
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();

const tagsAsSelectValues = useMemo(() => {
const selectTags = tags.map(tag => ({
value: tag.name,
const selectTags = tags.map((tag: { id: number; name: string }) => ({
value: tag.id,
label: tag.name,
key: tag.name,
}));
return selectTags;
}, [tags.length]);
Expand Down Expand Up @@ -309,41 +303,6 @@
setColorScheme(colorScheme);
};

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 onFinish = () => {
const { title, slug, certifiedBy, certificationDetails } =
form.getFieldsValue();
Expand Down Expand Up @@ -401,31 +360,16 @@
updateMetadata: false,
});

if (isFeatureEnabled(FeatureFlag.TaggingSystem)) {
// update tags
try {
fetchTags(
{
objectType: OBJECT_TYPES.DASHBOARD,
objectId: dashboardId,
includeTypes: false,
},
(currentTags: TagType[]) => updateTags(currentTags, tags),
error => {
handleErrorResponse(error);
},
);
} catch (error) {
handleErrorResponse(error);
}
}

const moreOnSubmitProps: { roles?: Roles } = {};
const morePutProps: { roles?: number[] } = {};
const morePutProps: { roles?: number[]; tags?: (number | undefined)[] } =
{};
if (isFeatureEnabled(FeatureFlag.DashboardRbac)) {
moreOnSubmitProps.roles = roles;
morePutProps.roles = (roles || []).map(r => r.id);
}
if (isFeatureEnabled(FeatureFlag.TaggingSystem)) {
morePutProps.tags = tags.map(tag => tag.id);

Check warning on line 371 in superset-frontend/src/dashboard/components/PropertiesModal/index.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/dashboard/components/PropertiesModal/index.tsx#L371

Added line #L371 was not covered by tests
}
const onSubmitProps = {
id: dashboardId,
title,
Expand Down Expand Up @@ -621,12 +565,12 @@
}
}, [dashboardId]);

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 }))]);
const handleChangeTags = (tags: { label: string; value: number }[]) => {
const parsedTags: TagType[] = ensureIsArray(tags).map(r => ({

Check warning on line 569 in superset-frontend/src/dashboard/components/PropertiesModal/index.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/dashboard/components/PropertiesModal/index.tsx#L569

Added line #L569 was not covered by tests
id: r.value,
name: r.label,
}));
setTags(parsedTags);

Check warning on line 573 in superset-frontend/src/dashboard/components/PropertiesModal/index.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/dashboard/components/PropertiesModal/index.tsx#L573

Added line #L573 was not covered by tests
};

return (
Expand Down
77 changes: 11 additions & 66 deletions superset-frontend/src/explore/components/PropertiesModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,12 @@
isFeatureEnabled,
FeatureFlag,
getClientErrorObject,
ensureIsArray,
} from '@superset-ui/core';
import Chart, { Slice } from 'src/types/Chart';
import withToasts from 'src/components/MessageToasts/withToasts';
import { loadTags } from 'src/components/Tags/utils';
import {
addTag,
deleteTaggedObjects,
fetchTags,
OBJECT_TYPES,
} from 'src/features/tags/tags';
import { fetchTags, OBJECT_TYPES } from 'src/features/tags/tags';
import TagType from 'src/types/TagType';

export type PropertiesModalProps = {
Expand Down Expand Up @@ -80,10 +76,9 @@
const [tags, setTags] = useState<TagType[]>([]);

const tagsAsSelectValues = useMemo(() => {
const selectTags = tags.map(tag => ({
value: tag.name,
const selectTags = tags.map((tag: { id: number; name: string }) => ({
value: tag.id,
label: tag.name,
key: tag.name,
}));
return selectTags;
}, [tags.length]);
Expand Down Expand Up @@ -144,41 +139,6 @@
[],
);

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;
Expand Down Expand Up @@ -209,22 +169,7 @@
).map(o => o.value);
}
if (isFeatureEnabled(FeatureFlag.TaggingSystem)) {
// update tags
try {
fetchTags(
{
objectType: OBJECT_TYPES.CHART,
objectId: slice.slice_id,
includeTypes: false,
},
(currentTags: TagType[]) => updateTags(currentTags, tags),
error => {
showError(error);
},
);
} catch (error) {
showError(error);
}
payload.tags = tags.map(tag => tag.id);

Check warning on line 172 in superset-frontend/src/explore/components/PropertiesModal/index.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/explore/components/PropertiesModal/index.tsx#L172

Added line #L172 was not covered by tests
}

try {
Expand Down Expand Up @@ -282,12 +227,12 @@
}
}, [slice.slice_id]);

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 }))]);
const handleChangeTags = (tags: { label: string; value: number }[]) => {
const parsedTags: TagType[] = ensureIsArray(tags).map(r => ({

Check warning on line 231 in superset-frontend/src/explore/components/PropertiesModal/index.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/explore/components/PropertiesModal/index.tsx#L231

Added line #L231 was not covered by tests
id: r.value,
name: r.label,
}));
setTags(parsedTags);

Check warning on line 235 in superset-frontend/src/explore/components/PropertiesModal/index.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/explore/components/PropertiesModal/index.tsx#L235

Added line #L235 was not covered by tests
};

const handleClearTags = () => {
Expand Down
2 changes: 1 addition & 1 deletion superset-frontend/src/types/TagType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { MouseEventHandler } from 'react';

export interface TagType {
id?: string | number;
id?: number;
type?: string | number;
editable?: boolean;
onDelete?: (index: number) => void;
Expand Down
4 changes: 3 additions & 1 deletion superset/charts/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
from superset.commands.chart.importers.dispatcher import ImportChartsCommand
from superset.commands.chart.update import UpdateChartCommand
from superset.commands.chart.warm_up_cache import ChartWarmUpCacheCommand
from superset.commands.exceptions import CommandException
from superset.commands.exceptions import CommandException, TagForbiddenError
from superset.commands.importers.exceptions import (
IncorrectFormatError,
NoValidFilesFoundError,
Expand Down Expand Up @@ -404,6 +404,8 @@ def put(self, pk: int) -> Response:
response = self.response_404()
except ChartForbiddenError:
response = self.response_403()
except TagForbiddenError as ex:
response = self.response(403, message=str(ex))
except ChartInvalidError as ex:
response = self.response_422(message=ex.normalized_messages())
except ChartUpdateFailedError as ex:
Expand Down
10 changes: 2 additions & 8 deletions superset/charts/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from superset import app
from superset.common.chart_data import ChartDataResultFormat, ChartDataResultType
from superset.db_engine_specs.base import builtin_time_grains
from superset.tags.models import TagType
from superset.utils import pandas_postprocessing, schema as utils
from superset.utils.core import (
AnnotationType,
Expand Down Expand Up @@ -122,6 +121,7 @@
owners_name_description = "Name of an owner of the chart."
certified_by_description = "Person or group that has certified this chart"
certification_details_description = "Details of the certification"
tags_description = "Tags to be associated with the chart"

openapi_spec_methods_override = {
"get": {"get": {"summary": "Get a chart detail information"}},
Expand All @@ -143,12 +143,6 @@
}


class TagSchema(Schema):
id = fields.Int()
name = fields.String()
type = fields.Enum(TagType, by_value=True)


class ChartEntityResponseSchema(Schema):
"""
Schema for a chart object
Expand Down Expand Up @@ -284,7 +278,7 @@ class ChartPutSchema(Schema):
)
is_managed_externally = fields.Boolean(allow_none=True, dump_default=False)
external_url = fields.String(allow_none=True)
tags = fields.Nested(TagSchema, many=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we removing the TagSchema reference here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a good catch -- I scanned for un-used imports but didn't realize that TagSchema was actually defined in this same file. I'll remove its definition in a follow up commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll hold on committing this change ☝️ until I get your feedback on the other comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we should prolly centralize TagSchema somewhere it could be used in all the resource update schemas

tags = fields.List(fields.Integer(metadata={"description": tags_description}))


class ChartGetDatasourceObjectDataResponseSchema(Schema):
Expand Down
Loading
Loading