fix(ui/navBar): Fix logic to display manage tags link (#13564)

Co-authored-by: v-tarasevich-blitz-brain <v.tarasevich@blitz-brain.com>
This commit is contained in:
Andrew Sikowitz 2025-07-03 13:53:02 -07:00 committed by GitHub
parent ae234d671e
commit 416c2093d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 258 additions and 29 deletions

View File

@ -50,7 +50,9 @@ const ButtonsContainer = styled.div`
export interface ModalButton extends ButtonProps {
text: string;
key?: string;
onClick: () => void;
buttonDataTestId?: string;
}
export interface ModalProps {
@ -78,6 +80,7 @@ export function Modal({
onCancel={onCancel}
closeIcon={<Icon icon="X" source="phosphor" />}
hasChildren={!!children}
data-testid={dataTestId}
title={
<HeaderContainer hasChildren={!!children}>
<Heading type="h1" color="gray" colorLevel={600} weight="bold" size="lg">
@ -93,10 +96,10 @@ export function Modal({
footer={
!!buttons.length && (
<ButtonsContainer>
{buttons.map(({ text, variant, onClick, ...buttonProps }, index) => (
{buttons.map(({ text, variant, onClick, key, buttonDataTestId, ...buttonProps }, index) => (
<Button
key={text}
data-testid={dataTestId && `${dataTestId}-${variant}-${index}`}
key={key || text}
data-testid={buttonDataTestId ?? (dataTestId && `${dataTestId}-${variant}-${index}`)}
variant={variant}
onClick={onClick}
{...buttonProps}

View File

@ -94,6 +94,9 @@ export const NavSidebar = () => {
const showStructuredProperties =
config?.featureFlags?.showManageStructuredProperties &&
(me.platformPrivileges?.manageStructuredProperties || me.platformPrivileges?.viewStructuredPropertiesPage);
const showManageTags =
config?.featureFlags?.showManageTags &&
(me.platformPrivileges?.manageTags || me.platformPrivileges?.viewManageTags);
const showDataSources =
config.managedIngestionConfig.enabled &&
@ -151,6 +154,7 @@ export const NavSidebar = () => {
icon: <Tag />,
selectedIcon: <Tag weight="fill" />,
link: PageRoutes.MANAGE_TAGS,
isHidden: !showManageTags,
},
{
type: NavBarMenuItemTypes.Item,

View File

@ -66,7 +66,7 @@ export const NoPageFound = () => {
<Number>4</Number>
</NumberContainer>
</PageNotFoundTextContainer>
<SubTitle>The page your requested was not found,</SubTitle>
<SubTitle>The page you requested was not found,</SubTitle>
<Button onClick={goToHomepage}>Back to Home</Button>
</PageNotFoundContainer>
</MainContainer>

View File

@ -2,10 +2,11 @@ import { Modal } from '@components';
import { message } from 'antd';
import React, { useState } from 'react';
import { ModalButton } from '@components/components/Modal/Modal';
import { useEnterKeyListener } from '@app/shared/useEnterKeyListener';
import OwnersSection, { PendingOwner } from '@app/sharedV2/owners/OwnersSection';
import TagDetailsSection from '@app/tags/CreateNewTagModal/TagDetailsSection';
import { ModalButton } from '@app/tags/CreateNewTagModal/types';
import { useBatchAddOwnersMutation, useSetTagColorMutation } from '@graphql/mutations.generated';
import { useCreateTagMutation } from '@graphql/tag.generated';
@ -120,6 +121,7 @@ const CreateNewTagModal: React.FC<CreateNewTagModalProps> = ({ onClose, open })
color: 'violet',
variant: 'text',
onClick: onClose,
buttonDataTestId: 'create-tag-modal-cancel-button',
},
{
text: 'Create',
@ -129,6 +131,7 @@ const CreateNewTagModal: React.FC<CreateNewTagModalProps> = ({ onClose, open })
onClick: onOk,
disabled: !tagName || isLoading,
isLoading,
buttonDataTestId: 'create-tag-modal-create-button',
},
];

View File

@ -59,6 +59,7 @@ const TagDetailsSection: React.FC<TagDetailsProps> = ({
value={tagName}
setValue={handleTagNameChange}
placeholder="Enter tag name"
data-testid="tag-name-field"
required
/>
</FormSection>
@ -69,6 +70,7 @@ const TagDetailsSection: React.FC<TagDetailsProps> = ({
value={tagDescription}
setValue={handleDescriptionChange}
placeholder="Add a description for your new tag"
data-testid="tag-description-field"
type="textarea"
/>
</FormSection>

View File

@ -1,14 +1,3 @@
// Interface for modal buttons matching the expected ButtonProps
export interface ModalButton {
text: string;
color: 'violet' | 'white' | 'black' | 'green' | 'red' | 'blue' | 'yellow' | 'gray';
variant: 'text' | 'filled' | 'outline';
onClick: () => void;
id?: string;
disabled?: boolean;
isLoading?: boolean;
}
// Common styled components
export const FormSection = {
marginBottom: '16px',

View File

@ -1,8 +1,10 @@
import { ButtonProps, ColorPicker, Input, Modal } from '@components';
import { ColorPicker, Input, Modal } from '@components';
import { message } from 'antd';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { ModalButton } from '@components/components/Modal/Modal';
import OwnersSection from '@app/sharedV2/owners/OwnersSection';
import { useEntityRegistry } from '@src/app/useEntityRegistry';
import {
@ -24,12 +26,6 @@ interface Props {
isModalOpen?: boolean;
}
// Define a compatible interface for modal buttons
interface ModalButton extends ButtonProps {
text: string;
onClick: () => void;
}
// Interface for pending owner
interface PendingOwner {
ownerUrn: string;
@ -239,6 +235,7 @@ const ManageTag = ({ tagUrn, onClose, onSave, isModalOpen = false }: Props) => {
variant: 'filled',
onClick: handleSave,
disabled: !hasChanges(),
buttonDataTestId: 'update-tag-button',
},
];
@ -251,7 +248,15 @@ const ManageTag = ({ tagUrn, onClose, onSave, isModalOpen = false }: Props) => {
const modalTitle = tagDisplayName ? `Edit Tag: ${tagDisplayName}` : 'Edit Tag';
return (
<Modal title={modalTitle} onCancel={onClose} buttons={buttons} open={isModalOpen} centered width={400}>
<Modal
title={modalTitle}
onCancel={onClose}
buttons={buttons}
open={isModalOpen}
centered
width={400}
dataTestId="edit-tag-modal"
>
<div>
<FormSection>
<Input
@ -260,6 +265,7 @@ const ManageTag = ({ tagUrn, onClose, onSave, isModalOpen = false }: Props) => {
setValue={handleDescriptionChange}
placeholder="Tag description"
type="textarea"
data-testid="tag-description-field"
/>
</FormSection>

View File

@ -145,6 +145,7 @@ const ManageTags = () => {
size="md"
color="violet"
icon={{ icon: 'Plus', source: 'phosphor' }}
data-testid="add-tag-button"
>
Create Tag
</Button>

View File

@ -233,6 +233,7 @@ const TagsTable = ({ searchQuery, searchData, loading: propLoading, networkStatu
color: 'red',
variant: 'filled',
onClick: handleDeleteTag,
buttonDataTestId: 'delete-tag-button',
},
]}
>

View File

@ -290,7 +290,7 @@ export const TagActionsColumn = React.memo(
return (
<CardIcons>
<Dropdown menu={{ items }} trigger={['click']} data-testid={`${tagUrn}-actions-dropdown`}>
<Icon icon="MoreVert" size="md" />
<Icon icon="MoreVert" size="md" data-testid={`${tagUrn}-actions`} />
</Dropdown>
</CardIcons>
);

View File

@ -1,6 +1,10 @@
describe("manage tags", () => {
it("Manage Tags Page - Verify search bar placeholder", () => {
beforeEach(() => {
cy.setIsThemeV2Enabled(false);
cy.login();
});
it("Manage Tags Page - Verify search bar placeholder", () => {
cy.visit("/tags");
cy.get('[data-testid="tag-search-input"]').should(
"have.attr",
@ -8,8 +12,8 @@ describe("manage tags", () => {
"Search tags...",
);
});
it("Manage Tags Page - Verify Title, Search, and Results", () => {
cy.login();
cy.visit("/tags");
cy.get('[data-testid="page-title"]').should("contain.text", "Manage Tags");
cy.get('[data-testid="urn:li:tag:Cypress-name"]').should(
@ -22,15 +26,15 @@ describe("manage tags", () => {
"Cypress",
);
});
it("Manage Tags Page - Verify search not exists", () => {
cy.login();
cy.visit("/tags");
cy.get('[data-testid="page-title"]').should("contain.text", "Manage Tags");
cy.get('[data-testid="urn:li:tag:Cypress-name"]').should(
"contain.text",
"Cypress",
);
cy.get('[data-testid="tag-search-input"]').type("test");
cy.get('[data-testid="tag-search-input"]').type("invalidvalue");
cy.get('[data-testid="tags-not-found"]').should(
"contain.text",
"No tags found for your search query",

View File

@ -0,0 +1,53 @@
export default class DatasetHelper {
static openDataset(urn, name) {
cy.goToDataset(urn, name);
}
static assignTag(name) {
cy.get("#entity-profile-tags").within(() => {
cy.clickOptionWithTestId("AddRoundedIcon");
});
cy.getWithTestId("tag-term-modal-input").within(() => {
cy.get("input").focus({ force: true }).type(name);
});
cy.get(`[name="${name}"]`).click();
cy.clickOptionWithTestId("add-tag-term-from-modal-btn");
cy.waitTextVisible("Added Tags!");
}
static ensureTagIsAssigned(name) {
cy.getWithTestId(`tag-${name}`).should("be.visible");
}
static unassignTag(name) {
cy.getWithTestId(`tag-${name}`).within(() => {
cy.get(".ant-tag-close-icon").click();
});
cy.get(".ant-modal-confirm-confirm").within(() => {
cy.get(".ant-btn-primary").click();
});
cy.waitTextVisible("Removed Tag!");
}
static ensureTagIsNotAssigned(name) {
cy.getWithTestId(`tag-${name}`).should("not.exist");
}
static searchByTag(tagName) {
cy.visit(
`/search?filter_tags___false___EQUAL___0=urn%3Ali%3Atag%3A${tagName}&page=1&query=%2A&unionType=0`,
);
}
static ensureEntityIsInSearchResults(urn) {
cy.getWithTestId(`preview-${urn}`).should("be.visible");
}
static ensureEntityIsNotInSearchResults(urn) {
cy.getWithTestId(`preview-${urn}`).should("not.exist");
}
}

View File

@ -0,0 +1,71 @@
export default class TagsPageHelper {
static openPage() {
cy.visit("/tags");
}
static getTagUrn(name) {
return `urn:li:tag:${name}`;
}
static create(name, description, shouldBeSuccessfullyCreated = true) {
cy.clickOptionWithTestId("add-tag-button");
cy.getWithTestId("tag-name-field").within(() =>
cy.get("input").focus().type(name),
);
cy.getWithTestId("tag-description-field").within(() =>
cy.get("input").focus().type(description),
);
cy.clickOptionWithTestId("create-tag-modal-create-button");
if (shouldBeSuccessfullyCreated) {
cy.waitTextVisible(`Tag "${name}" successfully created`);
} else {
cy.waitTextVisible("Failed to create tag. An unexpected error occurred");
cy.clickOptionWithTestId("create-tag-modal-cancel-button");
}
}
static remove(name) {
cy.getWithTestId("tag-search-input").focus().type(name, { delay: 0 });
cy.clickOptionWithTestId(`${TagsPageHelper.getTagUrn(name)}-actions`);
cy.clickOptionWithTestId("action-delete");
cy.clickOptionWithTestId("delete-tag-button");
cy.getWithTestId("tag-search-input").clear();
}
static edit(name, newDescription) {
cy.getWithTestId("tag-search-input").focus().type(name, { delay: 0 });
cy.clickOptionWithTestId(`${TagsPageHelper.getTagUrn(name)}-actions`);
cy.clickOptionWithTestId("action-edit");
cy.getWithTestId("edit-tag-modal").within(() => {
cy.getWithTestId("tag-description-field").within(() =>
cy.get("input").focus().clear().type(newDescription),
);
});
cy.clickOptionWithTestId("update-tag-button");
cy.getWithTestId("tag-search-input").clear();
}
static ensureTagIsInTable(name, description) {
cy.getWithTestId("tag-search-input").focus().type(name, { delay: 0 });
cy.getWithTestId(`${TagsPageHelper.getTagUrn(name)}-name`).should(
"contain",
name,
);
cy.getWithTestId(`${TagsPageHelper.getTagUrn(name)}-description`).should(
"contain",
description,
);
cy.getWithTestId("tag-search-input").clear();
}
static ensureTagIsNotInTable(name) {
cy.getWithTestId("tag-search-input").focus().type(name, { delay: 0 });
cy.getWithTestId(`${TagsPageHelper.getTagUrn(name)}-name`).should(
"not.exist",
);
cy.getWithTestId("tag-search-input").clear();
}
}

View File

@ -0,0 +1,92 @@
import DatasetHelper from "./helpers/dataset_helper";
import TagsPageHelper from "./helpers/tags_page_helper";
const test_id = `manage_tagsV2_${new Date().getTime()}`;
const SAMPLE_DATASET_URN =
"urn:li:dataset:(urn:li:dataPlatform:hive,SampleCypressHiveDataset,PROD)";
const SAMPLE_DATASET_NAME = "SampleCypressHiveDataset";
describe("tags", () => {
beforeEach(() => {
cy.setIsThemeV2Enabled(true);
cy.login();
});
it("verify search bar placeholder", () => {
cy.visit("/tags");
cy.get('[data-testid="tag-search-input"]').should(
"have.attr",
"placeholder",
"Search tags...",
);
});
it("verify title, search, and results", () => {
cy.visit("/tags");
cy.get('[data-testid="page-title"]').should("contain.text", "Manage Tags");
cy.get('[data-testid="urn:li:tag:Cypress-name"]').should(
"contain.text",
"Cypress",
);
cy.get('[data-testid="tag-search-input"]').type("Cypress");
cy.get('[data-testid="urn:li:tag:Cypress-name"]').should(
"contain.text",
"Cypress",
);
});
it("verify search not exists", () => {
cy.visit("/tags");
cy.get('[data-testid="page-title"]').should("contain.text", "Manage Tags");
cy.get('[data-testid="urn:li:tag:Cypress-name"]').should(
"contain.text",
"Cypress",
);
cy.get('[data-testid="tag-search-input"]').type("invalidvalue");
cy.get('[data-testid="tags-not-found"]').should(
"contain.text",
"No tags found for your search query",
);
});
it("should allow to create/edit/remove tags on tags page", () => {
const tagName = `tag_${test_id}_tags_page`;
const tagDescription = `${tagName} description`;
TagsPageHelper.openPage();
TagsPageHelper.create(tagName, tagDescription);
TagsPageHelper.ensureTagIsInTable(tagName, tagDescription);
// ensure that we can't to create tag with the same name
TagsPageHelper.create(tagName, tagDescription, false);
TagsPageHelper.edit(tagName, `${tagDescription} edited`);
TagsPageHelper.ensureTagIsInTable(tagName, `${tagDescription} edited`);
TagsPageHelper.remove(tagName);
TagsPageHelper.ensureTagIsNotInTable(tagName);
});
it("should allow to assign/unassign tags on a dataset", () => {
const tagName = `tag_${test_id}_dataset`;
const tagDescription = `${tagName} description`;
TagsPageHelper.openPage();
TagsPageHelper.create(tagName, tagDescription);
DatasetHelper.openDataset(SAMPLE_DATASET_URN, SAMPLE_DATASET_NAME);
DatasetHelper.assignTag(tagName);
DatasetHelper.ensureTagIsAssigned(tagName);
DatasetHelper.searchByTag(tagName);
DatasetHelper.ensureEntityIsInSearchResults(SAMPLE_DATASET_URN);
DatasetHelper.openDataset(SAMPLE_DATASET_URN, SAMPLE_DATASET_NAME);
DatasetHelper.unassignTag(tagName);
DatasetHelper.ensureTagIsNotAssigned(tagName);
DatasetHelper.searchByTag(tagName);
DatasetHelper.ensureEntityIsNotInSearchResults(SAMPLE_DATASET_URN);
TagsPageHelper.openPage();
TagsPageHelper.remove(tagName);
});
});