Skip to content

Commit

Permalink
feat: Tags ListView Page (apache#24964)
Browse files Browse the repository at this point in the history
  • Loading branch information
hughhhh authored Sep 12, 2023
1 parent baf713a commit 55ac01b
Show file tree
Hide file tree
Showing 31 changed files with 604 additions and 129 deletions.
40 changes: 37 additions & 3 deletions superset-frontend/src/components/ListView/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
* under the License.
*/
import { t, styled } from '@superset-ui/core';
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import Alert from 'src/components/Alert';
import cx from 'classnames';
import Button from 'src/components/Button';
import Icons from 'src/components/Icons';
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
import Pagination from 'src/components/Pagination';
import TableCollection from 'src/components/TableCollection';
import BulkTagModal from 'src/features/tags/BulkTagModal';
import CardCollection from './CardCollection';
import FilterControls from './Filters';
import { CardSortSelect } from './CardSortSelect';
Expand Down Expand Up @@ -98,7 +99,7 @@ const BulkSelectWrapper = styled(Alert)`
padding: ${theme.gridUnit * 2}px 0;
}
.deselect-all {
.deselect-all, .tag-btn {
color: ${theme.colors.primary.base};
margin-left: ${theme.gridUnit * 4}px;
}
Expand Down Expand Up @@ -207,6 +208,9 @@ export interface ListViewProps<T extends object = any> {
count: number;
pageSize: number;
fetchData: (conf: FetchDataConfig) => any;
refreshData: () => void;
addSuccessToast: (msg: string) => void;
addDangerToast: (msg: string) => void;
loading: boolean;
className?: string;
initialSort?: SortColumn[];
Expand All @@ -227,6 +231,8 @@ export interface ListViewProps<T extends object = any> {
showThumbnails?: boolean;
emptyState?: EmptyStateProps;
columnsForWrapText?: string[];
enableBulkTag?: boolean;
bulkTagResourceName?: string;
}

function ListView<T extends object = any>({
Expand All @@ -235,6 +241,7 @@ function ListView<T extends object = any>({
count,
pageSize: initialPageSize,
fetchData,
refreshData,
loading,
initialSort = [],
className = '',
Expand All @@ -250,6 +257,10 @@ function ListView<T extends object = any>({
highlightRowId,
emptyState,
columnsForWrapText,
enableBulkTag = false,
bulkTagResourceName,
addSuccessToast,
addDangerToast,
}: ListViewProps<T>) {
const {
getTableProps,
Expand Down Expand Up @@ -278,6 +289,7 @@ function ListView<T extends object = any>({
renderCard: Boolean(renderCard),
defaultViewMode,
});
const allowBulkTagActions = bulkTagResourceName && enableBulkTag;
const filterable = Boolean(filters.length);
if (filterable) {
const columnAccessors = columns.reduce(
Expand All @@ -302,6 +314,7 @@ function ListView<T extends object = any>({
}, [query.filters]);

const cardViewEnabled = Boolean(renderCard);
const [showBulkTagModal, setShowBulkTagModal] = useState<boolean>(false);

useEffect(() => {
// discard selections if bulk select is disabled
Expand All @@ -310,6 +323,17 @@ function ListView<T extends object = any>({

return (
<ListViewStyles>
{allowBulkTagActions && (
<BulkTagModal
show={showBulkTagModal}
selected={selectedFlatRows}
refreshData={refreshData}
resourceName={bulkTagResourceName}
addSuccessToast={addSuccessToast}
addDangerToast={addDangerToast}
onHide={() => setShowBulkTagModal(false)}
/>
)}
<div data-test={className} className={`superset-list-view ${className}`}>
<div className="header">
{cardViewEnabled && (
Expand Down Expand Up @@ -375,6 +399,17 @@ function ListView<T extends object = any>({
{action.name}
</Button>
))}
{enableBulkTag && (
<span
data-test="bulk-select-tag-btn"
role="button"
tabIndex={0}
className="tag-btn"
onClick={() => setShowBulkTagModal(true)}
>
{t('Add Tag')}
</span>
)}
</>
)}
</>
Expand Down Expand Up @@ -425,7 +460,6 @@ function ListView<T extends object = any>({
)}
</div>
</div>

{rows.length > 0 && (
<div className="pagination-container">
<Pagination
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ const config = (contentType: ContentType) => {
tooltip: (
<div>
<Info header={t('Created by')} text={contentType.createdBy} />
<Info header={t('Owners')} text={contentType.owners} />
{!!contentType.owners && (
<Info header={t('Owners')} text={contentType.owners} />
)}
<Info header={t('Created on')} text={contentType.createdOn} />
</div>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export type LastModified = {
export type Owner = {
type: MetadataType.OWNER;
createdBy: string;
owners: string[];
owners?: string[];
createdOn: string;
onClick?: (type: string) => void;
};
Expand Down
40 changes: 22 additions & 18 deletions superset-frontend/src/components/PageHeaderWithActions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export type PageHeaderWithActionsProps = {
showTitlePanelItems: boolean;
certificatiedBadgeProps?: CertifiedBadgeProps;
showFaveStar: boolean;
showMenuDropdown?: boolean;
faveStarProps: FaveStarProps;
titlePanelAdditionalItems: ReactNode;
rightPanelAdditionalItems: ReactNode;
Expand All @@ -129,6 +130,7 @@ export const PageHeaderWithActions = ({
rightPanelAdditionalItems,
additionalActionsMenu,
menuDropdownProps,
showMenuDropdown = true,
tooltipProps,
}: PageHeaderWithActionsProps) => {
const theme = useTheme();
Expand All @@ -149,25 +151,27 @@ export const PageHeaderWithActions = ({
<div className="right-button-panel">
{rightPanelAdditionalItems}
<div css={additionalActionsContainerStyles}>
<AntdDropdown
trigger={['click']}
overlay={additionalActionsMenu}
{...menuDropdownProps}
>
<Button
css={menuTriggerStyles}
buttonStyle="tertiary"
aria-label={t('Menu actions trigger')}
tooltip={tooltipProps?.text}
placement={tooltipProps?.placement}
data-test="actions-trigger"
{showMenuDropdown && (
<AntdDropdown
trigger={['click']}
overlay={additionalActionsMenu}
{...menuDropdownProps}
>
<Icons.MoreHoriz
iconColor={theme.colors.primary.dark2}
iconSize="l"
/>
</Button>
</AntdDropdown>
<Button
css={menuTriggerStyles}
buttonStyle="tertiary"
aria-label={t('Menu actions trigger')}
tooltip={tooltipProps?.text}
placement={tooltipProps?.placement}
data-test="actions-trigger"
>
<Icons.MoreHoriz
iconColor={theme.colors.primary.dark2}
iconSize="l"
/>
</Button>
</AntdDropdown>
)}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion superset-frontend/src/components/Tags/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const Tag = ({
<StyledTag role="link" key={id} onClick={onClick}>
{id ? (
<a
href={`/superset/all_entities/?tags=${name}`}
href={`/superset/all_entities/?id=${id}`}
target="_blank"
rel="noreferrer"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const createProps = () => ({
maxUndoHistoryToast: jest.fn(),
dashboardInfoChanged: jest.fn(),
dashboardTitleChanged: jest.fn(),
showMenuDropdown: true,
});
const props = createProps();
const editableProps = {
Expand Down
118 changes: 118 additions & 0 deletions superset-frontend/src/features/tags/BulkTagModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* 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 { t, SupersetClient } from '@superset-ui/core';
import { FormLabel } from 'src/components/Form';
import Modal from 'src/components/Modal';
import AsyncSelect from 'src/components/Select/AsyncSelect';
import Button from 'src/components/Button';
import { loadTags } from 'src/components/Tags/utils';
import { TaggableResourceOption } from 'src/features/tags/TagModal';

interface BulkTagModalProps {
onHide: () => void;
refreshData: () => void;
addSuccessToast: (msg: string) => void;
addDangerToast: (msg: string) => void;
show: boolean;
selected: any[];
resourceName: string;
}

const BulkTagModal: React.FC<BulkTagModalProps> = ({
show,
selected = [],
onHide,
refreshData,
resourceName,
addSuccessToast,
addDangerToast,
}) => {
useEffect(() => {}, []);

const onSave = async () => {
await SupersetClient.post({
endpoint: `/api/v1/tag/bulk_create`,
jsonPayload: {
tags: tags.map(tag => tag.value),
objects_to_tag: selected.map(item => [resourceName, +item.original.id]),
},
})
.then(({ json = {} }) => {
addSuccessToast(t('Tagged %s items', selected.length));
})
.catch(err => {
addDangerToast(t('Failed to tag items'));
});

refreshData();
onHide();
setTags([]);
};

const [tags, setTags] = useState<TaggableResourceOption[]>([]);

return (
<Modal
title={t('Bulk tag')}
show={show}
onHide={() => {
setTags([]);
onHide();
}}
footer={
<div>
<Button
data-test="modal-save-dashboard-button"
buttonStyle="secondary"
onClick={onHide}
>
{t('Cancel')}
</Button>
<Button
data-test="modal-save-dashboard-button"
buttonStyle="primary"
onClick={onSave}
>
{t('Save')}
</Button>
</div>
}
>
<>
<>{t('You are adding tags to the %s entities', selected.length)}</>
<br />
<FormLabel>{t('tags')}</FormLabel>
<AsyncSelect
ariaLabel="tags"
// @ts-ignore
value={tags}
options={loadTags}
onHide={onHide}
// @ts-ignore
onChange={tags => setTags(tags)}
placeholder={t('Select Tags')}
mode="multiple"
/>
</>
</Modal>
);
};

export default BulkTagModal;
28 changes: 27 additions & 1 deletion superset-frontend/src/features/tags/TagModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/**
* 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 TagModal from 'src/features/tags/TagModal';
Expand Down Expand Up @@ -36,7 +54,15 @@ test('renders correctly in edit mode', () => {
description: 'A test tag',
type: 'dashboard',
changed_on_delta_humanized: '',
created_by: {},
created_on_delta_humanized: '',
created_by: {
first_name: 'joe',
last_name: 'smith',
},
changed_by: {
first_name: 'tom',
last_name: 'brown',
},
};

render(<TagModal {...mockedProps} editTag={editTag} />);
Expand Down
Loading

0 comments on commit 55ac01b

Please sign in to comment.