mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-03 20:19:31 +00:00 
			
		
		
		
	Assets filters (#14229)
* assets filter * localisation * update filters * fix: update filters on teams page * hide searchbar & restrict adding assets for draft * fix: code smells * fix: encoding decoding issues * fix: css * fix: add permissions check * fix(ui): throw validation errors while modifying glossary term (#14236) * fix(ui): throw validation errors while modifying glossary term * update modal layout * fix: domain cypress * minor update * update modal * fix: glossary cypress * fix: show inline errors * fix comments --------- Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									0fb03358a9
								
							
						
					
					
						commit
						f32b4d4c47
					
				@ -436,14 +436,8 @@ export const removeAssetsFromDomain = (domainObj) => {
 | 
			
		||||
  domainObj.assets.forEach((asset, index) => {
 | 
			
		||||
    interceptURL('GET', '/api/v1/search/query*', 'searchAssets');
 | 
			
		||||
 | 
			
		||||
    cy.get(
 | 
			
		||||
      `[data-testid="table-data-card_${asset.fullyQualifiedName}"]`
 | 
			
		||||
    ).within(() => {
 | 
			
		||||
      cy.get('.explore-card-actions').invoke('show');
 | 
			
		||||
      cy.get('.explore-card-actions').within(() => {
 | 
			
		||||
        cy.get('[data-testid="delete-tag"]').click();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    cy.get(`[data-testid="manage-button-${asset.fullyQualifiedName}"]`).click();
 | 
			
		||||
    cy.get('[data-testid="delete-button"]').click();
 | 
			
		||||
 | 
			
		||||
    cy.get("[data-testid='save-button']").click();
 | 
			
		||||
 | 
			
		||||
@ -460,9 +454,13 @@ export const removeAssetsFromDomain = (domainObj) => {
 | 
			
		||||
 | 
			
		||||
export const addAssetsToDataProduct = (dataProductObj, domainObj) => {
 | 
			
		||||
  interceptURL('GET', `/api/v1/search/query**`, 'getDataProductAssets');
 | 
			
		||||
  interceptURL('GET', '/api/v1/dataProducts/**', 'getDataProductDetails');
 | 
			
		||||
 | 
			
		||||
  goToDataProductsTab(domainObj);
 | 
			
		||||
  cy.get(`[data-testid="explore-card-${dataProductObj.name}"]`).click();
 | 
			
		||||
  cy.get(
 | 
			
		||||
    `[data-testid="explore-card-${dataProductObj.name}"] [data-testid="entity-link"]`
 | 
			
		||||
  ).click();
 | 
			
		||||
  verifyResponseStatusCode('@getDataProductDetails', 200);
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="assets"]').should('be.visible').click();
 | 
			
		||||
  cy.get('.ant-tabs-tab-active').contains('Assets').should('be.visible');
 | 
			
		||||
@ -501,7 +499,9 @@ export const addAssetsToDataProduct = (dataProductObj, domainObj) => {
 | 
			
		||||
 | 
			
		||||
export const removeAssetsFromDataProduct = (dataProductObj, domainObj) => {
 | 
			
		||||
  goToDataProductsTab(domainObj);
 | 
			
		||||
  cy.get(`[data-testid="explore-card-${dataProductObj.name}"]`).click();
 | 
			
		||||
  cy.get(
 | 
			
		||||
    `[data-testid="explore-card-${dataProductObj.name}"] [data-testid="entity-link"]`
 | 
			
		||||
  ).click();
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="assets"]').should('be.visible').click();
 | 
			
		||||
  cy.get('.ant-tabs-tab-active').contains('Assets').should('be.visible');
 | 
			
		||||
@ -510,15 +510,8 @@ export const removeAssetsFromDataProduct = (dataProductObj, domainObj) => {
 | 
			
		||||
 | 
			
		||||
  dataProductObj.assets.forEach((asset, index) => {
 | 
			
		||||
    interceptURL('GET', '/api/v1/search/query*', 'searchAssets');
 | 
			
		||||
 | 
			
		||||
    cy.get(
 | 
			
		||||
      `[data-testid="table-data-card_${asset.fullyQualifiedName}"]`
 | 
			
		||||
    ).within(() => {
 | 
			
		||||
      cy.get('.explore-card-actions').invoke('show');
 | 
			
		||||
      cy.get('.explore-card-actions').within(() => {
 | 
			
		||||
        cy.get('[data-testid="delete-tag"]').click();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    cy.get(`[data-testid="manage-button-${asset.fullyQualifiedName}"]`).click();
 | 
			
		||||
    cy.get('[data-testid="delete-button"]').click();
 | 
			
		||||
 | 
			
		||||
    cy.get("[data-testid='save-button']").click();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -305,15 +305,63 @@ export const NEW_GLOSSARY = {
 | 
			
		||||
  name: 'Cypress Glossary',
 | 
			
		||||
  description: 'This is the Cypress Glossary',
 | 
			
		||||
  reviewer: 'Aaron Johnson',
 | 
			
		||||
  tag: 'PII.None',
 | 
			
		||||
  addReviewer: true,
 | 
			
		||||
  tag: 'PersonalData.Personal',
 | 
			
		||||
};
 | 
			
		||||
export const NEW_GLOSSARY_1 = {
 | 
			
		||||
  name: 'Cypress Product%Glossary',
 | 
			
		||||
  description: 'This is the Product glossary with percentage',
 | 
			
		||||
  reviewer: 'Brandy Miller',
 | 
			
		||||
  addReviewer: false,
 | 
			
		||||
};
 | 
			
		||||
export const CYPRESS_ASSETS_GLOSSARY = {
 | 
			
		||||
  name: 'Cypress Assets Glossary',
 | 
			
		||||
  description: 'This is the Assets Cypress Glossary',
 | 
			
		||||
  reviewer: '',
 | 
			
		||||
  addReviewer: false,
 | 
			
		||||
  tag: 'PII.None',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const COMMON_ASSETS = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'dim_customer',
 | 
			
		||||
    fullyQualifiedName: 'sample_data.ecommerce_db.shopify.dim_address',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'raw_order',
 | 
			
		||||
    fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_order',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'presto_etl',
 | 
			
		||||
    fullyQualifiedName: 'sample_airflow.presto_etl',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const CYPRESS_ASSETS_GLOSSARY_TERMS = {
 | 
			
		||||
  term_1: {
 | 
			
		||||
    name: 'Cypress%PercentTerm',
 | 
			
		||||
    description: 'This is the Cypress PercentTerm',
 | 
			
		||||
    synonyms: 'buy,collect,acquire',
 | 
			
		||||
    fullyQualifiedName: 'Cypress Assets Glossary.Cypress%PercentTerm',
 | 
			
		||||
    assets: COMMON_ASSETS,
 | 
			
		||||
  },
 | 
			
		||||
  term_2: {
 | 
			
		||||
    name: 'Cypress Space GTerm',
 | 
			
		||||
    description: 'This is the Cypress Sales',
 | 
			
		||||
    synonyms: 'give,disposal,deal',
 | 
			
		||||
    fullyQualifiedName: 'Cypress Assets Glossary.Cypress Space GTerm',
 | 
			
		||||
    assets: COMMON_ASSETS,
 | 
			
		||||
  },
 | 
			
		||||
  term_3: {
 | 
			
		||||
    name: 'Cypress.Dot.GTerm',
 | 
			
		||||
    description: 'This is the Cypress with space',
 | 
			
		||||
    synonyms: 'tea,coffee,water',
 | 
			
		||||
    fullyQualifiedName: 'Cypress Assets Glossary."Cypress.Dot.GTerm"',
 | 
			
		||||
    displayFqn: 'Cypress Assets Glossary."Cypress.Dot.GTerm"',
 | 
			
		||||
    assets: COMMON_ASSETS,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const NEW_GLOSSARY_TERMS = {
 | 
			
		||||
  term_1: {
 | 
			
		||||
    name: 'CypressPurchase',
 | 
			
		||||
@ -332,20 +380,7 @@ export const NEW_GLOSSARY_TERMS = {
 | 
			
		||||
    description: 'This is the Cypress with space',
 | 
			
		||||
    synonyms: 'tea,coffee,water',
 | 
			
		||||
    fullyQualifiedName: 'Cypress Glossary.Cypress Space',
 | 
			
		||||
    assets: [
 | 
			
		||||
      {
 | 
			
		||||
        name: 'dim_customer',
 | 
			
		||||
        fullyQualifiedName: 'sample_data.ecommerce_db.shopify.dim_address',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'raw_order',
 | 
			
		||||
        fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_order',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'presto_etl',
 | 
			
		||||
        fullyQualifiedName: 'sample_airflow.presto_etl',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    assets: COMMON_ASSETS,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
export const GLOSSARY_TERM_WITH_DETAILS = {
 | 
			
		||||
@ -613,3 +648,51 @@ export const DOMAIN_2 = {
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const DOMAIN_3 = {
 | 
			
		||||
  name: 'Cypress Space',
 | 
			
		||||
  updatedName: 'Cypress Space',
 | 
			
		||||
  updatedDisplayName: 'Cypress Space',
 | 
			
		||||
  fullyQualifiedName: 'Cypress Space',
 | 
			
		||||
  description: 'This is the Cypress for testing domain with space creation',
 | 
			
		||||
  experts: 'Alex Pollard',
 | 
			
		||||
  owner: 'Alex Pollard',
 | 
			
		||||
  domainType: 'Source-aligned',
 | 
			
		||||
  dataProducts: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'Cypress%PercentDP',
 | 
			
		||||
      description:
 | 
			
		||||
        'This is the data product description for Cypress DataProduct Assets',
 | 
			
		||||
      experts: 'Aaron Johnson',
 | 
			
		||||
      owner: 'Aaron Johnson',
 | 
			
		||||
      assets: [
 | 
			
		||||
        {
 | 
			
		||||
          name: 'forecast_sales_performance',
 | 
			
		||||
          fullyQualifiedName: 'sample_superset.forecast_sales_performance',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'eta_predictions',
 | 
			
		||||
          fullyQualifiedName: 'mlflow_svc.eta_predictions',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'operations_view',
 | 
			
		||||
          fullyQualifiedName: 'sample_looker.model.operations_view',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  assets: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'forecast_sales_performance',
 | 
			
		||||
      fullyQualifiedName: 'sample_superset.forecast_sales_performance',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'eta_predictions',
 | 
			
		||||
      fullyQualifiedName: 'mlflow_svc.eta_predictions',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'operations_view',
 | 
			
		||||
      fullyQualifiedName: 'sample_looker.model.operations_view',
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import {
 | 
			
		||||
  updateDomainDetails,
 | 
			
		||||
  verifyDomain,
 | 
			
		||||
} from '../../common/DomainUtils';
 | 
			
		||||
import { DOMAIN_1, DOMAIN_2 } from '../../constants/constants';
 | 
			
		||||
import { DOMAIN_1, DOMAIN_2, DOMAIN_3 } from '../../constants/constants';
 | 
			
		||||
 | 
			
		||||
describe('Domain page should work properly', () => {
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
@ -52,6 +52,11 @@ describe('Domain page should work properly', () => {
 | 
			
		||||
    addAssetsToDomain(DOMAIN_2);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Add assets to domain having space using asset selection modal should work properly', () => {
 | 
			
		||||
    createDomain(DOMAIN_3, false);
 | 
			
		||||
    addAssetsToDomain(DOMAIN_3);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Create new data product should work properly', () => {
 | 
			
		||||
    DOMAIN_1.dataProducts.forEach((dataProduct) => {
 | 
			
		||||
      createDataProducts(dataProduct, DOMAIN_1);
 | 
			
		||||
@ -72,6 +77,17 @@ describe('Domain page should work properly', () => {
 | 
			
		||||
    addAssetsToDataProduct(DOMAIN_2.dataProducts[0], DOMAIN_2);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Add data product assets using asset selection modal with separate domain and dp having space', () => {
 | 
			
		||||
    DOMAIN_3.dataProducts.forEach((dp) => {
 | 
			
		||||
      createDataProducts(dp, DOMAIN_3);
 | 
			
		||||
      cy.get('[data-testid="app-bar-item-domain"]')
 | 
			
		||||
        .should('be.visible')
 | 
			
		||||
        .click({ force: true });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    addAssetsToDataProduct(DOMAIN_3.dataProducts[0], DOMAIN_3);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Remove data product assets using asset selection modal should work properly', () => {
 | 
			
		||||
    removeAssetsFromDataProduct(DOMAIN_2.dataProducts[0], DOMAIN_2);
 | 
			
		||||
  });
 | 
			
		||||
@ -97,7 +113,7 @@ describe('Domain page should work properly', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Delete domain flow should work properly', () => {
 | 
			
		||||
    [DOMAIN_1, DOMAIN_2].forEach((domain) => {
 | 
			
		||||
    [DOMAIN_1, DOMAIN_2, DOMAIN_3].forEach((domain) => {
 | 
			
		||||
      deleteDomain(domain);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,8 @@ import {
 | 
			
		||||
} from '../../common/common';
 | 
			
		||||
import { deleteGlossary } from '../../common/GlossaryUtils';
 | 
			
		||||
import {
 | 
			
		||||
  CYPRESS_ASSETS_GLOSSARY,
 | 
			
		||||
  CYPRESS_ASSETS_GLOSSARY_TERMS,
 | 
			
		||||
  DELETE_TERM,
 | 
			
		||||
  INVALID_NAMES,
 | 
			
		||||
  NAME_MAX_LENGTH_VALIDATION_ERROR,
 | 
			
		||||
@ -63,7 +65,7 @@ const visitGlossaryTermPage = (termName, fqn, fetchPermission) => {
 | 
			
		||||
    'waitForTermPermission'
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  cy.get(`[data-row-key="${fqn}"]`)
 | 
			
		||||
  cy.get(`[data-row-key="${Cypress.$.escapeSelector(fqn)}"]`)
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .contains(termName)
 | 
			
		||||
@ -78,6 +80,83 @@ const visitGlossaryTermPage = (termName, fqn, fetchPermission) => {
 | 
			
		||||
  cy.get('.ant-tabs .glossary-overview-tab').should('be.visible').click();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createGlossary = (glossaryData) => {
 | 
			
		||||
  // Intercept API calls
 | 
			
		||||
  interceptURL('POST', '/api/v1/glossaries', 'createGlossary');
 | 
			
		||||
  interceptURL(
 | 
			
		||||
    'GET',
 | 
			
		||||
    '/api/v1/search/query?q=*disabled:false&index=tag_search_index&from=0&size=10&query_filter=%7B%7D',
 | 
			
		||||
    'fetchTags'
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Click on the "Add Glossary" button
 | 
			
		||||
  cy.get('[data-testid="add-glossary"]').click();
 | 
			
		||||
 | 
			
		||||
  // Validate redirection to the add glossary page
 | 
			
		||||
  cy.get('[data-testid="form-heading"]')
 | 
			
		||||
    .contains('Add Glossary')
 | 
			
		||||
    .should('be.visible');
 | 
			
		||||
 | 
			
		||||
  // Perform glossary creation steps
 | 
			
		||||
  cy.get('[data-testid="save-glossary"]')
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .click();
 | 
			
		||||
 | 
			
		||||
  validateForm();
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="name"]')
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .clear()
 | 
			
		||||
    .type(glossaryData.name);
 | 
			
		||||
 | 
			
		||||
  cy.get(descriptionBox)
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .type(glossaryData.description);
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="mutually-exclusive-button"]').scrollIntoView().click();
 | 
			
		||||
 | 
			
		||||
  if (glossaryData.tag) {
 | 
			
		||||
    // Add tag
 | 
			
		||||
    cy.get('[data-testid="tag-selector"] .ant-select-selection-overflow')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .type(glossaryData.tag);
 | 
			
		||||
 | 
			
		||||
    verifyResponseStatusCode('@fetchTags', 200);
 | 
			
		||||
    cy.get(`[data-testid="tag-${glossaryData.tag}"]`).click();
 | 
			
		||||
    cy.get('[data-testid="right-panel"]').click();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (glossaryData.addReviewer) {
 | 
			
		||||
    // Add reviewer
 | 
			
		||||
    cy.get('[data-testid="add-reviewers"]').scrollIntoView().click();
 | 
			
		||||
    cy.get('[data-testid="searchbar"]').type(CREDENTIALS.username);
 | 
			
		||||
    cy.get(`[title="${CREDENTIALS.username}"]`)
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
    cy.get('[data-testid="selectable-list-update-btn"]')
 | 
			
		||||
      .should('exist')
 | 
			
		||||
      .and('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="save-glossary"]')
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .click();
 | 
			
		||||
 | 
			
		||||
  cy.wait('@createGlossary').then(({ request }) => {
 | 
			
		||||
    expect(request.body.name).equals(glossaryData.name);
 | 
			
		||||
    expect(request.body.description).equals(glossaryData.description);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  cy.url().should('include', '/glossary/');
 | 
			
		||||
  checkDisplayName(glossaryData.name);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const checkDisplayName = (displayName) => {
 | 
			
		||||
  cy.get('[data-testid="entity-header-display-name"]')
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
@ -89,7 +168,7 @@ const checkDisplayName = (displayName) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const checkAssetsCount = (assetsCount) => {
 | 
			
		||||
  cy.get('[data-testid="assets"] [data-testid="count"]')
 | 
			
		||||
  cy.get('[data-testid="assets"] [data-testid="filter-count"]')
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('have.text', assetsCount);
 | 
			
		||||
};
 | 
			
		||||
@ -183,6 +262,63 @@ const fillGlossaryTermDetails = (term, glossary, isMutually = false) => {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addAssetToGlossaryTerm = (glossaryTerm, glossary) => {
 | 
			
		||||
  goToGlossaryPage();
 | 
			
		||||
  selectActiveGlossary(glossary.name);
 | 
			
		||||
  goToAssetsTab(glossaryTerm.name, glossaryTerm.fullyQualifiedName, true);
 | 
			
		||||
 | 
			
		||||
  checkAssetsCount(0);
 | 
			
		||||
  cy.contains('Adding a new Asset is easy, just give it a spin!').should(
 | 
			
		||||
    'be.visible'
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="glossary-term-add-button-menu"]').click();
 | 
			
		||||
  cy.get('.ant-dropdown-menu .ant-dropdown-menu-title-content')
 | 
			
		||||
    .contains('Assets')
 | 
			
		||||
    .click();
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="asset-selection-modal"] .ant-modal-title').should(
 | 
			
		||||
    'contain',
 | 
			
		||||
    'Add Assets'
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  glossaryTerm.assets.forEach((asset) => {
 | 
			
		||||
    interceptURL('GET', '/api/v1/search/query*', 'searchAssets');
 | 
			
		||||
    cy.get('[data-testid="asset-selection-modal"] [data-testid="searchbar"]')
 | 
			
		||||
      .click()
 | 
			
		||||
      .clear()
 | 
			
		||||
      .type(asset.name);
 | 
			
		||||
 | 
			
		||||
    verifyResponseStatusCode('@searchAssets', 200);
 | 
			
		||||
 | 
			
		||||
    cy.get(
 | 
			
		||||
      `[data-testid="table-data-card_${asset.fullyQualifiedName}"] input[type="checkbox"]`
 | 
			
		||||
    ).click();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  cy.get('[data-testid="save-btn"]').click();
 | 
			
		||||
  checkAssetsCount(glossaryTerm.assets.length);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeAssetsFromGlossaryTerm = (glossaryTerm, glossary) => {
 | 
			
		||||
  goToGlossaryPage();
 | 
			
		||||
  selectActiveGlossary(glossary.name);
 | 
			
		||||
  goToAssetsTab(glossaryTerm.name, glossaryTerm.fullyQualifiedName, true);
 | 
			
		||||
  checkAssetsCount(glossaryTerm.assets.length);
 | 
			
		||||
  glossaryTerm.assets.forEach((asset, index) => {
 | 
			
		||||
    interceptURL('GET', '/api/v1/search/query*', 'searchAssets');
 | 
			
		||||
    cy.get(`[data-testid="manage-button-${asset.fullyQualifiedName}"]`).click();
 | 
			
		||||
    cy.get('[data-testid="delete-button"]').click();
 | 
			
		||||
    cy.get("[data-testid='save-button']").click();
 | 
			
		||||
 | 
			
		||||
    interceptURL('GET', '/api/v1/search/query*', 'assetTab');
 | 
			
		||||
    // go assets tab
 | 
			
		||||
    goToAssetsTab(glossaryTerm.name, glossaryTerm.fullyQualifiedName, true);
 | 
			
		||||
    verifyResponseStatusCode('@assetTab', 200);
 | 
			
		||||
    checkAssetsCount(glossaryTerm.assets.length - (index + 1));
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createGlossaryTerm = (term, glossary, status, isMutually = false) => {
 | 
			
		||||
  fillGlossaryTermDetails(term, glossary, isMutually);
 | 
			
		||||
 | 
			
		||||
@ -194,12 +330,18 @@ const createGlossaryTerm = (term, glossary, status, isMutually = false) => {
 | 
			
		||||
 | 
			
		||||
  verifyResponseStatusCode('@createGlossaryTerms', 201);
 | 
			
		||||
 | 
			
		||||
  cy.get(`[data-row-key="${glossary.name}.${term.name}"]`)
 | 
			
		||||
  cy.get(
 | 
			
		||||
    `[data-row-key="${Cypress.$.escapeSelector(term.fullyQualifiedName)}"]`
 | 
			
		||||
  )
 | 
			
		||||
    .scrollIntoView()
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .contains(term.name);
 | 
			
		||||
 | 
			
		||||
  cy.get(`[data-testid="${glossary.name}.${term.name}-status"]`)
 | 
			
		||||
  cy.get(
 | 
			
		||||
    `[data-testid="${Cypress.$.escapeSelector(
 | 
			
		||||
      term.fullyQualifiedName
 | 
			
		||||
    )}-status"]`
 | 
			
		||||
  )
 | 
			
		||||
    .should('be.visible')
 | 
			
		||||
    .contains(status);
 | 
			
		||||
};
 | 
			
		||||
@ -501,132 +643,8 @@ describe('Glossary page should work properly', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Create new glossary flow should work properly', () => {
 | 
			
		||||
    interceptURL('POST', '/api/v1/glossaries', 'createGlossary');
 | 
			
		||||
    interceptURL(
 | 
			
		||||
      'GET',
 | 
			
		||||
      '/api/v1/search/query?q=*disabled:false&index=tag_search_index&from=0&size=10&query_filter=%7B%7D',
 | 
			
		||||
      'fetchTags'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="add-glossary"]').click();
 | 
			
		||||
 | 
			
		||||
    // Redirecting to add glossary page
 | 
			
		||||
    cy.get('[data-testid="form-heading"]')
 | 
			
		||||
      .contains('Add Glossary')
 | 
			
		||||
      .should('be.visible');
 | 
			
		||||
 | 
			
		||||
    // validation should work
 | 
			
		||||
    cy.get('[data-testid="save-glossary"]')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    validateForm();
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="name"]')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .clear()
 | 
			
		||||
      .type(NEW_GLOSSARY.name);
 | 
			
		||||
 | 
			
		||||
    cy.get(descriptionBox)
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .type(NEW_GLOSSARY.description);
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="mutually-exclusive-button"]')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="tag-selector"] .ant-select-selection-overflow')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .type('Personal');
 | 
			
		||||
    verifyResponseStatusCode('@fetchTags', 200);
 | 
			
		||||
    cy.get('[data-testid="tag-PersonalData.Personal"]').click();
 | 
			
		||||
    cy.get('[data-testid="right-panel"]').click();
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="add-reviewers"]').scrollIntoView().click();
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="searchbar"]').type(CREDENTIALS.username);
 | 
			
		||||
    cy.get(`[title="${CREDENTIALS.username}"]`)
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="selectable-list-update-btn"]')
 | 
			
		||||
      .should('exist')
 | 
			
		||||
      .and('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
    cy.get('[data-testid="delete-confirmation-modal"]').should('not.exist');
 | 
			
		||||
    cy.get('[data-testid="reviewers-container"]')
 | 
			
		||||
      .children()
 | 
			
		||||
      .should('have.length', 1);
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="save-glossary"]')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    cy.wait('@createGlossary').then(({ request }) => {
 | 
			
		||||
      expect(request.body).to.have.all.keys(
 | 
			
		||||
        'description',
 | 
			
		||||
        'mutuallyExclusive',
 | 
			
		||||
        'name',
 | 
			
		||||
        'owner',
 | 
			
		||||
        'reviewers',
 | 
			
		||||
        'tags'
 | 
			
		||||
      );
 | 
			
		||||
      expect(request.body.name).equals(NEW_GLOSSARY.name);
 | 
			
		||||
      expect(request.body.description).equals(NEW_GLOSSARY.description);
 | 
			
		||||
      expect(request.body.mutuallyExclusive).equals(true);
 | 
			
		||||
      expect(request.body.owner).to.have.all.keys('id', 'type');
 | 
			
		||||
      expect(request.body.reviewers).has.length(1);
 | 
			
		||||
      expect(request.body.tags).has.length(1);
 | 
			
		||||
      expect(request.body.tags[0].tagFQN).equals('PersonalData.Personal');
 | 
			
		||||
      expect(request.body.tags[0].source).equals('Classification');
 | 
			
		||||
 | 
			
		||||
      cy.url().should('include', '/glossary/');
 | 
			
		||||
 | 
			
		||||
      checkDisplayName(NEW_GLOSSARY.name);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Adding another Glossary with mutually exclusive flag off
 | 
			
		||||
    cy.get('[data-testid="add-glossary"]').should('be.visible').click();
 | 
			
		||||
    cy.get('[data-testid="name"]')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .type(NEW_GLOSSARY_1.name);
 | 
			
		||||
 | 
			
		||||
    cy.get(descriptionBox)
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .type(NEW_GLOSSARY_1.description);
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="save-glossary"]')
 | 
			
		||||
      .scrollIntoView()
 | 
			
		||||
      .should('be.visible')
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    cy.wait('@createGlossary').then(({ request }) => {
 | 
			
		||||
      expect(request.body).to.have.all.keys(
 | 
			
		||||
        'description',
 | 
			
		||||
        'mutuallyExclusive',
 | 
			
		||||
        'name',
 | 
			
		||||
        'owner',
 | 
			
		||||
        'reviewers',
 | 
			
		||||
        'tags'
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      expect(request.body.name).equals(NEW_GLOSSARY_1.name);
 | 
			
		||||
      expect(request.body.description).equals(NEW_GLOSSARY_1.description);
 | 
			
		||||
      expect(request.body.mutuallyExclusive).equals(false);
 | 
			
		||||
      expect(request.body.owner).to.have.all.keys('id', 'type');
 | 
			
		||||
      expect(request.body.reviewers).has.length(0);
 | 
			
		||||
      expect(request.body.tags).has.length(0);
 | 
			
		||||
 | 
			
		||||
      cy.url().should('include', '/glossary/');
 | 
			
		||||
      checkDisplayName(NEW_GLOSSARY_1.name);
 | 
			
		||||
    });
 | 
			
		||||
    createGlossary(NEW_GLOSSARY);
 | 
			
		||||
    createGlossary(NEW_GLOSSARY_1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Verify and Remove Tags from Glossary', () => {
 | 
			
		||||
@ -974,82 +992,22 @@ describe('Glossary page should work properly', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Add asset to glossary term using asset modal', () => {
 | 
			
		||||
    selectActiveGlossary(NEW_GLOSSARY.name);
 | 
			
		||||
    goToAssetsTab(
 | 
			
		||||
      NEW_GLOSSARY_TERMS.term_3.name,
 | 
			
		||||
      NEW_GLOSSARY_TERMS.term_3.fullyQualifiedName,
 | 
			
		||||
      true
 | 
			
		||||
    createGlossary(CYPRESS_ASSETS_GLOSSARY);
 | 
			
		||||
    const terms = Object.values(CYPRESS_ASSETS_GLOSSARY_TERMS);
 | 
			
		||||
    selectActiveGlossary(CYPRESS_ASSETS_GLOSSARY.name);
 | 
			
		||||
    terms.forEach((term) =>
 | 
			
		||||
      createGlossaryTerm(term, CYPRESS_ASSETS_GLOSSARY, 'Approved', true)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    checkAssetsCount(0);
 | 
			
		||||
    cy.contains('Adding a new Asset is easy, just give it a spin!').should(
 | 
			
		||||
      'be.visible'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="glossary-term-add-button-menu"]').click();
 | 
			
		||||
    cy.get('.ant-dropdown-menu .ant-dropdown-menu-title-content')
 | 
			
		||||
      .contains('Assets')
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="asset-selection-modal"] .ant-modal-title').should(
 | 
			
		||||
      'contain',
 | 
			
		||||
      'Add Assets'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    NEW_GLOSSARY_TERMS.term_3.assets.forEach((asset) => {
 | 
			
		||||
      interceptURL('GET', '/api/v1/search/query*', 'searchAssets');
 | 
			
		||||
      cy.get('[data-testid="asset-selection-modal"] [data-testid="searchbar"]')
 | 
			
		||||
        .click()
 | 
			
		||||
        .clear()
 | 
			
		||||
        .type(asset.name);
 | 
			
		||||
 | 
			
		||||
      verifyResponseStatusCode('@searchAssets', 200);
 | 
			
		||||
 | 
			
		||||
      cy.get(
 | 
			
		||||
        `[data-testid="table-data-card_${asset.fullyQualifiedName}"] input[type="checkbox"]`
 | 
			
		||||
      ).click();
 | 
			
		||||
    terms.forEach((term) => {
 | 
			
		||||
      addAssetToGlossaryTerm(term, CYPRESS_ASSETS_GLOSSARY);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    cy.get('[data-testid="save-btn"]').click();
 | 
			
		||||
 | 
			
		||||
    checkAssetsCount(NEW_GLOSSARY_TERMS.term_3.assets);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Remove asset from glossary term using asset modal', () => {
 | 
			
		||||
    selectActiveGlossary(NEW_GLOSSARY.name);
 | 
			
		||||
    goToAssetsTab(
 | 
			
		||||
      NEW_GLOSSARY_TERMS.term_3.name,
 | 
			
		||||
      NEW_GLOSSARY_TERMS.term_3.fullyQualifiedName,
 | 
			
		||||
      true
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    checkAssetsCount(NEW_GLOSSARY_TERMS.term_3.assets.length);
 | 
			
		||||
    NEW_GLOSSARY_TERMS.term_3.assets.assets.forEach((asset, index) => {
 | 
			
		||||
      interceptURL('GET', '/api/v1/search/query*', 'searchAssets');
 | 
			
		||||
 | 
			
		||||
      cy.get(
 | 
			
		||||
        `[data-testid="table-data-card_${asset.fullyQualifiedName}"]`
 | 
			
		||||
      ).within(() => {
 | 
			
		||||
        cy.get('.explore-card-actions').invoke('show');
 | 
			
		||||
        cy.get('.explore-card-actions').within(() => {
 | 
			
		||||
          cy.get('[data-testid="delete-tag"]').click();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      cy.get("[data-testid='save-button']").click();
 | 
			
		||||
 | 
			
		||||
      selectActiveGlossary(NEW_GLOSSARY.name);
 | 
			
		||||
 | 
			
		||||
      interceptURL('GET', '/api/v1/search/query*', 'assetTab');
 | 
			
		||||
      // go assets tab
 | 
			
		||||
      goToAssetsTab(
 | 
			
		||||
        NEW_GLOSSARY_TERMS.term_3.name,
 | 
			
		||||
        NEW_GLOSSARY_TERMS.term_3.fullyQualifiedName,
 | 
			
		||||
        true
 | 
			
		||||
      );
 | 
			
		||||
      verifyResponseStatusCode('@assetTab', 200);
 | 
			
		||||
 | 
			
		||||
      checkAssetsCount(NEW_GLOSSARY_TERMS.term_3.assets.length - (index + 1));
 | 
			
		||||
    const terms = Object.values(CYPRESS_ASSETS_GLOSSARY_TERMS);
 | 
			
		||||
    terms.forEach((term) => {
 | 
			
		||||
      removeAssetsFromGlossaryTerm(term, CYPRESS_ASSETS_GLOSSARY);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -1146,7 +1104,11 @@ describe('Cleanup', () => {
 | 
			
		||||
  it('Delete glossary should work properly', () => {
 | 
			
		||||
    goToGlossaryPage();
 | 
			
		||||
    verifyResponseStatusCode('@fetchGlossaries', 200);
 | 
			
		||||
    [NEW_GLOSSARY.name, NEW_GLOSSARY_1.name].forEach((glossary) => {
 | 
			
		||||
    [
 | 
			
		||||
      NEW_GLOSSARY.name,
 | 
			
		||||
      NEW_GLOSSARY_1.name,
 | 
			
		||||
      CYPRESS_ASSETS_GLOSSARY.name,
 | 
			
		||||
    ].forEach((glossary) => {
 | 
			
		||||
      deleteGlossary(glossary);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -1 +1,10 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none"><path fill="#F8B025" d="M11.813 6A5.812 5.812 0 1 1 .188 6a5.812 5.812 0 0 1 11.624 0ZM6 7.172a1.078 1.078 0 1 0 0 2.156 1.078 1.078 0 0 0 0-2.156ZM4.976 3.297l.174 3.187c.008.15.132.266.281.266H6.57c.15 0 .273-.117.28-.266l.175-3.187A.281.281 0 0 0 6.743 3H5.257a.281.281 0 0 0-.28.297Z"/></svg>
 | 
			
		||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
<g clip-path="url(#clip0_8121_47688)">
 | 
			
		||||
<path d="M1 30C1 14.011 14.011 1 30 1C45.989 1 59 14.011 59 30C59 45.989 45.989 59 30 59C14.011 59 1 45.989 1 30ZM27.0405 44.75C27.0405 46.3869 28.3676 47.7095 30 47.7095C31.6328 47.7095 32.9595 46.3828 32.9595 44.75C32.9595 43.1131 31.6324 41.7905 30 41.7905C28.3631 41.7905 27.0405 43.1176 27.0405 44.75ZM30 37.875C31.6324 37.875 32.9595 36.5524 32.9595 34.9155V15.25C32.9595 13.6172 31.6328 12.2905 30 12.2905C28.3672 12.2905 27.0405 13.6172 27.0405 15.25V34.9155C27.0405 36.5483 28.3672 37.875 30 37.875Z" fill="#FFBE0E" fill-opacity="0.5" stroke="#FFBE0E"/>
 | 
			
		||||
</g>
 | 
			
		||||
<defs>
 | 
			
		||||
<clipPath id="clip0_8121_47688">
 | 
			
		||||
<rect width="60" height="60" fill="white"/>
 | 
			
		||||
</clipPath>
 | 
			
		||||
</defs>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 813 B  | 
@ -0,0 +1,188 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed 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 { PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { Button, Divider, Dropdown, Menu, Typography } from 'antd';
 | 
			
		||||
import { isEmpty } from 'lodash';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { SearchIndex } from '../../enums/search.enum';
 | 
			
		||||
import {
 | 
			
		||||
  QueryFieldInterface,
 | 
			
		||||
  QueryFieldValueInterface,
 | 
			
		||||
} from '../../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
import { getAssetsPageQuickFilters } from '../../utils/AdvancedSearchUtils';
 | 
			
		||||
import { getSelectedValuesFromQuickFilter } from '../../utils/Explore.utils';
 | 
			
		||||
import { ExploreQuickFilterField } from '../Explore/ExplorePage.interface';
 | 
			
		||||
import ExploreQuickFilters from '../Explore/ExploreQuickFilters';
 | 
			
		||||
import { AssetFiltersProps } from './AssetFilters.interface';
 | 
			
		||||
 | 
			
		||||
const AssetFilters = ({
 | 
			
		||||
  filterData,
 | 
			
		||||
  type,
 | 
			
		||||
  aggregations,
 | 
			
		||||
  onQuickFilterChange,
 | 
			
		||||
  quickFilterQuery,
 | 
			
		||||
}: AssetFiltersProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const [filters, setFilters] = useState<ExploreQuickFilterField[]>([]);
 | 
			
		||||
  const [selectedFilter, setSelectedFilter] = useState<string[]>([]);
 | 
			
		||||
  const [selectedQuickFilters, setSelectedQuickFilters] = useState<
 | 
			
		||||
    ExploreQuickFilterField[]
 | 
			
		||||
  >([]);
 | 
			
		||||
 | 
			
		||||
  const handleMenuClick = ({ key }: { key: string }) => {
 | 
			
		||||
    setSelectedFilter((prevSelected) => [...prevSelected, key]);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const menu = useMemo(
 | 
			
		||||
    () => (
 | 
			
		||||
      <Menu selectedKeys={selectedFilter} onClick={handleMenuClick}>
 | 
			
		||||
        {filters.map((filter) => (
 | 
			
		||||
          <Menu.Item key={filter.key}>{filter.label}</Menu.Item>
 | 
			
		||||
        ))}
 | 
			
		||||
      </Menu>
 | 
			
		||||
    ),
 | 
			
		||||
    [selectedFilter, filters]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleQuickFiltersChange = (data: ExploreQuickFilterField[]) => {
 | 
			
		||||
    const must: QueryFieldInterface[] = [];
 | 
			
		||||
    data.forEach((filter) => {
 | 
			
		||||
      if (!isEmpty(filter.value)) {
 | 
			
		||||
        const should: QueryFieldValueInterface[] = [];
 | 
			
		||||
        if (filter.value) {
 | 
			
		||||
          filter.value.forEach((filterValue) => {
 | 
			
		||||
            const term: Record<string, string> = {};
 | 
			
		||||
            term[filter.key] = filterValue.key;
 | 
			
		||||
            should.push({ term });
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        must.push({
 | 
			
		||||
          bool: { should },
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const quickFilterQuery = isEmpty(must)
 | 
			
		||||
      ? undefined
 | 
			
		||||
      : {
 | 
			
		||||
          query: { bool: { must } },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    onQuickFilterChange?.(quickFilterQuery);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleQuickFiltersValueSelect = useCallback(
 | 
			
		||||
    (field: ExploreQuickFilterField) => {
 | 
			
		||||
      setSelectedQuickFilters((pre) => {
 | 
			
		||||
        const data = pre.map((preField) => {
 | 
			
		||||
          if (preField.key === field.key) {
 | 
			
		||||
            return field;
 | 
			
		||||
          } else {
 | 
			
		||||
            return preField;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        handleQuickFiltersChange(data);
 | 
			
		||||
 | 
			
		||||
        return data;
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [setSelectedQuickFilters]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const clearFilters = useCallback(() => {
 | 
			
		||||
    setSelectedQuickFilters((pre) => {
 | 
			
		||||
      const data = pre.map((preField) => {
 | 
			
		||||
        return { ...preField, value: [] };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      handleQuickFiltersChange(data);
 | 
			
		||||
 | 
			
		||||
      return data;
 | 
			
		||||
    });
 | 
			
		||||
  }, [handleQuickFiltersChange, setSelectedQuickFilters]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (filterData?.length === 0) {
 | 
			
		||||
      const dropdownItems = getAssetsPageQuickFilters(type);
 | 
			
		||||
 | 
			
		||||
      setFilters(
 | 
			
		||||
        dropdownItems.map((item) => ({
 | 
			
		||||
          ...item,
 | 
			
		||||
          value: getSelectedValuesFromQuickFilter(item, dropdownItems),
 | 
			
		||||
        }))
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      setFilters(filterData ?? []);
 | 
			
		||||
    }
 | 
			
		||||
  }, [filterData, type]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const updatedQuickFilters = filters
 | 
			
		||||
      .filter((filter) => selectedFilter.includes(filter.key))
 | 
			
		||||
      .map((selectedFilterItem) => {
 | 
			
		||||
        const originalFilterItem = selectedQuickFilters?.find(
 | 
			
		||||
          (filter) => filter.key === selectedFilterItem.key
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return originalFilterItem || selectedFilterItem;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    const newItems = updatedQuickFilters.filter(
 | 
			
		||||
      (item) =>
 | 
			
		||||
        !selectedQuickFilters.some(
 | 
			
		||||
          (existingItem) => item.key === existingItem.key
 | 
			
		||||
        )
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (newItems.length > 0) {
 | 
			
		||||
      setSelectedQuickFilters((prevSelected) => [...prevSelected, ...newItems]);
 | 
			
		||||
    }
 | 
			
		||||
  }, [selectedFilter, selectedQuickFilters, filters]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Dropdown overlay={menu} trigger={['click']}>
 | 
			
		||||
        <Button icon={<PlusOutlined />} size="small" type="primary" />
 | 
			
		||||
      </Dropdown>
 | 
			
		||||
      {selectedQuickFilters.length > 0 && (
 | 
			
		||||
        <>
 | 
			
		||||
          <Divider className="m-x-md h-6" type="vertical" />
 | 
			
		||||
          <div className="d-flex justify-between flex-1">
 | 
			
		||||
            <ExploreQuickFilters
 | 
			
		||||
              aggregations={aggregations}
 | 
			
		||||
              fields={selectedQuickFilters}
 | 
			
		||||
              index={SearchIndex.ALL}
 | 
			
		||||
              onFieldValueSelect={(data) =>
 | 
			
		||||
                handleQuickFiltersValueSelect?.(data)
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
            {quickFilterQuery && (
 | 
			
		||||
              <Typography.Text
 | 
			
		||||
                className="text-primary self-center cursor-pointer"
 | 
			
		||||
                onClick={clearFilters}>
 | 
			
		||||
                {t('label.clear-entity', {
 | 
			
		||||
                  entity: '',
 | 
			
		||||
                })}
 | 
			
		||||
              </Typography.Text>
 | 
			
		||||
            )}
 | 
			
		||||
          </div>
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AssetFilters;
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed 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 { Aggregations } from '../../interface/search.interface';
 | 
			
		||||
import { QueryFilterInterface } from '../../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
import { ExploreQuickFilterField } from '../Explore/ExplorePage.interface';
 | 
			
		||||
import { AssetsOfEntity } from '../Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
 | 
			
		||||
 | 
			
		||||
export interface AssetFiltersProps {
 | 
			
		||||
  filterData?: ExploreQuickFilterField[];
 | 
			
		||||
  defaultFilter?: string[];
 | 
			
		||||
  aggregations?: Aggregations;
 | 
			
		||||
  onQuickFilterChange?: (query?: QueryFilterInterface) => void;
 | 
			
		||||
  type: AssetsOfEntity;
 | 
			
		||||
  quickFilterQuery?: QueryFilterInterface;
 | 
			
		||||
}
 | 
			
		||||
@ -33,6 +33,7 @@ import { PipelineService } from '../../../generated/entity/services/pipelineServ
 | 
			
		||||
import { SearchService } from '../../../generated/entity/services/searchService';
 | 
			
		||||
import { StorageService } from '../../../generated/entity/services/storageService';
 | 
			
		||||
import { Team } from '../../../generated/entity/teams/team';
 | 
			
		||||
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
 | 
			
		||||
export interface AssetSelectionModalProps {
 | 
			
		||||
  entityFqn: string;
 | 
			
		||||
@ -40,7 +41,7 @@ export interface AssetSelectionModalProps {
 | 
			
		||||
  type?: AssetsOfEntity;
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  onSave?: () => void;
 | 
			
		||||
  queryFilter?: Record<string, unknown>;
 | 
			
		||||
  queryFilter?: QueryFilterInterface;
 | 
			
		||||
  emptyPlaceHolderText?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@
 | 
			
		||||
 */
 | 
			
		||||
import { Button, Checkbox, List, Modal, Space, Typography } from 'antd';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import { compare } from 'fast-json-patch';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import { EntityDetailUnion } from 'Models';
 | 
			
		||||
import VirtualList from 'rc-virtual-list';
 | 
			
		||||
import {
 | 
			
		||||
@ -26,30 +26,38 @@ import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { PAGE_SIZE_MEDIUM } from '../../../constants/constants';
 | 
			
		||||
import { SearchIndex } from '../../../enums/search.enum';
 | 
			
		||||
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
 | 
			
		||||
import { Table } from '../../../generated/entity/data/table';
 | 
			
		||||
import { DataProduct } from '../../../generated/entity/domains/dataProduct';
 | 
			
		||||
import { Domain } from '../../../generated/entity/domains/domain';
 | 
			
		||||
import {
 | 
			
		||||
  BulkOperationResult,
 | 
			
		||||
  Status,
 | 
			
		||||
} from '../../../generated/type/bulkOperationResult';
 | 
			
		||||
import { Aggregations } from '../../../interface/search.interface';
 | 
			
		||||
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
import {
 | 
			
		||||
  addAssetsToDataProduct,
 | 
			
		||||
  getDataProductByName,
 | 
			
		||||
  patchDataProduct,
 | 
			
		||||
} from '../../../rest/dataProductAPI';
 | 
			
		||||
import { getDomainByName } from '../../../rest/domainAPI';
 | 
			
		||||
import { addAssetsToDomain, getDomainByName } from '../../../rest/domainAPI';
 | 
			
		||||
import {
 | 
			
		||||
  addAssetsToGlossaryTerm,
 | 
			
		||||
  getGlossaryTermByFQN,
 | 
			
		||||
} from '../../../rest/glossaryAPI';
 | 
			
		||||
import { searchQuery } from '../../../rest/searchAPI';
 | 
			
		||||
import {
 | 
			
		||||
  getAPIfromSource,
 | 
			
		||||
  getAssetsFields,
 | 
			
		||||
  getEntityAPIfromSource,
 | 
			
		||||
} from '../../../utils/Assets/AssetsUtils';
 | 
			
		||||
import { getAssetsPageQuickFilters } from '../../../utils/AdvancedSearchUtils';
 | 
			
		||||
import { getEntityReferenceFromEntity } from '../../../utils/EntityUtils';
 | 
			
		||||
import { getDecodedFqn } from '../../../utils/StringsUtils';
 | 
			
		||||
import {
 | 
			
		||||
  getAggregations,
 | 
			
		||||
  getSelectedValuesFromQuickFilter,
 | 
			
		||||
} from '../../../utils/Explore.utils';
 | 
			
		||||
import { getCombinedQueryFilterObject } from '../../../utils/ExplorePage/ExplorePageUtils';
 | 
			
		||||
import { getEncodedFqn } from '../../../utils/StringsUtils';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import AssetFilters from '../../AssetFilters/AssetFilters.component';
 | 
			
		||||
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
 | 
			
		||||
import Searchbar from '../../common/SearchBarComponent/SearchBar.component';
 | 
			
		||||
import TableDataCardV2 from '../../common/TableDataCardV2/TableDataCardV2';
 | 
			
		||||
import { ExploreQuickFilterField } from '../../Explore/ExplorePage.interface';
 | 
			
		||||
import { AssetsOfEntity } from '../../Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
 | 
			
		||||
import Loader from '../../Loader/Loader';
 | 
			
		||||
import { SearchedDataProps } from '../../SearchedData/SearchedData.interface';
 | 
			
		||||
@ -62,13 +70,14 @@ export const AssetSelectionModal = ({
 | 
			
		||||
  onSave,
 | 
			
		||||
  open,
 | 
			
		||||
  type = AssetsOfEntity.GLOSSARY,
 | 
			
		||||
  queryFilter = {},
 | 
			
		||||
  queryFilter,
 | 
			
		||||
  emptyPlaceHolderText,
 | 
			
		||||
}: AssetSelectionModalProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const ES_UPDATE_DELAY = 500;
 | 
			
		||||
  const [search, setSearch] = useState('');
 | 
			
		||||
  const [items, setItems] = useState<SearchedDataProps['data']>([]);
 | 
			
		||||
  const [failedStatus, setFailedStatus] = useState<BulkOperationResult>();
 | 
			
		||||
  const [selectedItems, setSelectedItems] =
 | 
			
		||||
    useState<Map<string, EntityDetailUnion>>();
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
@ -80,9 +89,28 @@ export const AssetSelectionModal = ({
 | 
			
		||||
  const [totalCount, setTotalCount] = useState(0);
 | 
			
		||||
 | 
			
		||||
  const [isSaveLoading, setIsSaveLoading] = useState<boolean>(false);
 | 
			
		||||
  const [aggregations, setAggregations] = useState<Aggregations>();
 | 
			
		||||
  const [selectedQuickFilters, setSelectedQuickFilters] = useState<
 | 
			
		||||
    ExploreQuickFilterField[]
 | 
			
		||||
  >([]);
 | 
			
		||||
  const [quickFilterQuery, setQuickFilterQuery] =
 | 
			
		||||
    useState<QueryFilterInterface>();
 | 
			
		||||
  const [updatedQueryFilter, setUpdatedQueryFilter] =
 | 
			
		||||
    useState<QueryFilterInterface>(
 | 
			
		||||
      getCombinedQueryFilterObject(queryFilter as QueryFilterInterface, {
 | 
			
		||||
        query: {
 | 
			
		||||
          bool: {},
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  const fetchEntities = useCallback(
 | 
			
		||||
    async ({ searchText = '', page = 1, index = activeFilter }) => {
 | 
			
		||||
    async ({
 | 
			
		||||
      searchText = '',
 | 
			
		||||
      page = 1,
 | 
			
		||||
      index = activeFilter,
 | 
			
		||||
      updatedQueryFilter,
 | 
			
		||||
    }) => {
 | 
			
		||||
      try {
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        const res = await searchQuery({
 | 
			
		||||
@ -90,12 +118,13 @@ export const AssetSelectionModal = ({
 | 
			
		||||
          pageSize: PAGE_SIZE_MEDIUM,
 | 
			
		||||
          searchIndex: index,
 | 
			
		||||
          query: searchText,
 | 
			
		||||
          queryFilter: queryFilter,
 | 
			
		||||
          queryFilter: updatedQueryFilter,
 | 
			
		||||
        });
 | 
			
		||||
        const hits = res.hits.hits as SearchedDataProps['data'];
 | 
			
		||||
        setTotalCount(res.hits.total.value ?? 0);
 | 
			
		||||
        setItems(page === 1 ? hits : (prevItems) => [...prevItems, ...hits]);
 | 
			
		||||
        setPageNumber(page);
 | 
			
		||||
        setAggregations(getAggregations(res?.aggregations));
 | 
			
		||||
      } catch (_) {
 | 
			
		||||
        // Nothing here
 | 
			
		||||
      } finally {
 | 
			
		||||
@ -116,16 +145,31 @@ export const AssetSelectionModal = ({
 | 
			
		||||
      );
 | 
			
		||||
      setActiveEntity(data);
 | 
			
		||||
    } else if (type === AssetsOfEntity.GLOSSARY) {
 | 
			
		||||
      const data = await getGlossaryTermByFQN(getDecodedFqn(entityFqn), 'tags');
 | 
			
		||||
      const data = await getGlossaryTermByFQN(entityFqn, 'tags');
 | 
			
		||||
      setActiveEntity(data);
 | 
			
		||||
    }
 | 
			
		||||
  }, [type, entityFqn]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const dropdownItems = getAssetsPageQuickFilters(type);
 | 
			
		||||
 | 
			
		||||
    setSelectedQuickFilters(
 | 
			
		||||
      dropdownItems.map((item) => ({
 | 
			
		||||
        ...item,
 | 
			
		||||
        value: getSelectedValuesFromQuickFilter(item, dropdownItems),
 | 
			
		||||
      }))
 | 
			
		||||
    );
 | 
			
		||||
  }, [type]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (open) {
 | 
			
		||||
      fetchEntities({ index: activeFilter, searchText: search });
 | 
			
		||||
      fetchEntities({
 | 
			
		||||
        index: activeFilter,
 | 
			
		||||
        searchText: search,
 | 
			
		||||
        updatedQueryFilter,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [open, activeFilter, search, type]);
 | 
			
		||||
  }, [open, activeFilter, search, type, updatedQueryFilter]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (open) {
 | 
			
		||||
@ -166,165 +210,60 @@ export const AssetSelectionModal = ({
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getJsonPatchObject = (entity: Table) => {
 | 
			
		||||
    if (!activeEntity) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
    const { id, description, fullyQualifiedName, name, displayName } =
 | 
			
		||||
      activeEntity;
 | 
			
		||||
    const patchObj = {
 | 
			
		||||
      id,
 | 
			
		||||
      description,
 | 
			
		||||
      fullyQualifiedName,
 | 
			
		||||
      name,
 | 
			
		||||
      displayName,
 | 
			
		||||
      type: type === AssetsOfEntity.DATA_PRODUCT ? 'dataProduct' : 'domain',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (type === AssetsOfEntity.DATA_PRODUCT) {
 | 
			
		||||
      const jsonPatch = compare(entity, {
 | 
			
		||||
        ...entity,
 | 
			
		||||
        dataProducts: [...(entity.dataProducts ?? []), patchObj],
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return jsonPatch;
 | 
			
		||||
    } else {
 | 
			
		||||
      const jsonPatch = compare(entity, {
 | 
			
		||||
        ...entity,
 | 
			
		||||
        domain: patchObj,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return jsonPatch;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const dataProductsSave = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      setIsSaveLoading(true);
 | 
			
		||||
      if (!activeEntity) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const entities = [...(selectedItems?.values() ?? [])].map((item) => {
 | 
			
		||||
        return getEntityReferenceFromEntity(item, item.entityType);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const newEntities = entities.filter((entity) => {
 | 
			
		||||
        const entityKey = entity.id;
 | 
			
		||||
 | 
			
		||||
        return !((activeEntity as DataProduct).assets ?? []).some(
 | 
			
		||||
          (asset) => asset.id === entityKey
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (newEntities.length === 0) {
 | 
			
		||||
        onSave?.();
 | 
			
		||||
        onCancel();
 | 
			
		||||
        setIsSaveLoading(false);
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const updatedActiveEntity = {
 | 
			
		||||
        ...activeEntity,
 | 
			
		||||
        assets: [
 | 
			
		||||
          ...((activeEntity as DataProduct).assets ?? []),
 | 
			
		||||
          ...newEntities,
 | 
			
		||||
        ],
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const jsonPatch = compare(activeEntity, updatedActiveEntity);
 | 
			
		||||
      await patchDataProduct(activeEntity.id, jsonPatch);
 | 
			
		||||
      await new Promise((resolve) => {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          resolve('');
 | 
			
		||||
          onSave?.();
 | 
			
		||||
        }, ES_UPDATE_DELAY);
 | 
			
		||||
      });
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      showErrorToast(err as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsSaveLoading(false);
 | 
			
		||||
      onCancel();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const glossarySave = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      setIsSaveLoading(true);
 | 
			
		||||
      if (!activeEntity) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const entities = [...(selectedItems?.values() ?? [])].map((item) => {
 | 
			
		||||
        return getEntityReferenceFromEntity(item, item.entityType);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await addAssetsToGlossaryTerm(activeEntity as GlossaryTerm, entities);
 | 
			
		||||
      await new Promise((resolve) => {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          resolve('');
 | 
			
		||||
          onSave?.();
 | 
			
		||||
        }, ES_UPDATE_DELAY);
 | 
			
		||||
      });
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      showErrorToast(err as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsSaveLoading(false);
 | 
			
		||||
      onCancel();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSave = async () => {
 | 
			
		||||
    if (type === AssetsOfEntity.DATA_PRODUCT) {
 | 
			
		||||
      dataProductsSave();
 | 
			
		||||
    } else if (type === AssetsOfEntity.GLOSSARY) {
 | 
			
		||||
      glossarySave();
 | 
			
		||||
    } else {
 | 
			
		||||
      try {
 | 
			
		||||
        setIsSaveLoading(true);
 | 
			
		||||
        const entityDetails = [...(selectedItems?.values() ?? [])].map((item) =>
 | 
			
		||||
          getEntityAPIfromSource(item.entityType)(
 | 
			
		||||
            item.fullyQualifiedName,
 | 
			
		||||
            getAssetsFields(type)
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
        const entityDetailsResponse = await Promise.allSettled(entityDetails);
 | 
			
		||||
        const map = new Map();
 | 
			
		||||
    try {
 | 
			
		||||
      setIsSaveLoading(true);
 | 
			
		||||
      if (!activeEntity) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        entityDetailsResponse.forEach((response) => {
 | 
			
		||||
          if (response.status === 'fulfilled') {
 | 
			
		||||
            const entity = response.value;
 | 
			
		||||
            entity && map.set(entity.fullyQualifiedName, entity);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        const patchAPIPromises = [...(selectedItems?.values() ?? [])]
 | 
			
		||||
          .map((item) => {
 | 
			
		||||
            if (map.has(item.fullyQualifiedName) && activeEntity) {
 | 
			
		||||
              const entity = map.get(item.fullyQualifiedName);
 | 
			
		||||
              const jsonPatch = getJsonPatchObject(entity);
 | 
			
		||||
              const api = getAPIfromSource(item.entityType);
 | 
			
		||||
      const entities = [...(selectedItems?.values() ?? [])].map((item) => {
 | 
			
		||||
        return getEntityReferenceFromEntity(item, item.entityType);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
              return api(item.id, jsonPatch);
 | 
			
		||||
            }
 | 
			
		||||
      let res;
 | 
			
		||||
      switch (type) {
 | 
			
		||||
        case AssetsOfEntity.DATA_PRODUCT:
 | 
			
		||||
          res = await addAssetsToDataProduct(
 | 
			
		||||
            getEncodedFqn(activeEntity.fullyQualifiedName ?? ''),
 | 
			
		||||
            entities
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
          })
 | 
			
		||||
          .filter(Boolean);
 | 
			
		||||
          break;
 | 
			
		||||
        case AssetsOfEntity.GLOSSARY:
 | 
			
		||||
          res = await addAssetsToGlossaryTerm(
 | 
			
		||||
            activeEntity as GlossaryTerm,
 | 
			
		||||
            entities
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        await Promise.all(patchAPIPromises);
 | 
			
		||||
          break;
 | 
			
		||||
        case AssetsOfEntity.DOMAIN:
 | 
			
		||||
          res = await addAssetsToDomain(
 | 
			
		||||
            getEncodedFqn(activeEntity.fullyQualifiedName ?? ''),
 | 
			
		||||
            entities
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          // Handle other entity types here
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ((res as BulkOperationResult).status === Status.Success) {
 | 
			
		||||
        await new Promise((resolve) => {
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            resolve('');
 | 
			
		||||
            onSave?.();
 | 
			
		||||
          }, ES_UPDATE_DELAY);
 | 
			
		||||
        });
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        showErrorToast(err as AxiosError);
 | 
			
		||||
      } finally {
 | 
			
		||||
        setIsSaveLoading(false);
 | 
			
		||||
        onCancel();
 | 
			
		||||
      } else {
 | 
			
		||||
        setFailedStatus(res as BulkOperationResult);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      showErrorToast(err as AxiosError);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setIsSaveLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -332,6 +271,18 @@ export const AssetSelectionModal = ({
 | 
			
		||||
    handleSave();
 | 
			
		||||
  }, [type, handleSave]);
 | 
			
		||||
 | 
			
		||||
  const mergeFilters = useCallback(() => {
 | 
			
		||||
    const res = getCombinedQueryFilterObject(
 | 
			
		||||
      queryFilter as QueryFilterInterface,
 | 
			
		||||
      quickFilterQuery as QueryFilterInterface
 | 
			
		||||
    );
 | 
			
		||||
    setUpdatedQueryFilter(res);
 | 
			
		||||
  }, [queryFilter, quickFilterQuery]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    mergeFilters();
 | 
			
		||||
  }, [quickFilterQuery, queryFilter]);
 | 
			
		||||
 | 
			
		||||
  const onScroll: UIEventHandler<HTMLElement> = useCallback(
 | 
			
		||||
    (e) => {
 | 
			
		||||
      const scrollHeight =
 | 
			
		||||
@ -381,6 +332,27 @@ export const AssetSelectionModal = ({
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getErrorStatusAndMessage = useCallback(
 | 
			
		||||
    (id: string) => {
 | 
			
		||||
      if (!failedStatus?.failedRequest) {
 | 
			
		||||
        return {
 | 
			
		||||
          isError: false,
 | 
			
		||||
          errorMessage: null,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const matchingStatus = failedStatus.failedRequest.find(
 | 
			
		||||
        (status) => status.request.id === id
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        isError: !!matchingStatus,
 | 
			
		||||
        errorMessage: matchingStatus ? matchingStatus.message : null,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    [failedStatus]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Modal
 | 
			
		||||
      destroyOnClose
 | 
			
		||||
@ -429,6 +401,16 @@ export const AssetSelectionModal = ({
 | 
			
		||||
          onSearch={setSearch}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <div className="d-flex items-center">
 | 
			
		||||
          <AssetFilters
 | 
			
		||||
            aggregations={aggregations}
 | 
			
		||||
            filterData={selectedQuickFilters}
 | 
			
		||||
            quickFilterQuery={quickFilterQuery}
 | 
			
		||||
            type={type}
 | 
			
		||||
            onQuickFilterChange={(data) => setQuickFilterQuery(data)}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        {items.length > 0 && (
 | 
			
		||||
          <div className="border p-xs">
 | 
			
		||||
            <Checkbox
 | 
			
		||||
@ -444,20 +426,38 @@ export const AssetSelectionModal = ({
 | 
			
		||||
                height={500}
 | 
			
		||||
                itemKey="id"
 | 
			
		||||
                onScroll={onScroll}>
 | 
			
		||||
                {({ _source: item }) => (
 | 
			
		||||
                  <TableDataCardV2
 | 
			
		||||
                    openEntityInNewPage
 | 
			
		||||
                    showCheckboxes
 | 
			
		||||
                    checked={selectedItems?.has(item.id ?? '')}
 | 
			
		||||
                    className="border-none asset-selection-model-card cursor-pointer"
 | 
			
		||||
                    handleSummaryPanelDisplay={handleCardClick}
 | 
			
		||||
                    id={`tabledatacard-${item.id}`}
 | 
			
		||||
                    key={item.id}
 | 
			
		||||
                    showBody={false}
 | 
			
		||||
                    showName={false}
 | 
			
		||||
                    source={{ ...item, tags: [] }}
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
                {({ _source: item }) => {
 | 
			
		||||
                  const { isError, errorMessage } = getErrorStatusAndMessage(
 | 
			
		||||
                    item.id
 | 
			
		||||
                  );
 | 
			
		||||
 | 
			
		||||
                  return (
 | 
			
		||||
                    <div
 | 
			
		||||
                      className={classNames({
 | 
			
		||||
                        'm-y-sm border-danger rounded-4': isError,
 | 
			
		||||
                      })}>
 | 
			
		||||
                      <TableDataCardV2
 | 
			
		||||
                        openEntityInNewPage
 | 
			
		||||
                        showCheckboxes
 | 
			
		||||
                        checked={selectedItems?.has(item.id ?? '')}
 | 
			
		||||
                        className="border-none asset-selection-model-card cursor-pointer"
 | 
			
		||||
                        handleSummaryPanelDisplay={handleCardClick}
 | 
			
		||||
                        id={`tabledatacard-${item.id}`}
 | 
			
		||||
                        key={item.id}
 | 
			
		||||
                        showBody={false}
 | 
			
		||||
                        showName={false}
 | 
			
		||||
                        source={{ ...item, tags: [] }}
 | 
			
		||||
                      />
 | 
			
		||||
                      {isError && (
 | 
			
		||||
                        <div className="p-x-sm p-b-sm">
 | 
			
		||||
                          <Typography.Text type="danger">
 | 
			
		||||
                            {errorMessage}
 | 
			
		||||
                          </Typography.Text>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  );
 | 
			
		||||
                }}
 | 
			
		||||
              </VirtualList>
 | 
			
		||||
            </List>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
@ -50,4 +50,18 @@
 | 
			
		||||
  .rc-virtual-list-holder {
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
  }
 | 
			
		||||
  .assets-checkbox {
 | 
			
		||||
    .ant-checkbox-inner {
 | 
			
		||||
      border-width: 2px;
 | 
			
		||||
      height: 20px;
 | 
			
		||||
      width: 20px;
 | 
			
		||||
      border-color: @primary-color;
 | 
			
		||||
    }
 | 
			
		||||
    .ant-checkbox-inner::after {
 | 
			
		||||
      top: 44%;
 | 
			
		||||
      left: 18.5%;
 | 
			
		||||
      width: 6.5px;
 | 
			
		||||
      height: 11px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,9 +23,11 @@ import { AxiosError } from 'axios';
 | 
			
		||||
import { debounce, isEmpty, isUndefined, pick } from 'lodash';
 | 
			
		||||
import { CustomTagProps } from 'rc-select/lib/BaseSelect';
 | 
			
		||||
import React, { FC, useCallback, useMemo, useRef, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import Loader from '../../components/Loader/Loader';
 | 
			
		||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
 | 
			
		||||
import { TAG_START_WITH } from '../../constants/Tag.constants';
 | 
			
		||||
import { LabelType } from '../../generated/entity/data/table';
 | 
			
		||||
import { Paging } from '../../generated/type/paging';
 | 
			
		||||
import { TagLabel } from '../../generated/type/tagLabel';
 | 
			
		||||
import Fqn from '../../utils/Fqn';
 | 
			
		||||
@ -54,7 +56,7 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
 | 
			
		||||
  const [paging, setPaging] = useState<Paging>({} as Paging);
 | 
			
		||||
  const [currentPage, setCurrentPage] = useState(1);
 | 
			
		||||
  const selectedTagsRef = useRef<SelectOption[]>(initialOptions ?? []);
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const [optionFilteredCount, setOptionFilteredCount] = useState(0);
 | 
			
		||||
 | 
			
		||||
  const getFilteredOptions = (data: SelectOption[]) => {
 | 
			
		||||
@ -197,9 +199,12 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
 | 
			
		||||
      event.stopPropagation();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const isDerived =
 | 
			
		||||
      (selectedTag?.data as TagLabel).labelType === LabelType.Derived;
 | 
			
		||||
 | 
			
		||||
    const tagProps = {
 | 
			
		||||
      closable: true,
 | 
			
		||||
      closeIcon: (
 | 
			
		||||
      closable: !isDerived,
 | 
			
		||||
      closeIcon: !isDerived && (
 | 
			
		||||
        <CloseOutlined
 | 
			
		||||
          className="p-r-xs"
 | 
			
		||||
          data-testid="remove-tags"
 | 
			
		||||
@ -208,7 +213,7 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
 | 
			
		||||
        />
 | 
			
		||||
      ),
 | 
			
		||||
      'data-testid': `selected-tag-${tagLabel}`,
 | 
			
		||||
      onClose,
 | 
			
		||||
      onClose: !isDerived ? onClose : null,
 | 
			
		||||
      onMouseDown: onPreventMouseDown,
 | 
			
		||||
    } as TagProps;
 | 
			
		||||
 | 
			
		||||
@ -217,21 +222,32 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
 | 
			
		||||
        startWith={TAG_START_WITH.SOURCE_ICON}
 | 
			
		||||
        tag={tag}
 | 
			
		||||
        tagProps={tagProps}
 | 
			
		||||
        tooltipOverride={
 | 
			
		||||
          isDerived ? t('message.derived-tag-warning') : undefined
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleChange: SelectProps['onChange'] = (values: string[], options) => {
 | 
			
		||||
    const selectedValues = values.map((value) => {
 | 
			
		||||
      const initialData = initialOptions?.find(
 | 
			
		||||
        (item) => item.value === value
 | 
			
		||||
      )?.data;
 | 
			
		||||
      const data = (options as SelectOption[]).find(
 | 
			
		||||
        (option) => option.value === value
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        data ?? {
 | 
			
		||||
        (initialData
 | 
			
		||||
          ? {
 | 
			
		||||
              value,
 | 
			
		||||
              label: value,
 | 
			
		||||
              data: initialData,
 | 
			
		||||
            }
 | 
			
		||||
          : data) ?? {
 | 
			
		||||
          value,
 | 
			
		||||
          label: value,
 | 
			
		||||
          data: initialOptions?.find((item) => item.value === value)?.data,
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
@ -258,6 +274,11 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
 | 
			
		||||
      }}
 | 
			
		||||
      onChange={handleChange}
 | 
			
		||||
      onFocus={() => loadOptions('')}
 | 
			
		||||
      onInputKeyDown={(event) => {
 | 
			
		||||
        if (event.key === 'Backspace') {
 | 
			
		||||
          return event.stopPropagation();
 | 
			
		||||
        }
 | 
			
		||||
      }}
 | 
			
		||||
      onPopupScroll={onScroll}
 | 
			
		||||
      onSearch={debounceFetcher}
 | 
			
		||||
      {...props}>
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,7 @@ import {
 | 
			
		||||
import { Domain } from '../../../generated/entity/domains/domain';
 | 
			
		||||
import { Operation } from '../../../generated/entity/policies/policy';
 | 
			
		||||
import { Style } from '../../../generated/type/tagLabel';
 | 
			
		||||
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
import { searchData } from '../../../rest/miscAPI';
 | 
			
		||||
import { getEntityDeleteMessage } from '../../../utils/CommonUtils';
 | 
			
		||||
import { getQueryFilterToIncludeDomain } from '../../../utils/DomainUtils';
 | 
			
		||||
@ -77,7 +78,10 @@ import {
 | 
			
		||||
  getDataProductVersionsPath,
 | 
			
		||||
  getDomainPath,
 | 
			
		||||
} from '../../../utils/RouterUtils';
 | 
			
		||||
import { escapeESReservedCharacters } from '../../../utils/StringsUtils';
 | 
			
		||||
import {
 | 
			
		||||
  escapeESReservedCharacters,
 | 
			
		||||
  getEncodedFqn,
 | 
			
		||||
} from '../../../utils/StringsUtils';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import { EntityDetailsObjectInterface } from '../../Explore/ExplorePage.interface';
 | 
			
		||||
import StyleModal from '../../Modals/StyleModal/StyleModal.component';
 | 
			
		||||
@ -205,13 +209,14 @@ const DataProductsDetailsPage = ({
 | 
			
		||||
  const fetchDataProductAssets = async () => {
 | 
			
		||||
    if (dataProduct) {
 | 
			
		||||
      try {
 | 
			
		||||
        const encodedFqn = getEncodedFqn(
 | 
			
		||||
          escapeESReservedCharacters(dataProduct.fullyQualifiedName)
 | 
			
		||||
        );
 | 
			
		||||
        const res = await searchData(
 | 
			
		||||
          '',
 | 
			
		||||
          1,
 | 
			
		||||
          0,
 | 
			
		||||
          `(dataProducts.fullyQualifiedName:"${escapeESReservedCharacters(
 | 
			
		||||
            dataProduct.fullyQualifiedName
 | 
			
		||||
          )}")`,
 | 
			
		||||
          `(dataProducts.fullyQualifiedName:"${encodedFqn}")`,
 | 
			
		||||
          '',
 | 
			
		||||
          '',
 | 
			
		||||
          SearchIndex.ALL
 | 
			
		||||
@ -568,20 +573,24 @@ const DataProductsDetailsPage = ({
 | 
			
		||||
        onConfirm={onDelete}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <AssetSelectionModal
 | 
			
		||||
        emptyPlaceHolderText={t('message.domain-does-not-have-assets', {
 | 
			
		||||
          name: getEntityName(dataProduct.domain),
 | 
			
		||||
        })}
 | 
			
		||||
        entityFqn={dataProductFqn}
 | 
			
		||||
        open={assetModalVisible}
 | 
			
		||||
        queryFilter={getQueryFilterToIncludeDomain(
 | 
			
		||||
          dataProduct.domain?.fullyQualifiedName ?? '',
 | 
			
		||||
          dataProduct.fullyQualifiedName ?? ''
 | 
			
		||||
        )}
 | 
			
		||||
        type={AssetsOfEntity.DATA_PRODUCT}
 | 
			
		||||
        onCancel={() => setAssetModelVisible(false)}
 | 
			
		||||
        onSave={handleAssetSave}
 | 
			
		||||
      />
 | 
			
		||||
      {assetModalVisible && (
 | 
			
		||||
        <AssetSelectionModal
 | 
			
		||||
          emptyPlaceHolderText={t('message.domain-does-not-have-assets', {
 | 
			
		||||
            name: getEntityName(dataProduct.domain),
 | 
			
		||||
          })}
 | 
			
		||||
          entityFqn={dataProductFqn}
 | 
			
		||||
          open={assetModalVisible}
 | 
			
		||||
          queryFilter={
 | 
			
		||||
            getQueryFilterToIncludeDomain(
 | 
			
		||||
              dataProduct.domain?.fullyQualifiedName ?? '',
 | 
			
		||||
              dataProduct.fullyQualifiedName ?? ''
 | 
			
		||||
            ) as QueryFilterInterface
 | 
			
		||||
          }
 | 
			
		||||
          type={AssetsOfEntity.DATA_PRODUCT}
 | 
			
		||||
          onCancel={() => setAssetModelVisible(false)}
 | 
			
		||||
          onSave={handleAssetSave}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <StyleModal
 | 
			
		||||
        open={isStyleEditing}
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,10 @@ import {
 | 
			
		||||
  getDomainPath,
 | 
			
		||||
  getDomainVersionsPath,
 | 
			
		||||
} from '../../../utils/RouterUtils';
 | 
			
		||||
import { escapeESReservedCharacters } from '../../../utils/StringsUtils';
 | 
			
		||||
import {
 | 
			
		||||
  escapeESReservedCharacters,
 | 
			
		||||
  getEncodedFqn,
 | 
			
		||||
} from '../../../utils/StringsUtils';
 | 
			
		||||
import { showErrorToast } from '../../../utils/ToastUtils';
 | 
			
		||||
import DeleteWidgetModal from '../../common/DeleteWidget/DeleteWidgetModal';
 | 
			
		||||
import { EntityDetailsObjectInterface } from '../../Explore/ExplorePage.interface';
 | 
			
		||||
@ -235,13 +238,14 @@ const DomainDetailsPage = ({
 | 
			
		||||
  const fetchDataProducts = async () => {
 | 
			
		||||
    if (!isVersionsView) {
 | 
			
		||||
      try {
 | 
			
		||||
        const encodedFqn = getEncodedFqn(
 | 
			
		||||
          escapeESReservedCharacters(domain.fullyQualifiedName)
 | 
			
		||||
        );
 | 
			
		||||
        const res = await searchData(
 | 
			
		||||
          '',
 | 
			
		||||
          1,
 | 
			
		||||
          0,
 | 
			
		||||
          `(domain.fullyQualifiedName:${escapeESReservedCharacters(
 | 
			
		||||
            domain.fullyQualifiedName
 | 
			
		||||
          )})`,
 | 
			
		||||
          `(domain.fullyQualifiedName:"${encodedFqn}")`,
 | 
			
		||||
          '',
 | 
			
		||||
          '',
 | 
			
		||||
          SearchIndex.DATA_PRODUCT
 | 
			
		||||
@ -257,13 +261,14 @@ const DomainDetailsPage = ({
 | 
			
		||||
  const fetchDomainAssets = async () => {
 | 
			
		||||
    if (fqn && !isVersionsView) {
 | 
			
		||||
      try {
 | 
			
		||||
        const encodedFqn = getEncodedFqn(
 | 
			
		||||
          escapeESReservedCharacters(domain.fullyQualifiedName)
 | 
			
		||||
        );
 | 
			
		||||
        const res = await searchData(
 | 
			
		||||
          '',
 | 
			
		||||
          1,
 | 
			
		||||
          0,
 | 
			
		||||
          `(domain.fullyQualifiedName:"${escapeESReservedCharacters(
 | 
			
		||||
            domain.fullyQualifiedName
 | 
			
		||||
          )}") AND !(entityType:"dataProduct")`,
 | 
			
		||||
          `(domain.fullyQualifiedName:"${encodedFqn}") AND !(entityType:"dataProduct")`,
 | 
			
		||||
          '',
 | 
			
		||||
          '',
 | 
			
		||||
          SearchIndex.ALL
 | 
			
		||||
@ -631,15 +636,17 @@ const DomainDetailsPage = ({
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {assetModalVisible && (
 | 
			
		||||
        <AssetSelectionModal
 | 
			
		||||
          entityFqn={domainFqn}
 | 
			
		||||
          open={assetModalVisible}
 | 
			
		||||
          queryFilter={getQueryFilterToExcludeDomainTerms(domainFqn)}
 | 
			
		||||
          type={AssetsOfEntity.DOMAIN}
 | 
			
		||||
          onCancel={() => setAssetModelVisible(false)}
 | 
			
		||||
          onSave={handleAssetSave}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <AssetSelectionModal
 | 
			
		||||
        entityFqn={domainFqn}
 | 
			
		||||
        open={assetModalVisible}
 | 
			
		||||
        queryFilter={getQueryFilterToExcludeDomainTerms(domainFqn)}
 | 
			
		||||
        type={AssetsOfEntity.DOMAIN}
 | 
			
		||||
        onCancel={() => setAssetModelVisible(false)}
 | 
			
		||||
        onSave={handleAssetSave}
 | 
			
		||||
      />
 | 
			
		||||
      {domain && (
 | 
			
		||||
        <DeleteWidgetModal
 | 
			
		||||
          afterDeleteAction={() => onDelete(domain.id)}
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ import { formatDataProductResponse } from '../../../../utils/APIUtils';
 | 
			
		||||
import {
 | 
			
		||||
  escapeESReservedCharacters,
 | 
			
		||||
  getDecodedFqn,
 | 
			
		||||
  getEncodedFqn,
 | 
			
		||||
} from '../../../../utils/StringsUtils';
 | 
			
		||||
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
 | 
			
		||||
import EntitySummaryPanel from '../../../Explore/EntitySummaryPanel/EntitySummaryPanel.component';
 | 
			
		||||
@ -58,13 +59,15 @@ const DataProductsTab = forwardRef(
 | 
			
		||||
    const fetchDataProducts = async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        setLoading(true);
 | 
			
		||||
        const decodedFqn = getDecodedFqn(domainFqn);
 | 
			
		||||
        const encodedFqn = getEncodedFqn(
 | 
			
		||||
          escapeESReservedCharacters(decodedFqn)
 | 
			
		||||
        );
 | 
			
		||||
        const res = await searchData(
 | 
			
		||||
          '',
 | 
			
		||||
          1,
 | 
			
		||||
          PAGE_SIZE_LARGE,
 | 
			
		||||
          `(domain.fullyQualifiedName:"${escapeESReservedCharacters(
 | 
			
		||||
            getDecodedFqn(domainFqn)
 | 
			
		||||
          )}")`,
 | 
			
		||||
          `(domain.fullyQualifiedName:"${encodedFqn}")`,
 | 
			
		||||
          '',
 | 
			
		||||
          '',
 | 
			
		||||
          SearchIndex.DATA_PRODUCT
 | 
			
		||||
 | 
			
		||||
@ -429,13 +429,14 @@ const GlossaryHeader = ({
 | 
			
		||||
          {glossaryTermStatus && glossaryTermStatus === Status.Approved && (
 | 
			
		||||
            <Dropdown
 | 
			
		||||
              className="m-l-xs"
 | 
			
		||||
              data-testid="glossary-term-add-button-menu"
 | 
			
		||||
              menu={{
 | 
			
		||||
                items: addButtonContent,
 | 
			
		||||
              }}
 | 
			
		||||
              placement="bottomRight"
 | 
			
		||||
              trigger={['click']}>
 | 
			
		||||
              <Button type="primary">
 | 
			
		||||
              <Button
 | 
			
		||||
                data-testid="glossary-term-add-button-menu"
 | 
			
		||||
                type="primary">
 | 
			
		||||
                <Space>
 | 
			
		||||
                  {t('label.add')}
 | 
			
		||||
                  <DownOutlined />
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,8 @@
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { FilterOutlined, PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import {
 | 
			
		||||
  Badge,
 | 
			
		||||
  Button,
 | 
			
		||||
  Checkbox,
 | 
			
		||||
  Col,
 | 
			
		||||
@ -22,7 +21,6 @@ import {
 | 
			
		||||
  Menu,
 | 
			
		||||
  MenuProps,
 | 
			
		||||
  notification,
 | 
			
		||||
  Popover,
 | 
			
		||||
  Row,
 | 
			
		||||
  Skeleton,
 | 
			
		||||
  Space,
 | 
			
		||||
@ -32,11 +30,9 @@ import {
 | 
			
		||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import { compare } from 'fast-json-patch';
 | 
			
		||||
import { t } from 'i18next';
 | 
			
		||||
import { isEmpty, isObject, isString, noop } from 'lodash';
 | 
			
		||||
import { isEmpty, isObject } from 'lodash';
 | 
			
		||||
import { EntityDetailUnion } from 'Models';
 | 
			
		||||
import Qs from 'qs';
 | 
			
		||||
import React, {
 | 
			
		||||
  forwardRef,
 | 
			
		||||
  useCallback,
 | 
			
		||||
@ -52,7 +48,6 @@ import { ReactComponent as IconDropdown } from '../../../../assets/svg/menu.svg'
 | 
			
		||||
import {
 | 
			
		||||
  AssetsFilterOptions,
 | 
			
		||||
  ASSET_MENU_KEYS,
 | 
			
		||||
  ASSET_SUB_MENU_FILTER,
 | 
			
		||||
} from '../../../../constants/Assets.constants';
 | 
			
		||||
import {
 | 
			
		||||
  DE_ACTIVE_COLOR,
 | 
			
		||||
@ -67,18 +62,24 @@ import { DataProduct } from '../../../../generated/entity/domains/dataProduct';
 | 
			
		||||
import { Domain } from '../../../../generated/entity/domains/domain';
 | 
			
		||||
import { usePaging } from '../../../../hooks/paging/usePaging';
 | 
			
		||||
import { Aggregations } from '../../../../interface/search.interface';
 | 
			
		||||
import {
 | 
			
		||||
  QueryFieldInterface,
 | 
			
		||||
  QueryFieldValueInterface,
 | 
			
		||||
} from '../../../../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
import {
 | 
			
		||||
  getDataProductByName,
 | 
			
		||||
  patchDataProduct,
 | 
			
		||||
  removeAssetsFromDataProduct,
 | 
			
		||||
} from '../../../../rest/dataProductAPI';
 | 
			
		||||
import { getDomainByName } from '../../../../rest/domainAPI';
 | 
			
		||||
import {
 | 
			
		||||
  getDomainByName,
 | 
			
		||||
  removeAssetsFromDomain,
 | 
			
		||||
} from '../../../../rest/domainAPI';
 | 
			
		||||
import {
 | 
			
		||||
  getGlossaryTermByFQN,
 | 
			
		||||
  removeAssetsFromGlossaryTerm,
 | 
			
		||||
} from '../../../../rest/glossaryAPI';
 | 
			
		||||
import { searchData } from '../../../../rest/miscAPI';
 | 
			
		||||
import { searchQuery } from '../../../../rest/searchAPI';
 | 
			
		||||
import { getAssetsPageQuickFilters } from '../../../../utils/AdvancedSearchUtils';
 | 
			
		||||
import { updateDomainAssets } from '../../../../utils/Assets/AssetsUtils';
 | 
			
		||||
import { getCountBadge, Transi18next } from '../../../../utils/CommonUtils';
 | 
			
		||||
import {
 | 
			
		||||
  getEntityName,
 | 
			
		||||
@ -88,12 +89,11 @@ import {
 | 
			
		||||
  getAggregations,
 | 
			
		||||
  getSelectedValuesFromQuickFilter,
 | 
			
		||||
} from '../../../../utils/Explore.utils';
 | 
			
		||||
import { getEntityTypeFromSearchIndex } from '../../../../utils/SearchUtils';
 | 
			
		||||
import {
 | 
			
		||||
  escapeESReservedCharacters,
 | 
			
		||||
  getDecodedFqn,
 | 
			
		||||
  getEncodedFqn,
 | 
			
		||||
} from '../../../../utils/StringsUtils';
 | 
			
		||||
import { getEntityIcon } from '../../../../utils/TableUtils';
 | 
			
		||||
import { showErrorToast } from '../../../../utils/ToastUtils';
 | 
			
		||||
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
 | 
			
		||||
import { ManageButtonItemLabel } from '../../../common/ManageButtonContentItem/ManageButtonContentItem.component';
 | 
			
		||||
@ -124,24 +124,25 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      isSummaryPanelOpen,
 | 
			
		||||
      onAddAsset,
 | 
			
		||||
      onRemoveAsset,
 | 
			
		||||
      assetCount,
 | 
			
		||||
      queryFilter,
 | 
			
		||||
      isEntityDeleted = false,
 | 
			
		||||
      type = AssetsOfEntity.GLOSSARY,
 | 
			
		||||
      noDataPlaceholder,
 | 
			
		||||
      entityFqn,
 | 
			
		||||
      assetCount,
 | 
			
		||||
    }: AssetsTabsProps,
 | 
			
		||||
    ref
 | 
			
		||||
  ) => {
 | 
			
		||||
    const popupRef = React.useRef<HTMLElement>(null);
 | 
			
		||||
    const [itemCount, setItemCount] = useState<Record<EntityType, number>>(
 | 
			
		||||
      {} as Record<EntityType, number>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const [activeFilter, setActiveFilter] = useState<SearchIndex[]>([]);
 | 
			
		||||
    const [activeFilter, _] = useState<SearchIndex[]>([]);
 | 
			
		||||
    const { fqn } = useParams<{ fqn: string }>();
 | 
			
		||||
    const [isLoading, setIsLoading] = useState(true);
 | 
			
		||||
    const [data, setData] = useState<SearchedDataProps['data']>([]);
 | 
			
		||||
    const [quickFilterQuery, setQuickFilterQuery] =
 | 
			
		||||
      useState<Record<string, unknown>>();
 | 
			
		||||
    const {
 | 
			
		||||
      currentPage,
 | 
			
		||||
      pageSize,
 | 
			
		||||
@ -175,43 +176,54 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
    const [selectedItems, setSelectedItems] =
 | 
			
		||||
      useState<Map<string, EntityDetailUnion>>();
 | 
			
		||||
    const [aggregations, setAggregations] = useState<Aggregations>();
 | 
			
		||||
    const [selectedFilter, setSelectedFilter] = useState<string[]>([]); // Contains menu selection
 | 
			
		||||
    const [selectedQuickFilters, setSelectedQuickFilters] = useState<
 | 
			
		||||
      ExploreQuickFilterField[]
 | 
			
		||||
    >([] as ExploreQuickFilterField[]);
 | 
			
		||||
    >([]);
 | 
			
		||||
    const [filters, setFilters] = useState<ExploreQuickFilterField[]>([]);
 | 
			
		||||
    const [searchValue, setSearchValue] = useState('');
 | 
			
		||||
    const entityTypeString =
 | 
			
		||||
      type === AssetsOfEntity.GLOSSARY
 | 
			
		||||
        ? t('label.glossary-term-lowercase')
 | 
			
		||||
        : type === AssetsOfEntity.DOMAIN
 | 
			
		||||
        ? t('label.domain-lowercase')
 | 
			
		||||
        : t('label.data-product-lowercase');
 | 
			
		||||
 | 
			
		||||
    const parsedQueryString = Qs.parse(
 | 
			
		||||
      location.search.startsWith('?')
 | 
			
		||||
        ? location.search.substr(1)
 | 
			
		||||
        : location.search
 | 
			
		||||
    const handleMenuClick = ({ key }: { key: string }) => {
 | 
			
		||||
      setSelectedFilter((prevSelected) => [...prevSelected, key]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const filterMenu = useMemo(
 | 
			
		||||
      () => (
 | 
			
		||||
        <Menu selectedKeys={selectedFilter} onClick={handleMenuClick}>
 | 
			
		||||
          {filters.map((filter) => (
 | 
			
		||||
            <Menu.Item key={filter.key}>{filter.label}</Menu.Item>
 | 
			
		||||
          ))}
 | 
			
		||||
        </Menu>
 | 
			
		||||
      ),
 | 
			
		||||
      [selectedFilter, filters]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const searchQuery = isString(parsedQueryString.search)
 | 
			
		||||
      ? parsedQueryString.search
 | 
			
		||||
      : '';
 | 
			
		||||
 | 
			
		||||
    const [searchValue, setSearchValue] = useState(searchQuery);
 | 
			
		||||
 | 
			
		||||
    const queryParam = useMemo(() => {
 | 
			
		||||
      const encodedFqn = getEncodedFqn(escapeESReservedCharacters(entityFqn));
 | 
			
		||||
      switch (type) {
 | 
			
		||||
        case AssetsOfEntity.DOMAIN:
 | 
			
		||||
          return `(domain.fullyQualifiedName:"${escapeESReservedCharacters(
 | 
			
		||||
            entityFqn
 | 
			
		||||
          )}") AND !(entityType:"dataProduct")`;
 | 
			
		||||
          return `(domain.fullyQualifiedName:"${encodedFqn}") AND !(entityType:"dataProduct")`;
 | 
			
		||||
 | 
			
		||||
        case AssetsOfEntity.DATA_PRODUCT:
 | 
			
		||||
          return `(dataProducts.fullyQualifiedName:"${escapeESReservedCharacters(
 | 
			
		||||
            entityFqn
 | 
			
		||||
          )}")`;
 | 
			
		||||
          return `(dataProducts.fullyQualifiedName:"${encodedFqn}")`;
 | 
			
		||||
 | 
			
		||||
        case AssetsOfEntity.TEAM:
 | 
			
		||||
          return `(owner.fullyQualifiedName:"${fqn}")`;
 | 
			
		||||
          return `(owner.fullyQualifiedName:"${escapeESReservedCharacters(
 | 
			
		||||
            fqn
 | 
			
		||||
          )}")`;
 | 
			
		||||
 | 
			
		||||
        case AssetsOfEntity.MY_DATA:
 | 
			
		||||
        case AssetsOfEntity.FOLLOWING:
 | 
			
		||||
          return queryFilter ?? '';
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
          return `(tags.tagFQN:"${escapeESReservedCharacters(entityFqn)}")`;
 | 
			
		||||
          return `(tags.tagFQN:"${encodedFqn}")`;
 | 
			
		||||
      }
 | 
			
		||||
    }, [type, fqn, entityFqn]);
 | 
			
		||||
 | 
			
		||||
@ -225,45 +237,46 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      }) => {
 | 
			
		||||
        try {
 | 
			
		||||
          setIsLoading(true);
 | 
			
		||||
          const res = await searchData(
 | 
			
		||||
            searchValue,
 | 
			
		||||
            page,
 | 
			
		||||
            pageSize,
 | 
			
		||||
            queryParam,
 | 
			
		||||
            '',
 | 
			
		||||
            '',
 | 
			
		||||
            index
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          setAggregations(getAggregations(res?.data.aggregations));
 | 
			
		||||
 | 
			
		||||
          // Extract useful details from the Response
 | 
			
		||||
          const totalCount = res?.data?.hits?.total.value ?? 0;
 | 
			
		||||
          const hits = res?.data?.hits?.hits;
 | 
			
		||||
          const res = await searchQuery({
 | 
			
		||||
            pageNumber: page,
 | 
			
		||||
            pageSize: pageSize,
 | 
			
		||||
            searchIndex: index,
 | 
			
		||||
            query: `*${searchValue}*`,
 | 
			
		||||
            filters: queryParam,
 | 
			
		||||
            queryFilter: quickFilterQuery,
 | 
			
		||||
          });
 | 
			
		||||
          const hits = res.hits.hits as SearchedDataProps['data'];
 | 
			
		||||
          const totalCount = res?.hits?.total.value ?? 0;
 | 
			
		||||
 | 
			
		||||
          // Find EntityType for selected searchIndex
 | 
			
		||||
          const entityType = AssetsFilterOptions.find((f) =>
 | 
			
		||||
            activeFilter.includes(f.value)
 | 
			
		||||
          )?.label;
 | 
			
		||||
 | 
			
		||||
          // Update states
 | 
			
		||||
          handlePagingChange({ total: totalCount });
 | 
			
		||||
          entityType &&
 | 
			
		||||
            setItemCount((prevCount) => ({
 | 
			
		||||
              ...prevCount,
 | 
			
		||||
              [entityType]: totalCount,
 | 
			
		||||
            }));
 | 
			
		||||
          setData(hits as SearchedDataProps['data']);
 | 
			
		||||
 | 
			
		||||
          // Select first card to show summary right panel
 | 
			
		||||
          hits[0] && setSelectedCard(hits[0]._source as SourceType);
 | 
			
		||||
          handlePagingChange({ total: res.hits.total.value ?? 0 });
 | 
			
		||||
          setData(hits);
 | 
			
		||||
          setAggregations(getAggregations(res?.aggregations));
 | 
			
		||||
          hits[0] && setSelectedCard(hits[0]._source);
 | 
			
		||||
        } catch (_) {
 | 
			
		||||
          // Nothing here
 | 
			
		||||
        } finally {
 | 
			
		||||
          setIsLoading(false);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      [activeFilter, currentPage, pageSize, searchValue]
 | 
			
		||||
      [
 | 
			
		||||
        activeFilter,
 | 
			
		||||
        currentPage,
 | 
			
		||||
        pageSize,
 | 
			
		||||
        searchValue,
 | 
			
		||||
        queryParam,
 | 
			
		||||
        quickFilterQuery,
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const hideNotification = () => {
 | 
			
		||||
@ -284,14 +297,6 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
    const handleAssetButtonVisibleChange = (newVisible: boolean) =>
 | 
			
		||||
      setVisible(newVisible);
 | 
			
		||||
 | 
			
		||||
    const handleActiveFilter = (key: SearchIndex) => {
 | 
			
		||||
      if (activeFilter.includes(key)) {
 | 
			
		||||
        setActiveFilter((prev) => prev.filter((item) => item !== key));
 | 
			
		||||
      } else {
 | 
			
		||||
        setActiveFilter((prev) => [...prev, key]);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const fetchCurrentEntity = useCallback(async () => {
 | 
			
		||||
      let data;
 | 
			
		||||
      const fqn = encodeURIComponent(entityFqn ?? '');
 | 
			
		||||
@ -339,20 +344,12 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      });
 | 
			
		||||
    }, [activeFilter, itemCount]);
 | 
			
		||||
 | 
			
		||||
    const getAssetMenuCount = useCallback(
 | 
			
		||||
      (key: EntityType) =>
 | 
			
		||||
        ASSET_SUB_MENU_FILTER.find((item) => item.key === key)
 | 
			
		||||
          ?.children.map((item) => itemCount[item.value] ?? 0)
 | 
			
		||||
          ?.reduce((acc, cv) => acc + cv, 0),
 | 
			
		||||
      [itemCount]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const items: ItemType[] = [
 | 
			
		||||
      {
 | 
			
		||||
        label: (
 | 
			
		||||
          <ManageButtonItemLabel
 | 
			
		||||
            description={t('message.delete-asset-from-entity-type', {
 | 
			
		||||
              entityType: t('label.glossary-term-lowercase'),
 | 
			
		||||
              entityType: entityTypeString,
 | 
			
		||||
            })}
 | 
			
		||||
            icon={<DeleteIcon color={DE_ACTIVE_COLOR} width="18px" />}
 | 
			
		||||
            id="delete-button"
 | 
			
		||||
@ -368,85 +365,11 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const getOptions = useCallback(
 | 
			
		||||
      (
 | 
			
		||||
        option: {
 | 
			
		||||
          label: string;
 | 
			
		||||
          key: EntityType | SearchIndex;
 | 
			
		||||
          value?: EntityType;
 | 
			
		||||
        },
 | 
			
		||||
        isChildren?: boolean
 | 
			
		||||
      ) => {
 | 
			
		||||
        const assetCount = isChildren
 | 
			
		||||
          ? itemCount[option.value as EntityType]
 | 
			
		||||
          : getAssetMenuCount(option.key as EntityType);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          label: isChildren ? (
 | 
			
		||||
            <div className="w-full d-flex justify-between">
 | 
			
		||||
              <div className="w-full d-flex items-center justify-between p-r-xss">
 | 
			
		||||
                <span className="d-flex items-center">
 | 
			
		||||
                  <span className="m-r-xs w-4 d-flex">
 | 
			
		||||
                    {getEntityIcon(option.key)}
 | 
			
		||||
                  </span>
 | 
			
		||||
 | 
			
		||||
                  <Typography.Text
 | 
			
		||||
                    className="asset-sub-menu-title text-color-inherit"
 | 
			
		||||
                    ellipsis={{ tooltip: true }}>
 | 
			
		||||
                    {option.label}
 | 
			
		||||
                  </Typography.Text>
 | 
			
		||||
                </span>
 | 
			
		||||
 | 
			
		||||
                <span>
 | 
			
		||||
                  {getCountBadge(assetCount, 'asset-badge-container')}
 | 
			
		||||
                </span>
 | 
			
		||||
              </div>
 | 
			
		||||
              <Checkbox
 | 
			
		||||
                checked={activeFilter.includes(option.key as SearchIndex)}
 | 
			
		||||
                className="asset-sub-menu-checkbox"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <div className="d-flex justify-between">
 | 
			
		||||
              <span>{option.label}</span>
 | 
			
		||||
              <span>{getCountBadge(assetCount, 'asset-badge-container')}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          ),
 | 
			
		||||
          key: option.key,
 | 
			
		||||
          value: option.key,
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
      [
 | 
			
		||||
        getEntityIcon,
 | 
			
		||||
        handlePageChange,
 | 
			
		||||
        handleActiveFilter,
 | 
			
		||||
        setSelectedCard,
 | 
			
		||||
        activeFilter,
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const onExploreCardDelete = useCallback((source: SourceType) => {
 | 
			
		||||
      setAssetToDelete(source);
 | 
			
		||||
      setShowDeleteModal(true);
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const filteredAssetMenus = useMemo(() => {
 | 
			
		||||
      switch (type) {
 | 
			
		||||
        case AssetsOfEntity.DOMAIN:
 | 
			
		||||
          return ASSET_SUB_MENU_FILTER.filter(
 | 
			
		||||
            (item) => item.key !== EntityType.DOMAIN
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        case AssetsOfEntity.GLOSSARY:
 | 
			
		||||
          return ASSET_SUB_MENU_FILTER.filter(
 | 
			
		||||
            (item) => item.key !== EntityType.GOVERN
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
          return ASSET_SUB_MENU_FILTER;
 | 
			
		||||
      }
 | 
			
		||||
    }, [type]);
 | 
			
		||||
 | 
			
		||||
    const handleCheckboxChange = (
 | 
			
		||||
      selected: boolean,
 | 
			
		||||
      source: EntityDetailUnion
 | 
			
		||||
@ -463,36 +386,26 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const subMenuItems = useMemo(() => {
 | 
			
		||||
      return filteredAssetMenus.map((option) => ({
 | 
			
		||||
        ...getOptions(option),
 | 
			
		||||
        children: option.children.map((item) => getOptions(item, true)),
 | 
			
		||||
      }));
 | 
			
		||||
    }, [filteredAssetMenus, getOptions]);
 | 
			
		||||
 | 
			
		||||
    const fetchCountsByEntity = async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        setIsCountLoading(true);
 | 
			
		||||
        const res = await searchData(
 | 
			
		||||
          '',
 | 
			
		||||
          0,
 | 
			
		||||
          0,
 | 
			
		||||
          queryParam,
 | 
			
		||||
          '',
 | 
			
		||||
          '',
 | 
			
		||||
          SearchIndex.ALL
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const buckets = res.data.aggregations[`sterms#index_count`].buckets;
 | 
			
		||||
        const res = await searchQuery({
 | 
			
		||||
          query: `*${searchValue}*`,
 | 
			
		||||
          pageNumber: 0,
 | 
			
		||||
          pageSize: 0,
 | 
			
		||||
          queryFilter: quickFilterQuery,
 | 
			
		||||
          searchIndex: SearchIndex.ALL,
 | 
			
		||||
          filters: queryParam,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const buckets = res.aggregations[`index_count`].buckets;
 | 
			
		||||
        const counts: Record<string, number> = {};
 | 
			
		||||
        buckets.forEach((item) => {
 | 
			
		||||
          if (item) {
 | 
			
		||||
            counts[
 | 
			
		||||
              getEntityTypeFromSearchIndex(item?.key) ?? EntityType.TABLE
 | 
			
		||||
            ] = item.doc_count;
 | 
			
		||||
            counts[item.key ?? ''] = item.doc_count;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        setItemCount(counts as Record<EntityType, number>);
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        showErrorToast(err as AxiosError);
 | 
			
		||||
@ -538,7 +451,7 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
            type={ERROR_PLACEHOLDER_TYPE.FILTER}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
      } else if (noDataPlaceholder || searchValue) {
 | 
			
		||||
      } else if (noDataPlaceholder || searchValue || !permissions.Create) {
 | 
			
		||||
        return (
 | 
			
		||||
          <ErrorPlaceHolder>
 | 
			
		||||
            {isObject(noDataPlaceholder) && (
 | 
			
		||||
@ -574,23 +487,28 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            </Typography.Paragraph>
 | 
			
		||||
            <Tooltip
 | 
			
		||||
              placement="top"
 | 
			
		||||
              title={
 | 
			
		||||
                isEntityDeleted
 | 
			
		||||
                  ? t('message.this-action-is-not-allowed-for-deleted-entities')
 | 
			
		||||
                  : t('label.add')
 | 
			
		||||
              }>
 | 
			
		||||
              <Button
 | 
			
		||||
                ghost
 | 
			
		||||
                data-testid="add-placeholder-button"
 | 
			
		||||
                disabled={isEntityDeleted}
 | 
			
		||||
                icon={<PlusOutlined />}
 | 
			
		||||
                type="primary"
 | 
			
		||||
                onClick={onAddAsset}>
 | 
			
		||||
                {t('label.add')}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
 | 
			
		||||
            {permissions.Create && (
 | 
			
		||||
              <Tooltip
 | 
			
		||||
                placement="top"
 | 
			
		||||
                title={
 | 
			
		||||
                  isEntityDeleted
 | 
			
		||||
                    ? t(
 | 
			
		||||
                        'message.this-action-is-not-allowed-for-deleted-entities'
 | 
			
		||||
                      )
 | 
			
		||||
                    : t('label.add')
 | 
			
		||||
                }>
 | 
			
		||||
                <Button
 | 
			
		||||
                  ghost
 | 
			
		||||
                  data-testid="add-placeholder-button"
 | 
			
		||||
                  disabled={isEntityDeleted}
 | 
			
		||||
                  icon={<PlusOutlined />}
 | 
			
		||||
                  type="primary"
 | 
			
		||||
                  onClick={onAddAsset}>
 | 
			
		||||
                  {t('label.add')}
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Tooltip>
 | 
			
		||||
            )}
 | 
			
		||||
          </ErrorPlaceHolder>
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
@ -607,13 +525,59 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      return <div data-testid="manage-dropdown-list-container">{menus}</div>;
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const handleQuickFiltersChange = (data: ExploreQuickFilterField[]) => {
 | 
			
		||||
      const must: QueryFieldInterface[] = [];
 | 
			
		||||
      data.forEach((filter) => {
 | 
			
		||||
        if (!isEmpty(filter.value)) {
 | 
			
		||||
          const should: QueryFieldValueInterface[] = [];
 | 
			
		||||
          if (filter.value) {
 | 
			
		||||
            filter.value.forEach((filterValue) => {
 | 
			
		||||
              const term: Record<string, string> = {};
 | 
			
		||||
              term[filter.key] = filterValue.key;
 | 
			
		||||
              should.push({ term });
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          must.push({
 | 
			
		||||
            bool: { should },
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const quickFilterQuery = isEmpty(must)
 | 
			
		||||
        ? undefined
 | 
			
		||||
        : {
 | 
			
		||||
            query: { bool: { must } },
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
      setQuickFilterQuery(quickFilterQuery);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleQuickFiltersValueSelect = useCallback(
 | 
			
		||||
      (field: ExploreQuickFilterField) => {
 | 
			
		||||
        setSelectedQuickFilters((pre) => {
 | 
			
		||||
          const data = pre.map((preField) => {
 | 
			
		||||
            if (preField.key === field.key) {
 | 
			
		||||
              return field;
 | 
			
		||||
            } else {
 | 
			
		||||
              return preField;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          handleQuickFiltersChange(data);
 | 
			
		||||
 | 
			
		||||
          return data;
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      [setSelectedQuickFilters]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const assetListing = useMemo(
 | 
			
		||||
      () =>
 | 
			
		||||
        data.length ? (
 | 
			
		||||
          <div className="assets-data-container p-t-sm">
 | 
			
		||||
            {data.map(({ _source, _id = '' }) => (
 | 
			
		||||
              <ExploreSearchCard
 | 
			
		||||
                showCheckboxes
 | 
			
		||||
                showEntityIcon
 | 
			
		||||
                actionPopoverContent={
 | 
			
		||||
                  isRemovable && permissions.EditAll ? (
 | 
			
		||||
@ -627,7 +591,7 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
                      trigger={['click']}>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        className={classNames('flex-center px-1.5')}
 | 
			
		||||
                        data-testid="manage-button"
 | 
			
		||||
                        data-testid={`manage-button-${_source.fullyQualifiedName}`}
 | 
			
		||||
                        title="Manage"
 | 
			
		||||
                        type="text">
 | 
			
		||||
                        <IconDropdown className="anticon self-center manage-dropdown-icon" />
 | 
			
		||||
@ -643,6 +607,7 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
                handleSummaryPanelDisplay={setSelectedCard}
 | 
			
		||||
                id={_id}
 | 
			
		||||
                key={'assets_' + _id}
 | 
			
		||||
                showCheckboxes={Boolean(activeEntity) && permissions.Create}
 | 
			
		||||
                showTags={false}
 | 
			
		||||
                source={_source}
 | 
			
		||||
                onCheckboxChange={(selected) =>
 | 
			
		||||
@ -669,6 +634,7 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      [
 | 
			
		||||
        type,
 | 
			
		||||
        data,
 | 
			
		||||
        activeEntity,
 | 
			
		||||
        permissions,
 | 
			
		||||
        paging,
 | 
			
		||||
        currentPage,
 | 
			
		||||
@ -706,7 +672,7 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
    const assetsHeader = useMemo(() => {
 | 
			
		||||
      return (
 | 
			
		||||
        <div className="w-full d-flex justify-between items-center p-l-sm">
 | 
			
		||||
          {data.length > 0 && (
 | 
			
		||||
          {activeEntity && permissions.Create && data.length > 0 && (
 | 
			
		||||
            <Checkbox
 | 
			
		||||
              className="assets-checkbox p-x-sm"
 | 
			
		||||
              onChange={(e) => onSelectAll(e.target.checked)}>
 | 
			
		||||
@ -715,53 +681,11 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
              })}
 | 
			
		||||
            </Checkbox>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          <Popover
 | 
			
		||||
            align={{ targetOffset: [0, 10] }}
 | 
			
		||||
            content={
 | 
			
		||||
              <Menu
 | 
			
		||||
                multiple
 | 
			
		||||
                items={subMenuItems}
 | 
			
		||||
                mode="inline"
 | 
			
		||||
                openKeys={openKeys}
 | 
			
		||||
                rootClassName="asset-multi-menu-selector"
 | 
			
		||||
                selectedKeys={activeFilter}
 | 
			
		||||
                style={{ width: 256, height: 340 }}
 | 
			
		||||
                onClick={(value) => {
 | 
			
		||||
                  handlePageChange(1);
 | 
			
		||||
                  handleActiveFilter(value.key as SearchIndex);
 | 
			
		||||
                  setSelectedCard(undefined);
 | 
			
		||||
                }}
 | 
			
		||||
                onOpenChange={onOpenChange}
 | 
			
		||||
              />
 | 
			
		||||
            }
 | 
			
		||||
            getPopupContainer={(triggerNode: HTMLElement) =>
 | 
			
		||||
              popupRef.current ?? triggerNode
 | 
			
		||||
            }
 | 
			
		||||
            key="asset-options-popover"
 | 
			
		||||
            open={visible}
 | 
			
		||||
            overlayClassName="ant-popover-asset"
 | 
			
		||||
            placement="bottomRight"
 | 
			
		||||
            showArrow={false}
 | 
			
		||||
            trigger="click"
 | 
			
		||||
            onOpenChange={handleAssetButtonVisibleChange}>
 | 
			
		||||
            {Boolean(assetCount) && (
 | 
			
		||||
              <Badge count={activeFilter.length}>
 | 
			
		||||
                <Button
 | 
			
		||||
                  ghost
 | 
			
		||||
                  icon={<FilterOutlined />}
 | 
			
		||||
                  ref={popupRef}
 | 
			
		||||
                  style={{ background: 'white' }}
 | 
			
		||||
                  type="primary">
 | 
			
		||||
                  {t('label.filter-plural')}
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Badge>
 | 
			
		||||
            )}
 | 
			
		||||
          </Popover>
 | 
			
		||||
        </div>
 | 
			
		||||
      );
 | 
			
		||||
    }, [
 | 
			
		||||
      activeFilter,
 | 
			
		||||
      activeEntity,
 | 
			
		||||
      isLoading,
 | 
			
		||||
      data,
 | 
			
		||||
      openKeys,
 | 
			
		||||
@ -790,35 +714,23 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          let updatedEntity;
 | 
			
		||||
          const entities = [...(assetsData?.values() ?? [])].map((item) => {
 | 
			
		||||
            return getEntityReferenceFromEntity(
 | 
			
		||||
              item,
 | 
			
		||||
              (item as EntityDetailUnion).entityType
 | 
			
		||||
            );
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          switch (type) {
 | 
			
		||||
            case AssetsOfEntity.DATA_PRODUCT:
 | 
			
		||||
              const updatedAssets = (
 | 
			
		||||
                (activeEntity as DataProduct)?.assets ?? []
 | 
			
		||||
              ).filter(
 | 
			
		||||
                (asset) => !assetsData.some((item) => item.id === asset.id)
 | 
			
		||||
              await removeAssetsFromDataProduct(
 | 
			
		||||
                getEncodedFqn(activeEntity.fullyQualifiedName ?? ''),
 | 
			
		||||
                entities
 | 
			
		||||
              );
 | 
			
		||||
 | 
			
		||||
              updatedEntity = {
 | 
			
		||||
                ...activeEntity,
 | 
			
		||||
                assets: updatedAssets,
 | 
			
		||||
              };
 | 
			
		||||
              const jsonPatch = compare(activeEntity, updatedEntity);
 | 
			
		||||
              const res = await patchDataProduct(
 | 
			
		||||
                (activeEntity as DataProduct).id,
 | 
			
		||||
                jsonPatch
 | 
			
		||||
              );
 | 
			
		||||
              setActiveEntity(res);
 | 
			
		||||
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case AssetsOfEntity.GLOSSARY:
 | 
			
		||||
              const entities = [...(assetsData?.values() ?? [])].map((item) => {
 | 
			
		||||
                return getEntityReferenceFromEntity(
 | 
			
		||||
                  item,
 | 
			
		||||
                  (item as EntityDetailUnion).entityType
 | 
			
		||||
                );
 | 
			
		||||
              });
 | 
			
		||||
              await removeAssetsFromGlossaryTerm(
 | 
			
		||||
                activeEntity as GlossaryTerm,
 | 
			
		||||
                entities
 | 
			
		||||
@ -827,11 +739,10 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
              break;
 | 
			
		||||
 | 
			
		||||
            case AssetsOfEntity.DOMAIN:
 | 
			
		||||
              const selectedItemMap = new Map();
 | 
			
		||||
              assetsData.forEach((item) => {
 | 
			
		||||
                selectedItemMap.set(item.id, item);
 | 
			
		||||
              });
 | 
			
		||||
              await updateDomainAssets(undefined, type, selectedItemMap);
 | 
			
		||||
              await removeAssetsFromDomain(
 | 
			
		||||
                getEncodedFqn(activeEntity.fullyQualifiedName ?? ''),
 | 
			
		||||
                entities
 | 
			
		||||
              );
 | 
			
		||||
 | 
			
		||||
              break;
 | 
			
		||||
            default:
 | 
			
		||||
@ -856,17 +767,40 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      [type, activeEntity, entityFqn]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const clearFilters = useCallback(() => {
 | 
			
		||||
      setQuickFilterQuery(undefined);
 | 
			
		||||
      setSelectedQuickFilters((pre) => {
 | 
			
		||||
        const data = pre.map((preField) => {
 | 
			
		||||
          return { ...preField, value: [] };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        handleQuickFiltersChange(data);
 | 
			
		||||
 | 
			
		||||
        return data;
 | 
			
		||||
      });
 | 
			
		||||
    }, [
 | 
			
		||||
      setQuickFilterQuery,
 | 
			
		||||
      handleQuickFiltersChange,
 | 
			
		||||
      setSelectedQuickFilters,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const openNotification = () => {
 | 
			
		||||
      notification.warning({
 | 
			
		||||
        key: 'asset-tab-notification-key',
 | 
			
		||||
        message: (
 | 
			
		||||
          <div className="d-flex justify-between">
 | 
			
		||||
          <div className="d-flex items-center justify-between">
 | 
			
		||||
            {selectedItems && selectedItems.size > 1 && (
 | 
			
		||||
              <Typography.Text>
 | 
			
		||||
                {selectedItems.size} {t('label.selected-lowercase')}
 | 
			
		||||
              <Typography.Text className="text-white">
 | 
			
		||||
                {selectedItems.size} {t('label.items-selected-lowercase')}
 | 
			
		||||
              </Typography.Text>
 | 
			
		||||
            )}
 | 
			
		||||
            <Button onClick={deleteSelectedItems}>{t('label.delete')}</Button>
 | 
			
		||||
            <Button
 | 
			
		||||
              danger
 | 
			
		||||
              data-testid="delete-all-button"
 | 
			
		||||
              type="primary"
 | 
			
		||||
              onClick={deleteSelectedItems}>
 | 
			
		||||
              {t('label.delete')}
 | 
			
		||||
            </Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        ),
 | 
			
		||||
        placement: 'bottom',
 | 
			
		||||
@ -880,12 +814,12 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
        index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter,
 | 
			
		||||
        page: currentPage,
 | 
			
		||||
      });
 | 
			
		||||
    }, [activeFilter, currentPage, pageSize, searchValue]);
 | 
			
		||||
    }, [activeFilter, currentPage, pageSize, searchValue, quickFilterQuery]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      const dropdownItems = getAssetsPageQuickFilters();
 | 
			
		||||
      const dropdownItems = getAssetsPageQuickFilters(type);
 | 
			
		||||
 | 
			
		||||
      setSelectedQuickFilters(
 | 
			
		||||
      setFilters(
 | 
			
		||||
        dropdownItems.map((item) => ({
 | 
			
		||||
          ...item,
 | 
			
		||||
          value: getSelectedValuesFromQuickFilter(
 | 
			
		||||
@ -897,6 +831,32 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      );
 | 
			
		||||
    }, [type]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      const updatedQuickFilters = filters
 | 
			
		||||
        .filter((filter) => selectedFilter.includes(filter.key))
 | 
			
		||||
        .map((selectedFilterItem) => {
 | 
			
		||||
          const originalFilterItem = selectedQuickFilters?.find(
 | 
			
		||||
            (filter) => filter.key === selectedFilterItem.key
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          return originalFilterItem || selectedFilterItem;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      const newItems = updatedQuickFilters.filter(
 | 
			
		||||
        (item) =>
 | 
			
		||||
          !selectedQuickFilters.some(
 | 
			
		||||
            (existingItem) => item.key === existingItem.key
 | 
			
		||||
          )
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (newItems.length > 0) {
 | 
			
		||||
        setSelectedQuickFilters((prevSelected) => [
 | 
			
		||||
          ...prevSelected,
 | 
			
		||||
          ...newItems,
 | 
			
		||||
        ]);
 | 
			
		||||
      }
 | 
			
		||||
    }, [selectedFilter, selectedQuickFilters, filters]);
 | 
			
		||||
 | 
			
		||||
    useImperativeHandle(ref, () => ({
 | 
			
		||||
      refreshAssets() {
 | 
			
		||||
        fetchAssets({
 | 
			
		||||
@ -926,28 +886,48 @@ const AssetsTabs = forwardRef(
 | 
			
		||||
      <div
 | 
			
		||||
        className={classNames('assets-tab-container p-md')}
 | 
			
		||||
        data-testid="table-container">
 | 
			
		||||
        <Row className="filters-row gap-2">
 | 
			
		||||
          <Col span={24}>
 | 
			
		||||
            <Searchbar
 | 
			
		||||
              removeMargin
 | 
			
		||||
              showClearSearch
 | 
			
		||||
              placeholder={t('label.search-entity', {
 | 
			
		||||
                entity: t('label.asset-plural'),
 | 
			
		||||
              })}
 | 
			
		||||
              searchValue={searchValue}
 | 
			
		||||
              onSearch={setSearchValue}
 | 
			
		||||
            />
 | 
			
		||||
          </Col>
 | 
			
		||||
          <Col className="searched-data-container" span={24}>
 | 
			
		||||
            <ExploreQuickFilters
 | 
			
		||||
              aggregations={aggregations}
 | 
			
		||||
              fields={selectedQuickFilters}
 | 
			
		||||
              index={SearchIndex.ALL}
 | 
			
		||||
              showDeleted={false}
 | 
			
		||||
              onFieldValueSelect={noop}
 | 
			
		||||
            />
 | 
			
		||||
          </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
        {assetCount > 0 && (
 | 
			
		||||
          <Row className="filters-row gap-2 p-l-lg">
 | 
			
		||||
            <Col span={18}>
 | 
			
		||||
              <div className="d-flex items-center gap-3">
 | 
			
		||||
                <Dropdown overlay={filterMenu} trigger={['click']}>
 | 
			
		||||
                  <Button icon={<PlusOutlined />} size="small" type="primary" />
 | 
			
		||||
                </Dropdown>
 | 
			
		||||
                <div className="flex-1">
 | 
			
		||||
                  <Searchbar
 | 
			
		||||
                    removeMargin
 | 
			
		||||
                    showClearSearch
 | 
			
		||||
                    placeholder={t('label.search-entity', {
 | 
			
		||||
                      entity: t('label.asset-plural'),
 | 
			
		||||
                    })}
 | 
			
		||||
                    searchValue={searchValue}
 | 
			
		||||
                    onSearch={setSearchValue}
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Col>
 | 
			
		||||
            <Col className="searched-data-container m-b-xs" span={24}>
 | 
			
		||||
              <div className="d-flex justify-between">
 | 
			
		||||
                <ExploreQuickFilters
 | 
			
		||||
                  aggregations={aggregations}
 | 
			
		||||
                  fields={selectedQuickFilters}
 | 
			
		||||
                  index={SearchIndex.ALL}
 | 
			
		||||
                  showDeleted={false}
 | 
			
		||||
                  onFieldValueSelect={handleQuickFiltersValueSelect}
 | 
			
		||||
                />
 | 
			
		||||
                {quickFilterQuery && (
 | 
			
		||||
                  <Typography.Text
 | 
			
		||||
                    className="text-primary self-center cursor-pointer"
 | 
			
		||||
                    onClick={clearFilters}>
 | 
			
		||||
                    {t('label.clear-entity', {
 | 
			
		||||
                      entity: '',
 | 
			
		||||
                    })}
 | 
			
		||||
                  </Typography.Text>
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </Col>
 | 
			
		||||
          </Row>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {isLoading || isCountLoading ? (
 | 
			
		||||
          <Row className="p-lg" gutter={[0, 16]}>
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@ import { OperationPermission } from '../../../PermissionProvider/PermissionProvi
 | 
			
		||||
import TagsContainerV2 from '../../../Tag/TagsContainerV2/TagsContainerV2';
 | 
			
		||||
import { DisplayType } from '../../../Tag/TagsViewer/TagsViewer.interface';
 | 
			
		||||
import GlossaryDetailsRightPanel from '../../GlossaryDetailsRightPanel/GlossaryDetailsRightPanel.component';
 | 
			
		||||
import { GlossaryUpdateConfirmationModal } from '../../GlossaryUpdateConfirmationModal/GlossaryUpdateConfirmationModal';
 | 
			
		||||
import GlossaryTermReferences from './GlossaryTermReferences';
 | 
			
		||||
import GlossaryTermSynonyms from './GlossaryTermSynonyms';
 | 
			
		||||
import RelatedTerms from './RelatedTerms';
 | 
			
		||||
@ -51,6 +52,7 @@ const GlossaryOverviewTab = ({
 | 
			
		||||
}: Props) => {
 | 
			
		||||
  const [isDescriptionEditable, setIsDescriptionEditable] =
 | 
			
		||||
    useState<boolean>(false);
 | 
			
		||||
  const [tagsUpdatating, setTagsUpdating] = useState<TagLabel[]>();
 | 
			
		||||
 | 
			
		||||
  const onDescriptionUpdate = async (updatedHTML: string) => {
 | 
			
		||||
    if (selectedData.description !== updatedHTML) {
 | 
			
		||||
@ -82,14 +84,7 @@ const GlossaryOverviewTab = ({
 | 
			
		||||
  }, [selectedData, isVersionView]);
 | 
			
		||||
 | 
			
		||||
  const handleTagsUpdate = async (updatedTags: TagLabel[]) => {
 | 
			
		||||
    if (updatedTags) {
 | 
			
		||||
      const updatedData = {
 | 
			
		||||
        ...selectedData,
 | 
			
		||||
        tags: updatedTags,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      onUpdate(updatedData);
 | 
			
		||||
    }
 | 
			
		||||
    setTagsUpdating(updatedTags);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const tags = useMemo(
 | 
			
		||||
@ -103,6 +98,17 @@ const GlossaryOverviewTab = ({
 | 
			
		||||
    [isVersionView, selectedData]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleGlossaryTagUpdateValidationConfirm = async () => {
 | 
			
		||||
    if (selectedData) {
 | 
			
		||||
      await onUpdate({
 | 
			
		||||
        ...selectedData,
 | 
			
		||||
        tags: tagsUpdatating,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setTagsUpdating(undefined);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Row className="glossary-overview-tab h-full" gutter={[32, 16]}>
 | 
			
		||||
      <Col
 | 
			
		||||
@ -185,6 +191,14 @@ const GlossaryOverviewTab = ({
 | 
			
		||||
          onUpdate={onUpdate}
 | 
			
		||||
        />
 | 
			
		||||
      </Col>
 | 
			
		||||
      {tagsUpdatating && (
 | 
			
		||||
        <GlossaryUpdateConfirmationModal
 | 
			
		||||
          glossaryTerm={selectedData as GlossaryTerm}
 | 
			
		||||
          updatedTags={tagsUpdatating}
 | 
			
		||||
          onCancel={() => setTagsUpdating(undefined)}
 | 
			
		||||
          onValidationSuccess={handleGlossaryTagUpdateValidationConfirm}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    </Row>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -79,3 +79,22 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.asset-tab-delete-notification {
 | 
			
		||||
  background-color: @text-color !important;
 | 
			
		||||
  box-shadow: none !important;
 | 
			
		||||
  .ant-notification-notice-icon {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
  .ant-notification-notice-close {
 | 
			
		||||
    color: @white !important;
 | 
			
		||||
    left: 24px;
 | 
			
		||||
    top: 22px;
 | 
			
		||||
    width: 20px;
 | 
			
		||||
  }
 | 
			
		||||
  .ant-notification-notice-message {
 | 
			
		||||
    padding-right: 0 !important;
 | 
			
		||||
    margin-left: 20px !important;
 | 
			
		||||
    font-size: 14px !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed 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 { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
 | 
			
		||||
import { TagLabel } from '../../../generated/entity/data/table';
 | 
			
		||||
 | 
			
		||||
export interface GlossaryUpdateConfirmationModalProps {
 | 
			
		||||
  glossaryTerm: GlossaryTerm;
 | 
			
		||||
  onValidationSuccess: (() => void) | (() => Promise<void>);
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  updatedTags: TagLabel[];
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,90 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed 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 { act, fireEvent, render } from '@testing-library/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
 | 
			
		||||
import { GlossaryUpdateConfirmationModal } from './GlossaryUpdateConfirmationModal';
 | 
			
		||||
 | 
			
		||||
const mockOnCancel = jest.fn();
 | 
			
		||||
const mockOnValidationSuccess = jest.fn();
 | 
			
		||||
const mockValidateTagAddtionToGlossary = jest.fn().mockResolvedValue({});
 | 
			
		||||
 | 
			
		||||
jest.mock('../../../rest/glossaryAPI', () => ({
 | 
			
		||||
  validateTagAddtionToGlossary: mockValidateTagAddtionToGlossary,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
jest.mock('../../../utils/EntityUtils', () => ({
 | 
			
		||||
  getEntityLinkFromType: jest.fn(),
 | 
			
		||||
  getEntityName: jest.fn(),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
jest.mock('../../common/Table/Table', () => {
 | 
			
		||||
  return jest.fn();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('GlossaryUpdateConfirmationModal component', () => {
 | 
			
		||||
  it('should render confirmation screen', async () => {
 | 
			
		||||
    const { findByText } = render(
 | 
			
		||||
      <GlossaryUpdateConfirmationModal
 | 
			
		||||
        glossaryTerm={{} as GlossaryTerm}
 | 
			
		||||
        updatedTags={[]}
 | 
			
		||||
        onCancel={mockOnCancel}
 | 
			
		||||
        onValidationSuccess={mockOnValidationSuccess}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(await findByText('label.no-comma-cancel')).toBeInTheDocument();
 | 
			
		||||
    expect(await findByText('label.yes-comma-confirm')).toBeInTheDocument();
 | 
			
		||||
    expect(
 | 
			
		||||
      await findByText('message.tag-update-confirmation')
 | 
			
		||||
    ).toBeInTheDocument();
 | 
			
		||||
    expect(
 | 
			
		||||
      await findByText('message.glossary-tag-update-description')
 | 
			
		||||
    ).toBeInTheDocument();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should call onCancel on clicking on no, cancel button', async () => {
 | 
			
		||||
    const { findByText } = render(
 | 
			
		||||
      <GlossaryUpdateConfirmationModal
 | 
			
		||||
        glossaryTerm={{} as GlossaryTerm}
 | 
			
		||||
        updatedTags={[]}
 | 
			
		||||
        onCancel={mockOnCancel}
 | 
			
		||||
        onValidationSuccess={mockOnValidationSuccess}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    fireEvent.click(await findByText('label.no-comma-cancel'));
 | 
			
		||||
 | 
			
		||||
    expect(mockOnCancel).toHaveBeenCalled();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it.skip('should call validation api on clicking on yes, confirm button', async () => {
 | 
			
		||||
    const { findByText } = render(
 | 
			
		||||
      <GlossaryUpdateConfirmationModal
 | 
			
		||||
        glossaryTerm={{} as GlossaryTerm}
 | 
			
		||||
        updatedTags={[]}
 | 
			
		||||
        onCancel={mockOnCancel}
 | 
			
		||||
        onValidationSuccess={mockOnValidationSuccess}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    await act(async () => {
 | 
			
		||||
      fireEvent.click(await findByText('label.yes-comma-confirm'));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(mockValidateTagAddtionToGlossary).toHaveBeenCalledWith(
 | 
			
		||||
      { tags: [] },
 | 
			
		||||
      true
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,176 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright 2023 Collate.
 | 
			
		||||
 *  Licensed 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 Icon from '@ant-design/icons';
 | 
			
		||||
import { Button, Modal, Space, Typography } from 'antd';
 | 
			
		||||
import { isEmpty } from 'lodash';
 | 
			
		||||
import React, { useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { ReactComponent as ExclamationIcon } from '../../../assets/svg/ic-exclamation-circle.svg';
 | 
			
		||||
import { ClientErrors } from '../../../enums/axios.enum';
 | 
			
		||||
import { EntityType } from '../../../enums/entity.enum';
 | 
			
		||||
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
 | 
			
		||||
import { EntityReference } from '../../../generated/entity/type';
 | 
			
		||||
import {
 | 
			
		||||
  BulkOperationResult,
 | 
			
		||||
  Status,
 | 
			
		||||
} from '../../../generated/type/bulkOperationResult';
 | 
			
		||||
import { validateTagAddtionToGlossary } from '../../../rest/glossaryAPI';
 | 
			
		||||
import {
 | 
			
		||||
  getEntityLinkFromType,
 | 
			
		||||
  getEntityName,
 | 
			
		||||
} from '../../../utils/EntityUtils';
 | 
			
		||||
import Table from '../../common/Table/Table';
 | 
			
		||||
import { GlossaryUpdateConfirmationModalProps } from './GlossaryUpdateConfirmationModal.interface';
 | 
			
		||||
 | 
			
		||||
export const GlossaryUpdateConfirmationModal = ({
 | 
			
		||||
  glossaryTerm,
 | 
			
		||||
  onValidationSuccess,
 | 
			
		||||
  onCancel,
 | 
			
		||||
  updatedTags,
 | 
			
		||||
}: GlossaryUpdateConfirmationModalProps) => {
 | 
			
		||||
  const [failedStatus, setFailedStatus] = useState<BulkOperationResult>();
 | 
			
		||||
  const [tagError, setTagError] = useState<{ code: number; message: string }>();
 | 
			
		||||
  const [tagAdditionConfirmation, setTagAdditionConfirmation] = useState(false);
 | 
			
		||||
  const [validating, setValidating] = useState(false);
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const handleUpdateConfirmation = async () => {
 | 
			
		||||
    setTagAdditionConfirmation(true);
 | 
			
		||||
    setValidating(true);
 | 
			
		||||
    try {
 | 
			
		||||
      // dryRun validations so that we can list failures if any
 | 
			
		||||
      const res = await validateTagAddtionToGlossary(
 | 
			
		||||
        { ...glossaryTerm, tags: updatedTags } as GlossaryTerm,
 | 
			
		||||
        true
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (res.status && res.status === Status.Success) {
 | 
			
		||||
        await onValidationSuccess();
 | 
			
		||||
      } else {
 | 
			
		||||
        setFailedStatus(res);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      // error
 | 
			
		||||
      setTagError(err.response?.data);
 | 
			
		||||
    } finally {
 | 
			
		||||
      setValidating(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const tagsColumn = useMemo(() => {
 | 
			
		||||
    return [
 | 
			
		||||
      {
 | 
			
		||||
        title: t('label.asset-plural'),
 | 
			
		||||
        dataIndex: 'request',
 | 
			
		||||
        key: 'request',
 | 
			
		||||
        render: (record: EntityReference) => (
 | 
			
		||||
          <Link
 | 
			
		||||
            target="_blank"
 | 
			
		||||
            to={getEntityLinkFromType(
 | 
			
		||||
              record.fullyQualifiedName ?? '',
 | 
			
		||||
              record.type as EntityType
 | 
			
		||||
            )}>
 | 
			
		||||
            {record.fullyQualifiedName}
 | 
			
		||||
          </Link>
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: t('label.failure-reason'),
 | 
			
		||||
        dataIndex: 'message',
 | 
			
		||||
        key: 'message',
 | 
			
		||||
        render: (error: string) => (
 | 
			
		||||
          <Typography.Paragraph>{error}</Typography.Paragraph>
 | 
			
		||||
        ),
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Modal
 | 
			
		||||
      centered
 | 
			
		||||
      open
 | 
			
		||||
      closable={false}
 | 
			
		||||
      closeIcon={null}
 | 
			
		||||
      footer={
 | 
			
		||||
        tagAdditionConfirmation && (
 | 
			
		||||
          <div className="d-flex justify-between">
 | 
			
		||||
            <Typography.Text type="secondary">
 | 
			
		||||
              {failedStatus?.numberOfRowsFailed &&
 | 
			
		||||
                `${failedStatus.numberOfRowsFailed} ${t('label.failed')}`}
 | 
			
		||||
            </Typography.Text>
 | 
			
		||||
            <Button onClick={onCancel}>{t('label.cancel')}</Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      title={
 | 
			
		||||
        tagAdditionConfirmation
 | 
			
		||||
          ? t('message.glossary-tag-update-modal-title')
 | 
			
		||||
          : undefined
 | 
			
		||||
      }
 | 
			
		||||
      width={tagAdditionConfirmation ? 750 : undefined}
 | 
			
		||||
      onCancel={onCancel}>
 | 
			
		||||
      {tagAdditionConfirmation || validating ? (
 | 
			
		||||
        <div className="d-flex flex-column gap-2">
 | 
			
		||||
          {!isEmpty(failedStatus?.failedRequest) && !validating && (
 | 
			
		||||
            <>
 | 
			
		||||
              <Table
 | 
			
		||||
                bordered
 | 
			
		||||
                columns={tagsColumn}
 | 
			
		||||
                dataSource={failedStatus?.failedRequest}
 | 
			
		||||
                loading={validating}
 | 
			
		||||
                pagination={{
 | 
			
		||||
                  pageSize: 5,
 | 
			
		||||
                  showSizeChanger: true,
 | 
			
		||||
                }}
 | 
			
		||||
                rowKey={(record) => record.request.id}
 | 
			
		||||
              />
 | 
			
		||||
              <Typography.Text italic className="m-t-sm" type="secondary">
 | 
			
		||||
                {t('message.glossary-tag-assignement-help-message')}
 | 
			
		||||
              </Typography.Text>
 | 
			
		||||
            </>
 | 
			
		||||
          )}
 | 
			
		||||
          {tagError?.code === ClientErrors.BAD_REQUEST && (
 | 
			
		||||
            <Typography.Text type="danger">{tagError.message}</Typography.Text>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <div className="d-flex items-center flex-column gap-2">
 | 
			
		||||
          <Icon
 | 
			
		||||
            className="m-b-lg"
 | 
			
		||||
            component={ExclamationIcon}
 | 
			
		||||
            style={{ fontSize: '60px' }}
 | 
			
		||||
          />
 | 
			
		||||
          <Typography.Title level={5}>
 | 
			
		||||
            {t('message.tag-update-confirmation')}
 | 
			
		||||
          </Typography.Title>
 | 
			
		||||
          <Typography.Text className="text-center">
 | 
			
		||||
            {t('message.glossary-tag-update-description')}{' '}
 | 
			
		||||
            <span className="font-medium">{getEntityName(glossaryTerm)}</span>
 | 
			
		||||
          </Typography.Text>
 | 
			
		||||
          <div className="m-t-lg">
 | 
			
		||||
            <Space size={8}>
 | 
			
		||||
              <Button onClick={onCancel}>{t('label.no-comma-cancel')}</Button>
 | 
			
		||||
              <Button
 | 
			
		||||
                loading={validating}
 | 
			
		||||
                type="primary"
 | 
			
		||||
                onClick={handleUpdateConfirmation}>
 | 
			
		||||
                {t('label.yes-comma-confirm')}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Space>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </Modal>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@ -14,19 +14,18 @@ import { Tag, Tooltip, Typography } from 'antd';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import React, { useCallback, useMemo } from 'react';
 | 
			
		||||
import { useHistory } from 'react-router-dom';
 | 
			
		||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
 | 
			
		||||
import { ROUTES } from '../../../constants/constants';
 | 
			
		||||
import { TagSource } from '../../../generated/type/tagLabel';
 | 
			
		||||
import { getTagDisplay, getTagTooltip } from '../../../utils/TagsUtils';
 | 
			
		||||
 | 
			
		||||
import { ReactComponent as IconTerm } from '../../../assets/svg/book.svg';
 | 
			
		||||
import { ReactComponent as IconTag } from '../../../assets/svg/classification.svg';
 | 
			
		||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
 | 
			
		||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
 | 
			
		||||
import { ROUTES } from '../../../constants/constants';
 | 
			
		||||
import { TAG_START_WITH } from '../../../constants/Tag.constants';
 | 
			
		||||
import { TagSource } from '../../../generated/type/tagLabel';
 | 
			
		||||
import { reduceColorOpacity } from '../../../utils/CommonUtils';
 | 
			
		||||
import { getEntityName } from '../../../utils/EntityUtils';
 | 
			
		||||
import Fqn from '../../../utils/Fqn';
 | 
			
		||||
import { getEncodedFqn } from '../../../utils/StringsUtils';
 | 
			
		||||
import { getTagDisplay, getTagTooltip } from '../../../utils/TagsUtils';
 | 
			
		||||
import { TagsV1Props } from './TagsV1.interface';
 | 
			
		||||
import './tagsV1.less';
 | 
			
		||||
 | 
			
		||||
@ -37,6 +36,7 @@ const TagsV1 = ({
 | 
			
		||||
  showOnlyName = false,
 | 
			
		||||
  isVersionPage = false,
 | 
			
		||||
  tagProps,
 | 
			
		||||
  tooltipOverride,
 | 
			
		||||
}: TagsV1Props) => {
 | 
			
		||||
  const history = useHistory();
 | 
			
		||||
  const color = useMemo(
 | 
			
		||||
@ -153,7 +153,7 @@ const TagsV1 = ({
 | 
			
		||||
  const addTagChip = useMemo(
 | 
			
		||||
    () => (
 | 
			
		||||
      <Tag
 | 
			
		||||
        className="tag-chip tag-chip-add-button"
 | 
			
		||||
        className={classNames('tag-chip tag-chip-add-button')}
 | 
			
		||||
        icon={<PlusIcon height={16} name="plus" width={16} />}>
 | 
			
		||||
        <Typography.Paragraph
 | 
			
		||||
          className="m-0 text-xs font-medium text-primary"
 | 
			
		||||
@ -174,7 +174,7 @@ const TagsV1 = ({
 | 
			
		||||
      className="cursor-pointer"
 | 
			
		||||
      mouseEnterDelay={1.5}
 | 
			
		||||
      placement="bottomLeft"
 | 
			
		||||
      title={getTagTooltip(tag.tagFQN, tag.description)}
 | 
			
		||||
      title={tooltipOverride ?? getTagTooltip(tag.tagFQN, tag.description)}
 | 
			
		||||
      trigger="hover">
 | 
			
		||||
      {tagChip}
 | 
			
		||||
    </Tooltip>
 | 
			
		||||
 | 
			
		||||
@ -22,4 +22,6 @@ export type TagsV1Props = {
 | 
			
		||||
  className?: string;
 | 
			
		||||
  isVersionPage?: boolean;
 | 
			
		||||
  tagProps?: TagProps;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  tooltipOverride?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -46,6 +46,7 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
 | 
			
		||||
          { 'diff-removed': tag?.removed }
 | 
			
		||||
        )}
 | 
			
		||||
        isVersionPage={tag?.added || tag?.removed}
 | 
			
		||||
        key={tag.tagFQN}
 | 
			
		||||
        showOnlyName={tag.source === TagSource.Glossary}
 | 
			
		||||
        startWith={TAG_START_WITH.SOURCE_ICON}
 | 
			
		||||
        tag={tag}
 | 
			
		||||
 | 
			
		||||
@ -63,14 +63,16 @@ export const OwnerLabel = ({
 | 
			
		||||
        style={{ fontSize: '18px' }}
 | 
			
		||||
      />
 | 
			
		||||
    ) : (
 | 
			
		||||
      <ProfilePicture
 | 
			
		||||
        displayName={displayName}
 | 
			
		||||
        id={owner.id}
 | 
			
		||||
        key="profile-picture"
 | 
			
		||||
        name={owner.name ?? ''}
 | 
			
		||||
        type="circle"
 | 
			
		||||
        width="18"
 | 
			
		||||
      />
 | 
			
		||||
      <div style={{ flexBasis: '18px' }}>
 | 
			
		||||
        <ProfilePicture
 | 
			
		||||
          displayName={displayName}
 | 
			
		||||
          id={owner.id}
 | 
			
		||||
          key="profile-picture"
 | 
			
		||||
          name={owner.name ?? ''}
 | 
			
		||||
          type="circle"
 | 
			
		||||
          width="18"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }, [owner]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -171,6 +171,29 @@ export const DATA_PRODUCT_DROPDOWN_ITEMS = [
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const DOMAIN_DATAPRODUCT_DROPDOWN_ITEMS = [
 | 
			
		||||
  {
 | 
			
		||||
    label: t('label.owner'),
 | 
			
		||||
    key: 'owner.displayName.keyword',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: t('label.tag'),
 | 
			
		||||
    key: 'tags.tagFQN',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: t('label.tier'),
 | 
			
		||||
    key: 'tier.tagFQN',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: t('label.service'),
 | 
			
		||||
    key: 'service.displayName.keyword',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: t('label.service-type'),
 | 
			
		||||
    key: 'serviceType',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const ALL_DROPDOWN_ITEMS = [
 | 
			
		||||
  ...COMMON_DROPDOWN_ITEMS,
 | 
			
		||||
  ...TABLE_DROPDOWN_ITEMS,
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@
 | 
			
		||||
export enum QueryFilterFieldsEnum {
 | 
			
		||||
  MUST = 'must',
 | 
			
		||||
  SHOULD = 'should',
 | 
			
		||||
  MUST_NOT = 'must_not',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ExplorePageTabs {
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Datenmodelle",
 | 
			
		||||
    "data-model-type": "Datenmodelltyp",
 | 
			
		||||
    "data-product": "Datenprodukt",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Datenprodukte",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "Datenverhältnisse",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "Fehlgeschlagen",
 | 
			
		||||
    "failure-context": "Fehlerkontext",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "Funktion",
 | 
			
		||||
    "feature-lowercase": "funktion",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "Ungültige Bedingung",
 | 
			
		||||
    "invalid-name": "Ungültiger Name",
 | 
			
		||||
    "is-ready-for-preview": "ist bereit zur Vorschau",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "Januar",
 | 
			
		||||
    "job-lowercase": "aufgabe",
 | 
			
		||||
    "join": "Beitreten",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "Neue Test-Suite",
 | 
			
		||||
    "next": "Nächster",
 | 
			
		||||
    "no": "Nein",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "Keine Datenanlagen gefunden für",
 | 
			
		||||
    "no-data-found": "Keine Daten gefunden",
 | 
			
		||||
    "no-description": "Keine Beschreibung",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "Ja",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Gestern",
 | 
			
		||||
    "your-entity": "Ihre {{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "Nachricht löschen?",
 | 
			
		||||
    "delete-team-message": "Alle Teams unter \"{{teamName}}\" werden ebenfalls {{deleteType}} gelöscht.",
 | 
			
		||||
    "delete-webhook-permanently": "Möchten Sie den Webhook \"{{webhookName}}\" dauerhaft löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "Durch Deaktivieren der Klassifizierung können Sie nicht mehr nach dieser suchen oder sie mit zugeordneten Tags einer Entität zuweisen.",
 | 
			
		||||
    "disabled-classification-actions-message": "Sie können diese Aktion auf deaktivierten Klassifikationen nicht ausführen.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "In Tabelle suchen",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Setzen Sie Unternehmensziele und KPIs, um die Datenkultur Ihres Unternehmens proaktiv voranzutreiben. Fördern Sie eine Kultur kontinuierlicher Verbesserung mit rechtzeitigen Berichten zur Überwachung der Datenqualität.",
 | 
			
		||||
    "get-started-with-open-metadata": "Erste Schritte mit OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Jeder Begriff im Glossar hat eine eindeutige Definition. Neben der Definition des Standardbegriffs für ein Konzept können auch Synonyme sowie verwandte Begriffe (z. B. übergeordnete und untergeordnete Begriffe) angegeben werden. Es können Referenzen zu den Assets hinzugefügt werden, die sich auf die Begriffe beziehen. Neue Begriffe können dem Glossar hinzugefügt oder aktualisiert werden. Die Glossarbegriffe können von bestimmten Benutzern überprüft werden, die die Begriffe akzeptieren oder ablehnen können.",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "Zurück zur Anmeldeseite",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "Sie haben die Tour erfolgreich abgeschlossen.",
 | 
			
		||||
    "synonym-placeholder": "Um ein Synonym hinzuzufügen, geben Sie es einfach ein und drücken Sie Enter",
 | 
			
		||||
    "system-tag-delete-disable-message": "Das Löschen von systemgenerierten Tags ist nicht zulässig. Sie können versuchen, den Tag stattdessen zu deaktivieren.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Machen Sie eine Produkttour, um loszulegen!",
 | 
			
		||||
    "team-moved-success": "Team erfolgreich verschoben!",
 | 
			
		||||
    "team-no-asset": "Ihr Team hat keine Vermögenswerte.",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Data Models",
 | 
			
		||||
    "data-model-type": "Data Model Type",
 | 
			
		||||
    "data-product": "Data Product",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Data Products",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "Data Proportions",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "Failed",
 | 
			
		||||
    "failure-context": "Failure Context",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "Feature",
 | 
			
		||||
    "feature-lowercase": "feature",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "Invalid condition",
 | 
			
		||||
    "invalid-name": "Invalid Name",
 | 
			
		||||
    "is-ready-for-preview": "is ready for preview",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "January",
 | 
			
		||||
    "job-lowercase": "job",
 | 
			
		||||
    "join": "Join",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "New Test Suite",
 | 
			
		||||
    "next": "Next",
 | 
			
		||||
    "no": "No",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "No data assets found for",
 | 
			
		||||
    "no-data-found": "No data found",
 | 
			
		||||
    "no-description": "No description",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "Yes",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Yesterday",
 | 
			
		||||
    "your-entity": "Your {{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "Delete Message?",
 | 
			
		||||
    "delete-team-message": "Any teams under \"{{teamName}}\" will be {{deleteType}} deleted as well.",
 | 
			
		||||
    "delete-webhook-permanently": "You want to delete webhook {{webhookName}} permanently? This action cannot be reverted.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "By disabling classification, you will not be able to search by, or assign associated tags to any entity.",
 | 
			
		||||
    "disabled-classification-actions-message": "You can not perform this action on disabled classifications.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "Find in table",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Set organizational goals and KPIs to proactively drive the data culture of your company. Foster a culture of continuous improvement with timely reports to monitor data health.",
 | 
			
		||||
    "get-started-with-open-metadata": "Get started with OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Every term in the glossary has a unique definition. Along with defining the standard term for a concept, the synonyms as well as related terms (for e.g., parent and child terms) can be specified. References can be added to the assets related to the terms. New terms can be added or updated to the Glossary. The glossary terms can be reviewed by certain users, who can accept or reject the terms.",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "Go back to Login page",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "You’ve successfully completed the tour.",
 | 
			
		||||
    "synonym-placeholder": "To add a synonym, simply type it in and press Enter",
 | 
			
		||||
    "system-tag-delete-disable-message": "Deleting a system generated tags is not allowed. You can try disabling the tag instead.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Take a product tour to get started!",
 | 
			
		||||
    "team-moved-success": "Team moved successfully!",
 | 
			
		||||
    "team-no-asset": "Your team does not have any assets.",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Data Models",
 | 
			
		||||
    "data-model-type": "Data Model Type",
 | 
			
		||||
    "data-product": "Data Product",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Data Products",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "Data Proportions",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "Falló",
 | 
			
		||||
    "failure-context": "Contexto del error",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "Funcionalidad",
 | 
			
		||||
    "feature-lowercase": "funcionalidad",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "Condición inválida",
 | 
			
		||||
    "invalid-name": "Nombre inválido",
 | 
			
		||||
    "is-ready-for-preview": "está listo para previsualización",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "Enero",
 | 
			
		||||
    "job-lowercase": "trabajo",
 | 
			
		||||
    "join": "Unirse",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "Nueva Suite de Pruebas",
 | 
			
		||||
    "next": "Siguiente",
 | 
			
		||||
    "no": "No",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "No data assets found for",
 | 
			
		||||
    "no-data-found": "No se encontraron datos",
 | 
			
		||||
    "no-description": "Sin descripción",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "Sí",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Yesterday",
 | 
			
		||||
    "your-entity": "Tu {{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "¿Eliminar mensaje?",
 | 
			
		||||
    "delete-team-message": "Cualquier equipo bajo \"{{teamName}}\" también será {{deleteType}} eliminado.",
 | 
			
		||||
    "delete-webhook-permanently": "¿Quieres eliminar permanentemente el webhook {{webhookName}}? Esta acción no se puede revertir.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "Option to disable classifications, You won't be able to see associated tags within application",
 | 
			
		||||
    "disabled-classification-actions-message": "You can not perform this action on disabled classifications.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "Buscar en la tabla",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Fomenta la colaboración entre los productores y consumidores de datos.",
 | 
			
		||||
    "get-started-with-open-metadata": "Empezar con OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Cada término en el glosario tiene una definición única. Además de definir el término estándar para un concepto, se pueden especificar sinónimos y términos relacionados (por ejemplo, términos padre e hijo). Se pueden agregar referencias a los activos relacionados con los términos. Se pueden agregar o actualizar nuevos términos al glosario. Los términos del glosario pueden ser revisados por ciertos usuarios, quienes pueden aceptar o rechazar los términos.",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "Volver a la página de inicio de sesión",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "Has completado el recorrido con éxito.",
 | 
			
		||||
    "synonym-placeholder": "To add a synonym, simply type it in and press Enter",
 | 
			
		||||
    "system-tag-delete-disable-message": "Deleting a system generated tags is not allowed. You can try disabling the tag instead.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Take a product tour to get started!",
 | 
			
		||||
    "team-moved-success": "¡Equipo movido con éxito!",
 | 
			
		||||
    "team-no-asset": "Tu equipo no tiene ningún activo.",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Modèles de Données",
 | 
			
		||||
    "data-model-type": "Type de Modèle de Données",
 | 
			
		||||
    "data-product": "Produit de Données",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Produits de Données",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "Proportions des Données",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "Échec",
 | 
			
		||||
    "failure-context": "Contexte de l'Échec",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "Fonctionnalité",
 | 
			
		||||
    "feature-lowercase": "fonctionnalité",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "Condition Invalide",
 | 
			
		||||
    "invalid-name": "Nom Invalide",
 | 
			
		||||
    "is-ready-for-preview": "est prêt à être prévisualisé",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "Janvier",
 | 
			
		||||
    "job-lowercase": "tâche",
 | 
			
		||||
    "join": "Rejoindre",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "Nouvel Ensemble de Tests",
 | 
			
		||||
    "next": "Suivant",
 | 
			
		||||
    "no": "Non",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "Aucun actif de données trouvé pour",
 | 
			
		||||
    "no-data-found": "Aucune donnée trouvée",
 | 
			
		||||
    "no-description": "Aucune description",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "Oui",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Hier",
 | 
			
		||||
    "your-entity": "Votre {{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "Supprimer le Message ?",
 | 
			
		||||
    "delete-team-message": "N'importe quelle équipe sous \"{{teamName}}\" sera {{deleteType}} supprimée aussi.",
 | 
			
		||||
    "delete-webhook-permanently": "Vous voulez supprimer le webhook {{webhookName}} de manière permanente ? Cette action ne peut pas être annulée.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "Option to disable classifications, You won't be able to see associated tags within the application",
 | 
			
		||||
    "disabled-classification-actions-message": "You cannot perform this action on disabled classifications.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "Trouver dans la table",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Encouragez la collaborations entre les consommateurs et producteurs de données",
 | 
			
		||||
    "get-started-with-open-metadata": "Commencez votre Journée avec OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Chaque terme du glossaire a une définition unique. En plus de définir le terme standard pour un concept, les synonymes ainsi que les termes associés (par exemple, les termes parent et enfant) peuvent être spécifiés. Des références peuvent être ajoutées aux actifs liés aux termes. De nouveaux termes peuvent être ajoutés ou mis à jour dans le glossaire. Les termes du glossaire peuvent être examinés par certains utilisateurs, qui peuvent accepter ou rejeter les termes.",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "Retour à la page d'accueil",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "Vous avez fini la visite avec succès.",
 | 
			
		||||
    "synonym-placeholder": "Pour ajouter un synonyme, tapez-le et appuyez sur Entrée",
 | 
			
		||||
    "system-tag-delete-disable-message": "Deleting a system generated tags is not allowed. You can try disabling the tag instead.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Faites une visite guidée pour vous lancer!",
 | 
			
		||||
    "team-moved-success": "L'équipe a été déplacée avec succès !",
 | 
			
		||||
    "team-no-asset": "Votre équipe n'a pas de ressources de données",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Data Models",
 | 
			
		||||
    "data-model-type": "Data Model Type",
 | 
			
		||||
    "data-product": "Data Product",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Data Products",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "Data Proportions",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "失敗",
 | 
			
		||||
    "failure-context": "Failure Context",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "Feature",
 | 
			
		||||
    "feature-lowercase": "feature",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "不正な条件",
 | 
			
		||||
    "invalid-name": "不正な名称",
 | 
			
		||||
    "is-ready-for-preview": "はプレビューの準備ができました",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "1月",
 | 
			
		||||
    "job-lowercase": "ジョブ",
 | 
			
		||||
    "join": "参加",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "新しいテストスイート",
 | 
			
		||||
    "next": "次へ",
 | 
			
		||||
    "no": "いいえ",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "No data assets found for",
 | 
			
		||||
    "no-data-found": "データが見つかりません",
 | 
			
		||||
    "no-description": "説明がありません",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "はい",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Yesterday",
 | 
			
		||||
    "your-entity": "あなたの{{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "このメッセージを削除しますか?",
 | 
			
		||||
    "delete-team-message": "Any teams under \"{{teamName}}\" will be {{deleteType}} deleted as well.",
 | 
			
		||||
    "delete-webhook-permanently": "You want to delete webhook {{webhookName}} permanently? This action cannot be reverted.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "Option to disable classifications, You won't be able to see associated tags within application",
 | 
			
		||||
    "disabled-classification-actions-message": "You can not perform this action on disabled classifications.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "テーブルで探す",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Fosters collaboration among the producers and consumers of data.",
 | 
			
		||||
    "get-started-with-open-metadata": "Get started with OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Every term in the glossary has a unique definition. Along with defining the standard term for a concept, the synonyms as well as related terms (for e.g., parent and child terms) can be specified. References can be added to the assets related to the terms. New terms can be added or updated to the Glossary. The glossary terms can be reviewed by certain users, who can accept or reject the terms.",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "ログインページに戻る",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "あなたは無事ツアーを終了しました。",
 | 
			
		||||
    "synonym-placeholder": "To add a synonym, simply type it in and press Enter",
 | 
			
		||||
    "system-tag-delete-disable-message": "Deleting a system generated tags is not allowed. You can try disabling the tag instead.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Take a product tour to get started!",
 | 
			
		||||
    "team-moved-success": "チームの移動が成功しました!",
 | 
			
		||||
    "team-no-asset": "あなたのチームはアセットを持っていません。",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Modelos de Dados",
 | 
			
		||||
    "data-model-type": "Tipo de Modelo de Dados",
 | 
			
		||||
    "data-product": "Produto de Dados",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Produtos de Dados",
 | 
			
		||||
    "data-profiler-metrics": "Métricas do Examinador de Dados",
 | 
			
		||||
    "data-proportion-plural": "Proporções de Dados",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Expandir OpenMetadata",
 | 
			
		||||
    "failed": "Falhou",
 | 
			
		||||
    "failure-context": "Contexto de Falha",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "URL do Favicon",
 | 
			
		||||
    "feature": "Recurso",
 | 
			
		||||
    "feature-lowercase": "recurso",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "Condição Inválida",
 | 
			
		||||
    "invalid-name": "Nome Inválido",
 | 
			
		||||
    "is-ready-for-preview": "está pronto para visualização",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "Janeiro",
 | 
			
		||||
    "job-lowercase": "trabalho",
 | 
			
		||||
    "join": "Juntar",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "Novo Conjunto de Testes",
 | 
			
		||||
    "next": "Próximo",
 | 
			
		||||
    "no": "Não",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "Nenhum ativo de dados encontrado para",
 | 
			
		||||
    "no-data-found": "Nenhum dado encontrado",
 | 
			
		||||
    "no-description": "Sem descrição",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Fluxos de Trabalho",
 | 
			
		||||
    "yes": "Sim",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Ontem",
 | 
			
		||||
    "your-entity": "Sua {{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "Excluir Mensagem?",
 | 
			
		||||
    "delete-team-message": "Qualquer equipe sob \"{{teamName}}\" será {{deleteType}} excluída também.",
 | 
			
		||||
    "delete-webhook-permanently": "Você deseja excluir permanentemente o webhook {{webhookName}}? Esta ação não pode ser revertida.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "Isso desativará a aplicação {{app}}.",
 | 
			
		||||
    "disable-classification-description": "Ao desativar a classificação, você não poderá buscar por, ou atribuir tags associadas a qualquer entidade.",
 | 
			
		||||
    "disabled-classification-actions-message": "Você não pode realizar esta ação em classificações desativadas.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "Encontrar na tabela",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Estabeleça metas organizacionais e KPIs para impulsionar proativamente a cultura de dados da sua empresa. Fomente uma cultura de melhoria contínua com relatórios oportunos para monitorar a saúde dos dados.",
 | 
			
		||||
    "get-started-with-open-metadata": "Comece com o OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Cada termo no glossário tem uma definição única. Além de definir o termo padrão para um conceito, os sinônimos e termos relacionados (por exemplo, termos pai e filho) podem ser especificados. Referências podem ser adicionadas aos ativos relacionados aos termos. Novos termos podem ser adicionados ou atualizados no Glossário. Os termos do glossário podem ser revisados por certos usuários, que podem aceitar ou rejeitar os termos.",
 | 
			
		||||
    "glossary-term-status": "Termo do Glossário foi {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "Voltar para a página de Login",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "Você concluiu com sucesso o tour.",
 | 
			
		||||
    "synonym-placeholder": "Para adicionar um sinônimo, basta digitá-lo e pressionar Enter",
 | 
			
		||||
    "system-tag-delete-disable-message": "Não é permitido excluir tags geradas pelo sistema. Você pode tentar desativar a tag em vez disso.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Faça um tour pelo produto para começar!",
 | 
			
		||||
    "team-moved-success": "Equipe movida com sucesso!",
 | 
			
		||||
    "team-no-asset": "Sua equipe não possui ativos.",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "Модели данных",
 | 
			
		||||
    "data-model-type": "Тип модели данных",
 | 
			
		||||
    "data-product": "Data Product",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "Data Products",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "Распределение данных",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "Неуспешно",
 | 
			
		||||
    "failure-context": "Контекст отказа",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "Свойство",
 | 
			
		||||
    "feature-lowercase": "свойство",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "Недопустимое условие",
 | 
			
		||||
    "invalid-name": "Неверное имя",
 | 
			
		||||
    "is-ready-for-preview": "готов к предварительному просмотру",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "Январь",
 | 
			
		||||
    "job-lowercase": "job",
 | 
			
		||||
    "join": "Соединить",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "Новый набор тестов",
 | 
			
		||||
    "next": "Следующий",
 | 
			
		||||
    "no": "Нет",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "Объекты данных не найдены",
 | 
			
		||||
    "no-data-found": "Данные не найдены",
 | 
			
		||||
    "no-description": "Нет описания",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "Да",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "Вчера",
 | 
			
		||||
    "your-entity": "Ваш {{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "Удалить сообщение?",
 | 
			
		||||
    "delete-team-message": "Все команды под \"{{teamName}}\" также будут {{deleteType}} удалены.",
 | 
			
		||||
    "delete-webhook-permanently": "Вы хотите навсегда удалить webhook {{webhookName}}? Это действие нельзя отменить.",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "Отключив классификацию, вы не сможете выполнять поиск или назначать связанные теги любому объекту.",
 | 
			
		||||
    "disabled-classification-actions-message": "Вы не можете выполнить это действие с отключенными классификациями.",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "Найти в таблице",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "Способствует сотрудничеству между производителями и потребителями данных.",
 | 
			
		||||
    "get-started-with-open-metadata": "Начните работу с OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "Каждый термин в глоссарии имеет уникальное определение. Наряду с определением стандартного термина для понятия можно указать синонимы, а также связанные термины (например, родительские и дочерние термины). Ссылки могут быть добавлены к объектам данных, связанным с терминами. Новые термины могут быть добавлены или обновлены в Глоссарий. Термины глоссария могут быть просмотрены определенными пользователями, которые могут принять или отклонить термины.",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "Вернуться на страницу входа",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "Вы успешно завершили экскурсию.",
 | 
			
		||||
    "synonym-placeholder": "Чтобы добавить синоним, просто введите его и нажмите Enter.",
 | 
			
		||||
    "system-tag-delete-disable-message": "Удаление сгенерированных системой тегов не допускается. Вместо этого вы можете попробовать отключить тег.",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "Ознакомьтесь с продуктом, чтобы начать работу!",
 | 
			
		||||
    "team-moved-success": "Команда успешно переехала!",
 | 
			
		||||
    "team-no-asset": "У вашей команды нет объектов.",
 | 
			
		||||
 | 
			
		||||
@ -258,6 +258,7 @@
 | 
			
		||||
    "data-model-plural": "数据模型",
 | 
			
		||||
    "data-model-type": "数据模型类型",
 | 
			
		||||
    "data-product": "数据产品",
 | 
			
		||||
    "data-product-lowercase": "data product",
 | 
			
		||||
    "data-product-plural": "数据产品",
 | 
			
		||||
    "data-profiler-metrics": "Data Profiler Metrics",
 | 
			
		||||
    "data-proportion-plural": "数据比例",
 | 
			
		||||
@ -413,6 +414,7 @@
 | 
			
		||||
    "extend-open-meta-data": "Extend OpenMetadata",
 | 
			
		||||
    "failed": "失败",
 | 
			
		||||
    "failure-context": "失败上下文",
 | 
			
		||||
    "failure-reason": "Failure Reason",
 | 
			
		||||
    "favicon-url": "Favicon URL",
 | 
			
		||||
    "feature": "特点",
 | 
			
		||||
    "feature-lowercase": "特点",
 | 
			
		||||
@ -529,6 +531,7 @@
 | 
			
		||||
    "invalid-condition": "无效条件",
 | 
			
		||||
    "invalid-name": "无效名称",
 | 
			
		||||
    "is-ready-for-preview": "可预览",
 | 
			
		||||
    "items-selected-lowercase": "items selected",
 | 
			
		||||
    "january": "一月",
 | 
			
		||||
    "job-lowercase": "作业",
 | 
			
		||||
    "join": "加入",
 | 
			
		||||
@ -659,6 +662,7 @@
 | 
			
		||||
    "new-test-suite": "新质控测试",
 | 
			
		||||
    "next": "下一步",
 | 
			
		||||
    "no": "否",
 | 
			
		||||
    "no-comma-cancel": "No, cancel",
 | 
			
		||||
    "no-data-asset-found-for": "没有查询到相关的数据资产",
 | 
			
		||||
    "no-data-found": "未找到数据",
 | 
			
		||||
    "no-description": "无描述",
 | 
			
		||||
@ -1160,6 +1164,7 @@
 | 
			
		||||
    "widget-lowercase": "widget",
 | 
			
		||||
    "workflow-plural": "Workflows",
 | 
			
		||||
    "yes": "是",
 | 
			
		||||
    "yes-comma-confirm": "Yes, confirm",
 | 
			
		||||
    "yesterday": "昨天",
 | 
			
		||||
    "your-entity": "您的{{entity}}"
 | 
			
		||||
  },
 | 
			
		||||
@ -1275,6 +1280,7 @@
 | 
			
		||||
    "delete-message-question-mark": "删除消息?",
 | 
			
		||||
    "delete-team-message": "任何在\"{{teamName}}\"下的团队也将被{{deleteType}}删除",
 | 
			
		||||
    "delete-webhook-permanently": "您要永久删除 Webhook {{webhookName}} 吗?此操作无法撤消",
 | 
			
		||||
    "derived-tag-warning": "This tag is automatically derived and can only be removed by deleting the related Glossary Term.",
 | 
			
		||||
    "disable-app": "This will disable the {{app}} application.",
 | 
			
		||||
    "disable-classification-description": "禁用分类选项,您将无法查看到关联的标签",
 | 
			
		||||
    "disabled-classification-actions-message": "您无法在已禁用的分类上执行此操作",
 | 
			
		||||
@ -1356,6 +1362,9 @@
 | 
			
		||||
    "find-in-table": "在数据表中查找",
 | 
			
		||||
    "fosters-collaboration-among-producers-and-consumers": "促进数据生产者和使用者之间的合作",
 | 
			
		||||
    "get-started-with-open-metadata": "开始使用 OpenMetadata",
 | 
			
		||||
    "glossary-tag-assignement-help-message": "You can either remove this assets or remove conflicting tag from the asset and try again adding tags!",
 | 
			
		||||
    "glossary-tag-update-description": "This action will apply the tag to all Assets linked to the Glossary Term",
 | 
			
		||||
    "glossary-tag-update-modal-title": "Validation failed for following assets",
 | 
			
		||||
    "glossary-term-description": "术语库中的每个术语都有一个唯一的定义。除了为概念定义标准术语之外,还可以指定同义词以及相关术语(例如,父项和子项)。可以向与术语相关的资产添加引用。可以向术语库添加或更新新术语。某些用户可以审查术语,并接受或拒绝这些术语。",
 | 
			
		||||
    "glossary-term-status": "Glossary Term was {{status}}.",
 | 
			
		||||
    "go-back-to-login-page": "返回登录页面",
 | 
			
		||||
@ -1574,6 +1583,7 @@
 | 
			
		||||
    "successfully-completed-the-tour": "您已成功完成导览",
 | 
			
		||||
    "synonym-placeholder": "输入并按回车键以新增一个同义词",
 | 
			
		||||
    "system-tag-delete-disable-message": "无法删除系统默认的标签,您可以尝试禁用标签",
 | 
			
		||||
    "tag-update-confirmation": "Would you like to proceed with adding a new tag?",
 | 
			
		||||
    "take-quick-product-tour": "快速查看产品导览",
 | 
			
		||||
    "team-moved-success": "团队移动成功",
 | 
			
		||||
    "team-no-asset": "您的团队没有任何资产",
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ export interface QueryFieldValueInterface {
 | 
			
		||||
export interface QueryFieldInterface {
 | 
			
		||||
  bool: {
 | 
			
		||||
    must?: Array<QueryFieldValueInterface>;
 | 
			
		||||
    must_not?: Array<QueryFieldValueInterface>;
 | 
			
		||||
    should?: Array<QueryFieldValueInterface>;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@ -26,6 +27,7 @@ export interface QueryFilterInterface {
 | 
			
		||||
  query: {
 | 
			
		||||
    bool: {
 | 
			
		||||
      must?: QueryFieldInterface[];
 | 
			
		||||
      must_not?: QueryFieldInterface[];
 | 
			
		||||
      should?: QueryFieldInterface[];
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,10 @@ import { PagingResponse } from 'Models';
 | 
			
		||||
import { PAGE_SIZE } from '../constants/constants';
 | 
			
		||||
import { SearchIndex } from '../enums/search.enum';
 | 
			
		||||
import { CreateDataProduct } from '../generated/api/domains/createDataProduct';
 | 
			
		||||
import { DataProduct } from '../generated/entity/domains/dataProduct';
 | 
			
		||||
import {
 | 
			
		||||
  DataProduct,
 | 
			
		||||
  EntityReference,
 | 
			
		||||
} from '../generated/entity/domains/dataProduct';
 | 
			
		||||
import { EntityHistory } from '../generated/type/entityHistory';
 | 
			
		||||
import { Include } from '../generated/type/include';
 | 
			
		||||
import { Paging } from '../generated/type/paging';
 | 
			
		||||
@ -136,3 +139,35 @@ export const fetchDataProductsElasticSearch = async (
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addAssetsToDataProduct = async (
 | 
			
		||||
  dataProductFqn: string,
 | 
			
		||||
  assets: EntityReference[]
 | 
			
		||||
) => {
 | 
			
		||||
  const data: { assets: EntityReference[] } = {
 | 
			
		||||
    assets: assets,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.put<
 | 
			
		||||
    { assets: EntityReference[] },
 | 
			
		||||
    AxiosResponse<DataProduct>
 | 
			
		||||
  >(`/dataProducts/${dataProductFqn}/assets/add`, data);
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeAssetsFromDataProduct = async (
 | 
			
		||||
  dataProductFqn: string,
 | 
			
		||||
  assets: EntityReference[]
 | 
			
		||||
) => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    assets: assets,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.put<
 | 
			
		||||
    { assets: EntityReference[] },
 | 
			
		||||
    AxiosResponse<DataProduct>
 | 
			
		||||
  >(`/dataProducts/${dataProductFqn}/assets/remove`, data);
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ import { AxiosResponse } from 'axios';
 | 
			
		||||
import { Operation } from 'fast-json-patch';
 | 
			
		||||
import { PagingResponse } from 'Models';
 | 
			
		||||
import { CreateDomain } from '../generated/api/domains/createDomain';
 | 
			
		||||
import { Domain } from '../generated/entity/domains/domain';
 | 
			
		||||
import { Domain, EntityReference } from '../generated/entity/domains/domain';
 | 
			
		||||
import { EntityHistory } from '../generated/type/entityHistory';
 | 
			
		||||
import { ListParams } from '../interface/API.interface';
 | 
			
		||||
import { getURLWithQueryFields } from '../utils/APIUtils';
 | 
			
		||||
@ -81,3 +81,35 @@ export const getDomainVersionData = async (id: string, version: string) => {
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addAssetsToDomain = async (
 | 
			
		||||
  domainFqn: string,
 | 
			
		||||
  assets: EntityReference[]
 | 
			
		||||
) => {
 | 
			
		||||
  const data: { assets: EntityReference[] } = {
 | 
			
		||||
    assets: assets,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.put<
 | 
			
		||||
    { assets: EntityReference[] },
 | 
			
		||||
    AxiosResponse<Domain>
 | 
			
		||||
  >(`/domains/${domainFqn}/assets/add`, data);
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeAssetsFromDomain = async (
 | 
			
		||||
  domainFqn: string,
 | 
			
		||||
  assets: EntityReference[]
 | 
			
		||||
) => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    assets: assets,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.put<
 | 
			
		||||
    { assets: EntityReference[] },
 | 
			
		||||
    AxiosResponse<Domain>
 | 
			
		||||
  >(`/domains/${domainFqn}/assets/remove`, data);
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import { CreateGlossary } from '../generated/api/data/createGlossary';
 | 
			
		||||
import { CreateGlossaryTerm } from '../generated/api/data/createGlossaryTerm';
 | 
			
		||||
import { EntityReference, Glossary } from '../generated/entity/data/glossary';
 | 
			
		||||
import { GlossaryTerm } from '../generated/entity/data/glossaryTerm';
 | 
			
		||||
import { BulkOperationResult } from '../generated/type/bulkOperationResult';
 | 
			
		||||
import { CSVImportResult } from '../generated/type/csvImportResult';
 | 
			
		||||
import { EntityHistory } from '../generated/type/entityHistory';
 | 
			
		||||
import { ListParams } from '../interface/API.interface';
 | 
			
		||||
@ -251,13 +252,31 @@ export const updateGlossaryTermVotes = async (
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const validateTagAddtionToGlossary = async (
 | 
			
		||||
  glossaryTerm: GlossaryTerm,
 | 
			
		||||
  dryRun = false
 | 
			
		||||
) => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    dryRun: dryRun,
 | 
			
		||||
    glossaryTags: glossaryTerm.tags ?? [],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const response = await APIClient.put<
 | 
			
		||||
    AddGlossaryToAssetsRequest,
 | 
			
		||||
    AxiosResponse<BulkOperationResult>
 | 
			
		||||
  >(`/glossaryTerms/${glossaryTerm.id}/tags/validate`, data);
 | 
			
		||||
 | 
			
		||||
  return response.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addAssetsToGlossaryTerm = async (
 | 
			
		||||
  glossaryTerm: GlossaryTerm,
 | 
			
		||||
  assets: EntityReference[]
 | 
			
		||||
  assets: EntityReference[],
 | 
			
		||||
  dryRun = false
 | 
			
		||||
) => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    assets: assets,
 | 
			
		||||
    dryRun: false,
 | 
			
		||||
    dryRun: dryRun,
 | 
			
		||||
    glossaryTags: glossaryTerm.tags ?? [],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -43,3 +43,7 @@
 | 
			
		||||
.border-color-primary {
 | 
			
		||||
  border-color: @primary-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.border-danger {
 | 
			
		||||
  border: 1px solid @error-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,10 @@ pre {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-danger {
 | 
			
		||||
  color: @error-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-inherit {
 | 
			
		||||
  font-size: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import { isArray, isEmpty } from 'lodash';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { RenderSettings } from 'react-awesome-query-builder';
 | 
			
		||||
import ProfilePicture from '../components/common/ProfilePicture/ProfilePicture';
 | 
			
		||||
import { AssetsOfEntity } from '../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
 | 
			
		||||
import { SearchDropdownOption } from '../components/SearchDropdown/SearchDropdown.interface';
 | 
			
		||||
import {
 | 
			
		||||
  COMMON_DROPDOWN_ITEMS,
 | 
			
		||||
@ -25,6 +26,7 @@ import {
 | 
			
		||||
  DASHBOARD_DATA_MODEL_TYPE,
 | 
			
		||||
  DASHBOARD_DROPDOWN_ITEMS,
 | 
			
		||||
  DATA_PRODUCT_DROPDOWN_ITEMS,
 | 
			
		||||
  DOMAIN_DATAPRODUCT_DROPDOWN_ITEMS,
 | 
			
		||||
  GLOSSARY_DROPDOWN_ITEMS,
 | 
			
		||||
  PIPELINE_DROPDOWN_ITEMS,
 | 
			
		||||
  SEARCH_INDEX_DROPDOWN_ITEMS,
 | 
			
		||||
@ -88,9 +90,17 @@ export const getDropDownItems = (index: string) => {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAssetsPageQuickFilters = () => {
 | 
			
		||||
export const getAssetsPageQuickFilters = (type: AssetsOfEntity) => {
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case AssetsOfEntity.DOMAIN:
 | 
			
		||||
    case AssetsOfEntity.DATA_PRODUCT:
 | 
			
		||||
      return [...DOMAIN_DATAPRODUCT_DROPDOWN_ITEMS];
 | 
			
		||||
 | 
			
		||||
    case AssetsOfEntity.GLOSSARY:
 | 
			
		||||
    default:
 | 
			
		||||
      return [...COMMON_DROPDOWN_ITEMS];
 | 
			
		||||
  }
 | 
			
		||||
  // TODO: Add more quick filters
 | 
			
		||||
  return [...COMMON_DROPDOWN_ITEMS];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAdvancedField = (field: string) => {
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@ import { EntityField } from '../constants/Feeds.constants';
 | 
			
		||||
import { DataProduct } from '../generated/entity/domains/dataProduct';
 | 
			
		||||
import { Domain } from '../generated/entity/domains/domain';
 | 
			
		||||
import { ChangeDescription, EntityReference } from '../generated/entity/type';
 | 
			
		||||
import { QueryFilterInterface } from '../pages/ExplorePage/ExplorePage.interface';
 | 
			
		||||
import { getEntityName } from './EntityUtils';
 | 
			
		||||
import {
 | 
			
		||||
  getChangedEntityNewValue,
 | 
			
		||||
@ -152,20 +153,18 @@ export const getQueryFilterToIncludeDomain = (
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const getQueryFilterToExcludeDomainTerms = (fqn: string) => ({
 | 
			
		||||
export const getQueryFilterToExcludeDomainTerms = (
 | 
			
		||||
  fqn: string
 | 
			
		||||
): QueryFilterInterface => ({
 | 
			
		||||
  query: {
 | 
			
		||||
    bool: {
 | 
			
		||||
      must: [
 | 
			
		||||
        {
 | 
			
		||||
          bool: {
 | 
			
		||||
            must: [
 | 
			
		||||
            must_not: [
 | 
			
		||||
              {
 | 
			
		||||
                bool: {
 | 
			
		||||
                  must_not: {
 | 
			
		||||
                    term: {
 | 
			
		||||
                      'domain.fullyQualifiedName': fqn,
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                term: {
 | 
			
		||||
                  'domain.fullyQualifiedName': fqn,
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,9 @@ export const getQueryFiltersArray = (
 | 
			
		||||
    case QueryFilterFieldsEnum.MUST: {
 | 
			
		||||
      return queryFiltersObj?.query?.bool?.must ?? [];
 | 
			
		||||
    }
 | 
			
		||||
    case QueryFilterFieldsEnum.MUST_NOT: {
 | 
			
		||||
      return queryFiltersObj?.query?.bool?.must_not ?? [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -58,6 +61,13 @@ export const getCombinedQueryFilterObject = (
 | 
			
		||||
    advancesSearchQueryFilter,
 | 
			
		||||
    advancesSearchFilter,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const mustNotField = getCombinedFields(QueryFilterFieldsEnum.MUST_NOT, [
 | 
			
		||||
    elasticsearchQueryFilter,
 | 
			
		||||
    advancesSearchQueryFilter,
 | 
			
		||||
    advancesSearchFilter,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const shouldField = getCombinedFields(QueryFilterFieldsEnum.SHOULD, [
 | 
			
		||||
    elasticsearchQueryFilter,
 | 
			
		||||
    advancesSearchQueryFilter,
 | 
			
		||||
@ -68,6 +78,7 @@ export const getCombinedQueryFilterObject = (
 | 
			
		||||
    query: {
 | 
			
		||||
      bool: {
 | 
			
		||||
        ...(isEmpty(mustField) ? {} : { must: mustField }),
 | 
			
		||||
        ...(isEmpty(mustNotField) ? {} : { must_not: mustNotField }),
 | 
			
		||||
        ...(isEmpty(shouldField) ? {} : { should: shouldField }),
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -31,14 +31,10 @@ describe('Glossary Utils', () => {
 | 
			
		||||
          must: [
 | 
			
		||||
            {
 | 
			
		||||
              bool: {
 | 
			
		||||
                must: [
 | 
			
		||||
                must_not: [
 | 
			
		||||
                  {
 | 
			
		||||
                    bool: {
 | 
			
		||||
                      must_not: {
 | 
			
		||||
                        term: {
 | 
			
		||||
                          'tags.tagFQN': fqn,
 | 
			
		||||
                        },
 | 
			
		||||
                      },
 | 
			
		||||
                    term: {
 | 
			
		||||
                      'tags.tagFQN': fqn,
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                ],
 | 
			
		||||
 | 
			
		||||
@ -195,14 +195,10 @@ export const getQueryFilterToExcludeTerm = (fqn: string) => ({
 | 
			
		||||
      must: [
 | 
			
		||||
        {
 | 
			
		||||
          bool: {
 | 
			
		||||
            must: [
 | 
			
		||||
            must_not: [
 | 
			
		||||
              {
 | 
			
		||||
                bool: {
 | 
			
		||||
                  must_not: {
 | 
			
		||||
                    term: {
 | 
			
		||||
                      'tags.tagFQN': fqn,
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                term: {
 | 
			
		||||
                  'tags.tagFQN': fqn,
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            ],
 | 
			
		||||
 | 
			
		||||
@ -85,7 +85,6 @@ import IconDownArrow from '../assets/svg/ic-down-arrow.svg';
 | 
			
		||||
import IconEditLineageColor from '../assets/svg/ic-edit-lineage-colored.svg';
 | 
			
		||||
import IconEditLineage from '../assets/svg/ic-edit-lineage.svg';
 | 
			
		||||
import IconEdit from '../assets/svg/ic-edit.svg';
 | 
			
		||||
import IconExclamationCircle from '../assets/svg/ic-exclamation-circle.svg';
 | 
			
		||||
import IconExplore from '../assets/svg/ic-explore.svg';
 | 
			
		||||
import IconFeed from '../assets/svg/ic-feed.svg';
 | 
			
		||||
import IconFilter from '../assets/svg/ic-filter.svg';
 | 
			
		||||
@ -249,7 +248,6 @@ export const Icons = {
 | 
			
		||||
  GROWTH_ARROW: 'icon-growth-arrow',
 | 
			
		||||
  LOSS_ARROW: 'icon-loss-arrow',
 | 
			
		||||
  CHECK_CIRCLE: 'icon-check-circle',
 | 
			
		||||
  EXCLAMATION_CIRCLE: 'icon-exclamation-circle',
 | 
			
		||||
  TIMES_CIRCLE: 'icon-times-circle',
 | 
			
		||||
  HELP_CIRCLE: 'icon-help-circle',
 | 
			
		||||
  FILTERS: 'icon-filters',
 | 
			
		||||
@ -586,10 +584,7 @@ const SVGIcons: FunctionComponent<Props> = ({ icon, ...props }: Props) => {
 | 
			
		||||
      IconComponent = IconCheckCircle;
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
    case Icons.EXCLAMATION_CIRCLE:
 | 
			
		||||
      IconComponent = IconExclamationCircle;
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
    case Icons.TIMES_CIRCLE:
 | 
			
		||||
      IconComponent = IconTimesCircle;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user