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