1111 * limitations under the License.
1212 */
1313
14+ import { expect } from '@playwright/test' ;
15+ import { EntityStatus } from '../../../src/generated/entity/data/searchIndex' ;
1416import { COMMON_TIER_TAG } from '../../constant/common' ;
17+ import { DOMAIN_TAGS } from '../../constant/config' ;
1518import { SidebarItem } from '../../constant/sidebar' ;
19+ import { DataProduct } from '../../support/domain/DataProduct' ;
1620import { EntityDataClass } from '../../support/entity/EntityDataClass' ;
21+ import { MlModelClass } from '../../support/entity/MlModelClass' ;
1722import { TableClass } from '../../support/entity/TableClass' ;
1823import { TopicClass } from '../../support/entity/TopicClass' ;
1924import { Glossary } from '../../support/glossary/Glossary' ;
@@ -22,12 +27,17 @@ import { UserClass } from '../../support/user/UserClass';
2227import { performAdminLogin } from '../../utils/admin' ;
2328import {
2429 FIELDS ,
30+ fillRule ,
31+ fillStaticListRule ,
2532 OPERATOR ,
2633 runRuleGroupTests ,
2734 runRuleGroupTestsWithNonExistingValue ,
35+ selectOption ,
36+ showAdvancedSearchDialog ,
2837 verifyAllConditions ,
2938} from '../../utils/advancedSearch' ;
3039import { redirectToHomePage } from '../../utils/common' ;
40+ import { waitForAllLoadersToDisappear } from '../../utils/entity' ;
3141import { sidebarClick } from '../../utils/sidebar' ;
3242import { test } from '../fixtures/pages' ;
3343
@@ -285,8 +295,8 @@ test.describe('Advanced Search', { tag: ['@advanced-search'] }, () => {
285295 table2 . entityResponseData . name ,
286296 ] ,
287297 'project.keyword' : [
288- EntityDataClass . dashboardDataModel1 . entityResponseData . project ,
289- EntityDataClass . dashboardDataModel2 . entityResponseData . project ,
298+ EntityDataClass . dashboardDataModel1 . entityResponseData . project || '' ,
299+ EntityDataClass . dashboardDataModel2 . entityResponseData . project || '' ,
290300 ] ,
291301 'charts.displayName.keyword' : [
292302 EntityDataClass . dashboard1 . chartsResponseData . displayName ,
@@ -344,3 +354,279 @@ test.describe('Advanced Search', { tag: ['@advanced-search'] }, () => {
344354 await runRuleGroupTestsWithNonExistingValue ( page ) ;
345355 } ) ;
346356} ) ;
357+
358+ const ENTITY_STATUSES = Object . values ( EntityStatus ) ;
359+
360+ test . describe (
361+ 'Advanced Search - Entity Status Filter' ,
362+ { tag : [ DOMAIN_TAGS . DISCOVERY ] } ,
363+ ( ) => {
364+ type StatusEntry = {
365+ status : EntityStatus ;
366+ endpoint : string ;
367+ id : ( ) => string ;
368+ displayName : ( ) => string ;
369+ fqn : ( ) => string ;
370+ } ;
371+
372+ let glossaryForStatus : Glossary ;
373+ let glossaryTermApproved : GlossaryTerm ;
374+ let mlModelDraft : MlModelClass ;
375+ let dataProductInReview : DataProduct ;
376+ let statusEntries : StatusEntry [ ] ;
377+
378+ test . beforeAll (
379+ 'Create mixed entity types with distinct entity statuses' ,
380+ async ( { browser } ) => {
381+ const { apiContext, afterAction } = await performAdminLogin ( browser ) ;
382+
383+ glossaryForStatus = new Glossary ( ) ;
384+ glossaryTermApproved = new GlossaryTerm ( glossaryForStatus ) ;
385+ mlModelDraft = new MlModelClass ( ) ;
386+ dataProductInReview = new DataProduct ( ) ;
387+
388+ await glossaryForStatus . create ( apiContext ) ;
389+ await Promise . all ( [
390+ glossaryTermApproved . create ( apiContext ) ,
391+ mlModelDraft . create ( apiContext ) ,
392+ dataProductInReview . create ( apiContext ) ,
393+ ] ) ;
394+
395+ statusEntries = [
396+ {
397+ status : EntityStatus . Approved ,
398+ endpoint : 'glossaryTerms' ,
399+ id : ( ) => glossaryTermApproved . responseData . id ,
400+ displayName : ( ) => glossaryTermApproved . data . displayName ,
401+ fqn : ( ) => glossaryTermApproved . responseData . fullyQualifiedName ,
402+ } ,
403+ {
404+ status : EntityStatus . Draft ,
405+ endpoint : 'mlmodels' ,
406+ id : ( ) => mlModelDraft . entityResponseData . id ,
407+ displayName : ( ) => mlModelDraft . entity . displayName ,
408+ fqn : ( ) => mlModelDraft . entityResponseData . fullyQualifiedName ,
409+ } ,
410+ {
411+ status : EntityStatus . InReview ,
412+ endpoint : 'dataProducts' ,
413+ id : ( ) => dataProductInReview . responseData . id ?? '' ,
414+ displayName : ( ) => dataProductInReview . data . displayName ,
415+ fqn : ( ) =>
416+ dataProductInReview . responseData . fullyQualifiedName ?? '' ,
417+ } ,
418+ ] ;
419+
420+ // Patch entityStatus on each entity
421+ await Promise . all (
422+ statusEntries . map ( ( { id, status, endpoint } ) =>
423+ apiContext . patch ( `/api/v1/${ endpoint } /${ id ( ) } ` , {
424+ data : [ { op : 'add' , path : '/entityStatus' , value : status } ] ,
425+ headers : { 'Content-Type' : 'application/json-patch+json' } ,
426+ } )
427+ )
428+ ) ;
429+
430+ await afterAction ( ) ;
431+ }
432+ ) ;
433+
434+ test . beforeEach ( async ( { page } ) => {
435+ await redirectToHomePage ( page ) ;
436+ await sidebarClick ( page , SidebarItem . EXPLORE ) ;
437+ } ) ;
438+
439+ test ( 'All entity status options are visible in the Status dropdown' , async ( {
440+ page,
441+ } ) => {
442+ await test . step ( 'Open advanced search dialog' , async ( ) => {
443+ await showAdvancedSearchDialog ( page ) ;
444+ } ) ;
445+
446+ await test . step ( 'Select Status field and == operator' , async ( ) => {
447+ const ruleLocator = page . locator ( '.rule' ) . nth ( 0 ) ;
448+ await selectOption (
449+ page ,
450+ ruleLocator . locator ( '.rule--field .ant-select' ) ,
451+ 'Status' ,
452+ true
453+ ) ;
454+ await selectOption (
455+ page ,
456+ ruleLocator . locator ( '.rule--operator .ant-select' ) ,
457+ '=='
458+ ) ;
459+ } ) ;
460+
461+ await test . step ( 'Open Status value dropdown and verify all hard-coded options appear' , async ( ) => {
462+ const ruleLocator = page . locator ( '.rule' ) . nth ( 0 ) ;
463+ await ruleLocator . locator ( '.widget--widget > .ant-select' ) . click ( ) ;
464+
465+ const dropdown = page
466+ . locator ( '.ant-select-dropdown' )
467+ . filter ( { hasText : EntityStatus . Approved } )
468+ . last ( ) ;
469+
470+ await expect ( dropdown ) . toBeVisible ( ) ;
471+
472+ for ( const status of ENTITY_STATUSES ) {
473+ await expect (
474+ dropdown
475+ . locator ( '.ant-select-item-option' )
476+ . filter ( { hasText : new RegExp ( `^${ status } $` , 'i' ) } )
477+ . first ( )
478+ ) . toBeVisible ( ) ;
479+ }
480+ } ) ;
481+ } ) ;
482+
483+ test ( 'Filtering by status "==" shows matching entity and hides others across entity types' , async ( {
484+ page,
485+ } ) => {
486+ test . slow ( ) ;
487+
488+ for ( const entry of statusEntries ) {
489+ const { status } = entry ;
490+ const matchedName = entry . displayName ( ) ;
491+
492+ await test . step ( `Apply Status == "${ status } " AND Name == "${ matchedName } "` , async ( ) => {
493+ await showAdvancedSearchDialog ( page ) ;
494+
495+ await fillStaticListRule ( page , {
496+ fieldLabel : 'Status' ,
497+ condition : '==' ,
498+ value : status ,
499+ ruleIndex : 1 ,
500+ } ) ;
501+
502+ await page . getByTestId ( 'advanced-search-add-rule' ) . nth ( 1 ) . click ( ) ;
503+
504+ await fillRule ( page , {
505+ condition : '==' ,
506+ field : { id : 'Display Name' , name : 'displayName.keyword' } ,
507+ searchCriteria : matchedName ,
508+ index : 2 ,
509+ } ) ;
510+
511+ const searchRes = page . waitForResponse (
512+ '/api/v1/search/query?*index=dataAsset*'
513+ ) ;
514+ await page . getByTestId ( 'apply-btn' ) . click ( ) ;
515+ await searchRes ;
516+ await waitForAllLoadersToDisappear ( page ) ;
517+ } ) ;
518+
519+ await test . step ( 'Filter chip shows the applied status' , async ( ) => {
520+ await expect (
521+ page . getByTestId ( 'advance-search-filter-container' )
522+ ) . toContainText ( `'${ status } '` ) ;
523+ } ) ;
524+
525+ await test . step ( `"${ matchedName } " (${ entry . endpoint } ) is visible` , async ( ) => {
526+ await expect (
527+ page . getByTestId ( `table-data-card_${ entry . fqn ( ) } ` )
528+ ) . toBeVisible ( ) ;
529+ } ) ;
530+
531+ await test . step ( 'Entities with other statuses are not in results' , async ( ) => {
532+ const otherEntries = statusEntries . filter ( ( e ) => e . status !== status ) ;
533+
534+ for ( const other of otherEntries ) {
535+ await expect (
536+ page . getByTestId ( `table-data-card_${ other . fqn ( ) } ` )
537+ ) . not . toBeVisible ( ) ;
538+ }
539+ } ) ;
540+
541+ await page . getByTestId ( 'clear-filters' ) . click ( ) ;
542+ }
543+ } ) ;
544+
545+ test ( 'Filtering by status "!=" excludes matched entity but shows all other entity types' , async ( {
546+ page,
547+ } ) => {
548+ const targetEntry = statusEntries . find (
549+ ( e ) => e . status === EntityStatus . Approved
550+ ) ! ;
551+ const approvedName = targetEntry . displayName ( ) ;
552+ const otherEntries = statusEntries . filter (
553+ ( e ) => e . status !== EntityStatus . Approved
554+ ) ;
555+
556+ await test . step ( 'Apply Status != "Approved" AND Name == approved entity name' , async ( ) => {
557+ await showAdvancedSearchDialog ( page ) ;
558+
559+ await fillStaticListRule ( page , {
560+ fieldLabel : 'Status' ,
561+ condition : '!=' ,
562+ value : EntityStatus . Approved ,
563+ ruleIndex : 1 ,
564+ } ) ;
565+
566+ await page . getByTestId ( 'advanced-search-add-rule' ) . nth ( 1 ) . click ( ) ;
567+
568+ await fillRule ( page , {
569+ condition : '==' ,
570+ field : { id : 'Display Name' , name : 'displayName.keyword' } ,
571+ searchCriteria : approvedName ,
572+ index : 2 ,
573+ } ) ;
574+
575+ const searchRes = page . waitForResponse (
576+ '/api/v1/search/query?*index=dataAsset*'
577+ ) ;
578+ await page . getByTestId ( 'apply-btn' ) . click ( ) ;
579+ await searchRes ;
580+ await waitForAllLoadersToDisappear ( page ) ;
581+ } ) ;
582+
583+ await test . step ( 'Filter chip reflects the != condition' , async ( ) => {
584+ await expect (
585+ page . getByTestId ( 'advance-search-filter-container' )
586+ ) . toContainText ( `'${ EntityStatus . Approved } '` ) ;
587+ } ) ;
588+
589+ await test . step ( 'GlossaryTerm with Approved status is not visible' , async ( ) => {
590+ await expect (
591+ page . getByTestId ( `table-data-card_${ targetEntry . fqn ( ) } ` )
592+ ) . not . toBeVisible ( ) ;
593+ } ) ;
594+
595+ await test . step ( 'Draft and In Review entities appear when searched by their name and non-Approved status' , async ( ) => {
596+ for ( const entry of otherEntries ) {
597+ await page . getByTestId ( 'clear-filters' ) . click ( ) ;
598+ await showAdvancedSearchDialog ( page ) ;
599+
600+ await fillStaticListRule ( page , {
601+ fieldLabel : 'Status' ,
602+ condition : '!=' ,
603+ value : EntityStatus . Approved ,
604+ ruleIndex : 1 ,
605+ } ) ;
606+
607+ await page . getByTestId ( 'advanced-search-add-rule' ) . nth ( 1 ) . click ( ) ;
608+
609+ await fillRule ( page , {
610+ condition : '==' ,
611+ field : { id : 'Display Name' , name : 'displayName.keyword' } ,
612+ searchCriteria : entry . displayName ( ) ,
613+ index : 2 ,
614+ } ) ;
615+
616+ const searchRes = page . waitForResponse (
617+ '/api/v1/search/query?*index=dataAsset*'
618+ ) ;
619+ await page . getByTestId ( 'apply-btn' ) . click ( ) ;
620+ await searchRes ;
621+ await waitForAllLoadersToDisappear ( page ) ;
622+
623+ await expect (
624+ page . getByTestId ( `table-data-card_${ entry . fqn ( ) } ` )
625+ ) . toBeVisible ( ) ;
626+ }
627+ } ) ;
628+
629+ await page . getByTestId ( 'clear-filters' ) . click ( ) ;
630+ } ) ;
631+ }
632+ ) ;
0 commit comments