Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* limitations under the License.
*/
import test, { expect } from '@playwright/test';
import { SidebarItem } from '../../constant/sidebar';
import { Domain } from '../../support/domain/Domain';
import { TableClass } from '../../support/entity/TableClass';
import { UserClass } from '../../support/user/UserClass';
import {
Expand All @@ -19,7 +21,17 @@ import {
descriptionBox,
redirectToHomePage,
} from '../../utils/common';
import { createQueryByTableName, queryFilters } from '../../utils/query';
import { sidebarClick } from '../../utils/sidebar';
import {
selectActiveGlobalDomain,
selectAllDomainsFromDropdown,
} from '../../utils/domain';
import {
createQueryByTableName,
createQueryViaUI,
navigateToTableQueriesTab,
queryFilters,
} from '../../utils/query';

// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
Expand Down Expand Up @@ -413,6 +425,115 @@ test('Verify Query Pagination', async ({ page, browser }) => {
await afterAction();
});

test.describe('Query Domain Filtering', () => {
const domainTable = new TableClass();
const domain1 = new Domain();
const domain2 = new Domain();
const query1Text = `SELECT * FROM domain1_query_${Date.now()}`;
const query2Text = `SELECT * FROM domain2_query_${Date.now() + 1}`;

test.beforeAll(async ({ browser }) => {
const { afterAction, apiContext } = await createNewPage(browser);
await domain1.create(apiContext);
await domain2.create(apiContext);
await domainTable.create(apiContext);
await afterAction();
});

test.afterAll(async ({ browser }) => {
const { afterAction, apiContext } = await createNewPage(browser);
await domainTable.delete(apiContext);
await domain1.delete(apiContext);
await domain2.delete(apiContext);
await afterAction();
});

test('Queries should be filtered by active domain selection', async ({
page,
}) => {
test.slow(true);
await redirectToHomePage(page);
await sidebarClick(page, SidebarItem.EXPLORE);

await test.step('Select Domain 1 and create Query 1', async () => {
await selectActiveGlobalDomain(page, domain1.responseData);
await createQueryViaUI({
page,
table: domainTable,
queryText: query1Text,
description: 'Query 1 with Domain 1',
});
});

await test.step('Select Domain 2 and create Query 2', async () => {
await selectAllDomainsFromDropdown(page);
await selectActiveGlobalDomain(page, domain2.responseData);
await createQueryViaUI({
page,
table: domainTable,
queryText: query2Text,
description: 'Query 2 with Domain 2',
});
});

await test.step(
'With Domain 1 selected - only Query 1 visible',
async () => {
await selectAllDomainsFromDropdown(page);
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async function body is not properly indented. The opening brace on line 481 should be followed by a consistently indented block starting at line 482. Currently line 482 has incorrect indentation that doesn't align with standard formatting.

Suggested change
await selectAllDomainsFromDropdown(page);
await selectAllDomainsFromDropdown(page);

Copilot uses AI. Check for mistakes.
await selectActiveGlobalDomain(page, domain1.responseData);
await navigateToTableQueriesTab({ page, table: domainTable });

await expect(page.getByText(query1Text)).toBeVisible();
await expect(page.getByText(query2Text)).not.toBeVisible();

const queryTab1 = page.getByTestId('table_queries');
const countText1 = await queryTab1
.locator('[data-testid="count"]')
.textContent();

expect(parseInt(countText1 || '0', 10)).toBe(1);
}
);

await test.step(
'With Domain 2 selected - only Query 2 visible',
async () => {
await selectAllDomainsFromDropdown(page);
await selectActiveGlobalDomain(page, domain2.responseData);
await navigateToTableQueriesTab({ page, table: domainTable });

await expect(page.getByText(query2Text)).toBeVisible();
await expect(page.getByText(query1Text)).not.toBeVisible();

const queryTab2 = page.getByTestId('table_queries');
const countText2 = await queryTab2
.locator('[data-testid="count"]')
.textContent();

expect(parseInt(countText2 || '0', 10)).toBe(1);
}
);

await test.step(
'With All Domains selected - both queries visible',
async () => {
await selectAllDomainsFromDropdown(page);
await navigateToTableQueriesTab({ page, table: domainTable });

await expect(page.getByText(query1Text)).toBeVisible();
await expect(page.getByText(query2Text)).toBeVisible();

const queryTab3 = page.getByTestId('table_queries');
const countText3 = await queryTab3
.locator('[data-testid="count"]')
.textContent();

expect(parseInt(countText3 || '0', 10)).toBeGreaterThanOrEqual(2);
}
);
});
});

test.afterAll(async ({ browser }) => {
const { afterAction, apiContext } = await createNewPage(browser);
for (const entity of entityData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ export class TableClass extends EntityClass {
page,
searchTerm: searchTerm ?? this.entityResponseData?.['fullyQualifiedName'],
dataTestId: `${
this.entityResponseData.service.name ?? this.service.name
}-${this.entityResponseData.name ?? this.entity.name}`,
this.entityResponseData?.service?.name ?? this.service.name
}-${this.entityResponseData?.name ?? this.entity.name}`,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,42 @@ export const verifyActiveDomainIsDefault = async (page: Page) => {
);
};

export const selectActiveGlobalDomain = async (
page: Page,
domainData: { name: string; displayName: string; fullyQualifiedName?: string }
) => {
await page.getByTestId('domain-dropdown').click();

await page.waitForSelector('[data-testid="domain-selectable-tree"]', {
state: 'visible',
});

const domainSearchResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/search/query') &&
response.url().includes('domain_search_index')
);

await page
.getByTestId('domain-selectable-tree')
.getByTestId('searchbar')
.fill(domainData.displayName);

await domainSearchResponse;

await page.getByText(domainData.displayName).click();


await page.waitForSelector('[data-testid="domain-selectable-tree"]', {
state: 'hidden',
});
};

export const selectAllDomainsFromDropdown = async (page: Page) => {
await page.getByTestId('domain-dropdown').click();
await page.getByTestId('all-domains-selector').click();
};

/**
* Sets up a complete environment for domain ownership testing
* Creates user, policy, role, domain, data product and assigns ownership
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { APIRequestContext, Page } from '@playwright/test';
import { APIRequestContext, expect, Page } from '@playwright/test';
import { TableClass } from '../support/entity/TableClass';
import { ResponseDataWithServiceType } from '../support/entity/Entity.interface';
import { descriptionBox } from './common';

export const createQueryByTableName = async (data: {
apiContext: APIRequestContext;
Expand Down Expand Up @@ -62,3 +64,71 @@ export const queryFilters = async ({
await page.click('[data-testid="update-btn"]');
await queryResponse;
};

export const createQueryViaUI = async ({
page,
table,
queryText,
description,
}: {
page: Page;
table: TableClass;
queryText: string;
description?: string;
}) => {
const tableFqn = table.entityResponseData?.fullyQualifiedName;
await page.goto(`/table/${encodeURIComponent(tableFqn)}`);
await page.waitForLoadState('networkidle');

const queryResponse = page.waitForResponse(
'/api/v1/search/query?q=*&index=query_search_index*'
);
await page.click('[data-testid="table_queries"]');
await queryResponse;

await page.click('[data-testid="add-query-btn"]');
await page.waitForLoadState('networkidle');

await page
.getByTestId('code-mirror-container')
.getByRole('textbox')
.fill(queryText);

if (description) {
await page.click(descriptionBox);
await page.keyboard.type(description);
}

const createQueryResponse = page.waitForResponse('/api/v1/queries');
await page.click('[data-testid="save-btn"]');
await createQueryResponse;

await page.waitForURL('**/table_queries**');

await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
await expect(page.getByText(queryText)).toBeVisible();
};

export const navigateToTableQueriesTab = async ({
page,
table,
}: {
page: Page;
table: TableClass;
}) => {
const tableFqn = table.entityResponseData?.fullyQualifiedName;
await page.goto(`/table/${encodeURIComponent(tableFqn)}`);
await page.waitForLoadState('networkidle');

const queryResponse = page.waitForResponse(
'/api/v1/search/query?q=*&index=query_search_index*'
);
await page.click('[data-testid="table_queries"]');
await queryResponse;

await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import SchemaEditor from '../../components/Database/SchemaEditor/SchemaEditor';
import { HTTP_STATUS_CODE } from '../../constants/Auth.constants';
import {
DEFAULT_DOMAIN_VALUE,
INITIAL_PAGING_VALUE,
PAGE_SIZE_MEDIUM,
} from '../../constants/constants';
Expand All @@ -38,6 +39,7 @@
import { withPageLayout } from '../../hoc/withPageLayout';
import { useApplicationStore } from '../../hooks/useApplicationStore';
import { useFqn } from '../../hooks/useFqn';
import { useDomainStore } from '../../hooks/useDomainStore';
import { FieldProp, FieldTypes } from '../../interface/FormUtils.interface';
import { postQuery } from '../../rest/queryAPI';
import { searchQuery } from '../../rest/searchAPI';
Expand Down Expand Up @@ -67,6 +69,7 @@
const [initialOptions, setInitialOptions] = useState<DefaultOptionType[]>();
const [isSaving, setIsSaving] = useState(false);
const navigate = useNavigate();
const hasActiveDomain = useDomainStore.getState().activeDomain !== DEFAULT_DOMAIN_VALUE;
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hasActiveDomain variable is computed at render time and cached, but activeDomain is fetched fresh at submit time (line 152). This creates an inconsistency where hasActiveDomain might not match the current activeDomain value when the form is submitted. This means if a user switches domains after the component renders but before submitting, the wrong domain logic will be applied. Move the hasActiveDomain check inside handleSubmit to ensure it uses the same activeDomain value that gets assigned to the query.

Copilot uses AI. Check for mistakes.

const fetchEntityDetails = async () => {
try {
Expand Down Expand Up @@ -114,9 +117,9 @@
return table
? filter(options, ({ value }) => value !== table.id)
: options;
} catch (error) {
return [];
}

Check warning on line 122 in openmetadata-ui/src/main/resources/ui/src/pages/AddQueryPage/AddQueryPage.component.tsx

View check run for this annotation

SonarQubeCloud / [open-metadata-ui] SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=open-metadata-ui&issues=AZsguDFPqlf51wHRWAI4&open=AZsguDFPqlf51wHRWAI4&pullRequest=24807
};

useEffect(() => {
Expand All @@ -129,9 +132,9 @@
try {
const option = await fetchTableEntity();
setInitialOptions(option);
} catch (error) {
setInitialOptions([]);
}

Check warning on line 137 in openmetadata-ui/src/main/resources/ui/src/pages/AddQueryPage/AddQueryPage.component.tsx

View check run for this annotation

SonarQubeCloud / [open-metadata-ui] SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=open-metadata-ui&issues=AZsguDFPqlf51wHRWAI5&open=AZsguDFPqlf51wHRWAI5&pullRequest=24807
};

useEffect(() => {
Expand All @@ -146,6 +149,7 @@

const handleSubmit: FormProps['onFinish'] = async (values): Promise<void> => {
setIsSaving(true);
const activeDomain = useDomainStore.getState().activeDomain;
const updatedValues: CreateQuery = {
...values,
description: isEmpty(description) ? undefined : description,
Expand All @@ -167,6 +171,10 @@
],
queryDate: getCurrentMillis(),
service: getPartialNameFromFQN(datasetFQN, ['service']),
domains:
hasActiveDomain
? [activeDomain]
: [],
};

try {
Expand Down
Loading