Skip to content

Commit e18c848

Browse files
Ashish Guptagithub-actions[bot]
andauthored
Data Asset rule enforcement on ui (#24049)
* data asset rule enforcement on ui * added multi user and multi team support at same time * fix the multi user case which wasn't working correctly and clean the ui as well * fix the rule condition for glossary * fix the glossary dropdown getting closed on select select after selection where we should wait for footer dropdown button action * added playwright test for data asset rules table * fix unit test and supported playwright test for table entity * change the method and variable name for better readability * naming convention fix around the glossary validation rule * supported playwright test for all the entities * supported in service entities * spearate the test of rules enable and disable state and added supported in glossaryand teams page of rules owner * Update generated TypeScript types * fix the unit test and supported rule in tags page * fix the generated type issue * fix he domain single select where de-selecting throwing error and fix type * modify the playwrigth config so dataAssetRules should be run after the chromium is finish executing so the global run changes won't interrupt other test * fix all unit test * fix the test failing in dataAssetHeader * trying the chromium parallel test running * modifyed yaml files to run the data asset rules after all machine runs is completed * change the test running order in playwright config * reverted the playwright config * run the data asset rules in only one runner * added support for multiple user and team combination in ownerLabel compoennt * fix the placemnet issue, reduce the team font to 12 and minor cleanup * modify many owner component cases as per ui * clear ruleEnforcementProvider and useEntityRules hooks * change the dropdown on owner user to mui component * fix unit test and make chnages in playwright postgress around test running * fix unit test failing * fix some playwright test due to rule enforcement and made changes in CI playeright config to only run dataasset on container 6 * reverted the baseurl * fix the version view for owner, fix some failing playwright test and re-modify the postgress script to run test * supported the placement option for owner in case of user and team both and postgress e2e change * remove the other e2e file changes and fix playwright around rules changes * fix the e2e command * fix the failing test around disabled rule and based on comments made fix --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 99bd82e commit e18c848

108 files changed

Lines changed: 3989 additions & 564 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/mysql-nightly-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,4 @@ jobs:
189189
SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.E2E_SLACK_BOT_OAUTH_TOKEN }}
190190
run: |
191191
npx playwright merge-reports --reporter json ./blob-reports > merged_tests_results.json
192-
npx playwright-slack-report -c playwright/slack-cli.config.json -j merged_tests_results.json > slack_report.json
192+
npx playwright-slack-report -c playwright/slack-cli.config.json -j merged_tests_results.json > slack_report.json

.github/workflows/playwright-postgresql-e2e.yml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ on:
2727
- 'openmetadata-dist/**'
2828
- 'docker/**'
2929
- '!docker/development/docker-compose.yml'
30-
- '!docker/development/docker-compose-postgres.yml'
30+
- '!docker/development/docker-compose-postgres.yml'
3131

3232
permissions:
3333
contents: read
@@ -109,11 +109,35 @@ jobs:
109109
- name: Run Playwright tests
110110
working-directory: openmetadata-ui/src/main/resources/ui/
111111
run: |
112-
if [[ "${{ contains(github.event.pull_request.changed_files, 'ingestion/') }}" == "true" ]]; then
113-
npx playwright test --grep @ingestion --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
112+
if [ "${{ matrix.shardIndex }}" -eq "1" ]; then
113+
echo "🔹 Running DataAssetRules-only tests on shard 1"
114+
115+
# The testMatch pattern ensures only DataAssetRules*.spec.ts files run
116+
npx playwright test \
117+
--project=setup \
118+
--project=DataAssetRulesEnabled \
119+
--project=DataAssetRulesDisabled
120+
114121
else
115-
npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
122+
# Shards 2-6 handle chromium tests (5 workers total)
123+
CHROMIUM_SHARD=$(( ${{ matrix.shardIndex }} - 1 ))
124+
if [[ "${{ contains(github.event.pull_request.changed_files, 'ingestion/') }}" == "true" ]]; then
125+
echo "🔹 Running ingestion tests on chromium shard ${CHROMIUM_SHARD}/5"
126+
npx playwright test \
127+
--project=chromium \
128+
--grep @ingestion \
129+
--grep-invert @dataAssetRules \
130+
--shard=${CHROMIUM_SHARD}/5
131+
132+
else
133+
echo "🔹 Running all other tests (excluding DataAssetRules) on chromium shard ${CHROMIUM_SHARD}/5"
134+
npx playwright test \
135+
--project=chromium \
136+
--grep-invert @dataAssetRules \
137+
--shard=${CHROMIUM_SHARD}/5
138+
fi
116139
fi
140+
117141
env:
118142
PLAYWRIGHT_IS_OSS: true
119143
PLAYWRIGHT_SNOWFLAKE_USERNAME: ${{ secrets.TEST_SNOWFLAKE_USERNAME }}

.github/workflows/postgresql-nightly-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,4 @@ jobs:
189189
SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.E2E_SLACK_BOT_OAUTH_TOKEN }}
190190
run: |
191191
npx playwright merge-reports --reporter json ./blob-reports > merged_tests_results.json
192-
npx playwright-slack-report -c playwright/slack-cli.config.json -j merged_tests_results.json > slack_report.json
192+
npx playwright-slack-report -c playwright/slack-cli.config.json -j merged_tests_results.json > slack_report.json

openmetadata-ui/src/main/resources/ui/playwright.config.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ export default defineConfig({
7676
// Added admin setup as a dependency. This will authorize the page with an admin user before running the test. doc: https://playwright.dev/docs/auth#multiple-signed-in-roles
7777
dependencies: ['setup', 'entity-data-setup'],
7878
grepInvert: /data-insight/,
79-
testIgnore: ['**/nightly/**'],
8079
teardown: 'entity-data-teardown',
80+
testIgnore: [
81+
'**/nightly/**',
82+
'**/DataAssetRulesEnabled.spec.ts',
83+
'**/DataAssetRulesDisabled.spec.ts',
84+
],
8185
},
8286
{
8387
name: 'entity-data-teardown',
@@ -95,6 +99,20 @@ export default defineConfig({
9599
grep: /data-insight/,
96100
teardown: 'entity-data-teardown',
97101
},
102+
{
103+
name: 'DataAssetRulesEnabled',
104+
testMatch: '**/DataAssetRulesEnabled.spec.ts',
105+
use: { ...devices['Desktop Chrome'] },
106+
dependencies: ['setup'],
107+
fullyParallel: true,
108+
},
109+
{
110+
name: 'DataAssetRulesDisabled',
111+
testMatch: '**/DataAssetRulesDisabled.spec.ts',
112+
use: { ...devices['Desktop Chrome'] },
113+
dependencies: ['DataAssetRulesEnabled'],
114+
fullyParallel: true,
115+
},
98116
],
99117

100118
// Increase timeout for the test

openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export enum GlobalSettingOptions {
9191
LINEAGE_CONFIG = 'lineageConfig',
9292
OM_URL_CONFIG = 'om-url-config',
9393
SEARCH_SETTINGS = 'search-settings',
94+
DATA_ASSET_RULES = 'dataAssetRules',
9495
DOMAINS = 'domains',
9596
ONLINE_USERS = 'online-users',
9697
CHARTS = 'charts',
@@ -238,6 +239,10 @@ export const SETTINGS_OPTIONS_PATH = {
238239
GlobalSettingsMenuCategory.PREFERENCES,
239240
`${GlobalSettingsMenuCategory.PREFERENCES}.${GlobalSettingOptions.SEARCH_SETTINGS}`,
240241
],
242+
[GlobalSettingOptions.DATA_ASSET_RULES]: [
243+
GlobalSettingsMenuCategory.PREFERENCES,
244+
`${GlobalSettingsMenuCategory.PREFERENCES}.${GlobalSettingOptions.DATA_ASSET_RULES}`,
245+
],
241246
[GlobalSettingOptions.SSO]: [GlobalSettingsMenuCategory.SSO],
242247
};
243248

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/*
2+
* Copyright 2025 Collate.
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
import { expect } from '@playwright/test';
14+
import { DataProduct } from '../../support/domain/DataProduct';
15+
import { Domain } from '../../support/domain/Domain';
16+
import { ApiCollectionClass } from '../../support/entity/ApiCollectionClass';
17+
import { ApiEndpointClass } from '../../support/entity/ApiEndpointClass';
18+
import { ChartClass } from '../../support/entity/ChartClass';
19+
import { ContainerClass } from '../../support/entity/ContainerClass';
20+
import { DashboardClass } from '../../support/entity/DashboardClass';
21+
import { DashboardDataModelClass } from '../../support/entity/DashboardDataModelClass';
22+
import { DatabaseClass } from '../../support/entity/DatabaseClass';
23+
import { DatabaseSchemaClass } from '../../support/entity/DatabaseSchemaClass';
24+
import { DirectoryClass } from '../../support/entity/DirectoryClass';
25+
import { FileClass } from '../../support/entity/FileClass';
26+
import { MetricClass } from '../../support/entity/MetricClass';
27+
import { MlModelClass } from '../../support/entity/MlModelClass';
28+
import { PipelineClass } from '../../support/entity/PipelineClass';
29+
import { SearchIndexClass } from '../../support/entity/SearchIndexClass';
30+
import { ApiServiceClass } from '../../support/entity/service/ApiServiceClass';
31+
import { DashboardServiceClass } from '../../support/entity/service/DashboardServiceClass';
32+
import { DatabaseServiceClass } from '../../support/entity/service/DatabaseServiceClass';
33+
import { DriveServiceClass } from '../../support/entity/service/DriveServiceClass';
34+
import { MessagingServiceClass } from '../../support/entity/service/MessagingServiceClass';
35+
import { MlmodelServiceClass } from '../../support/entity/service/MlmodelServiceClass';
36+
import { PipelineServiceClass } from '../../support/entity/service/PipelineServiceClass';
37+
import { SearchIndexServiceClass } from '../../support/entity/service/SearchIndexServiceClass';
38+
import { StorageServiceClass } from '../../support/entity/service/StorageServiceClass';
39+
import { SpreadsheetClass } from '../../support/entity/SpreadsheetClass';
40+
import { StoredProcedureClass } from '../../support/entity/StoredProcedureClass';
41+
import { TableClass } from '../../support/entity/TableClass';
42+
import { TopicClass } from '../../support/entity/TopicClass';
43+
import { WorksheetClass } from '../../support/entity/WorksheetClass';
44+
import { Glossary } from '../../support/glossary/Glossary';
45+
import { GlossaryTerm } from '../../support/glossary/GlossaryTerm';
46+
import { TeamClass } from '../../support/team/TeamClass';
47+
import { UserClass } from '../../support/user/UserClass';
48+
import { performAdminLogin } from '../../utils/admin';
49+
import {
50+
assignDataProduct,
51+
assignDomain,
52+
redirectToHomePage,
53+
} from '../../utils/common';
54+
import {
55+
DATA_ASSET_RULES,
56+
DEFAULT_DATA_ASSET_RULES,
57+
} from '../../utils/dataAssetRules';
58+
import { addMultiOwner, assignGlossaryTerm } from '../../utils/entity';
59+
import { test } from '../fixtures/pages';
60+
61+
const entities = [
62+
ApiEndpointClass,
63+
TableClass,
64+
StoredProcedureClass,
65+
DashboardClass,
66+
PipelineClass,
67+
TopicClass,
68+
MlModelClass,
69+
ContainerClass,
70+
SearchIndexClass,
71+
DashboardDataModelClass,
72+
MetricClass,
73+
ChartClass,
74+
DirectoryClass,
75+
FileClass,
76+
SpreadsheetClass,
77+
WorksheetClass,
78+
ApiServiceClass,
79+
ApiCollectionClass,
80+
DatabaseServiceClass,
81+
DashboardServiceClass,
82+
MessagingServiceClass,
83+
MlmodelServiceClass,
84+
PipelineServiceClass,
85+
SearchIndexServiceClass,
86+
StorageServiceClass,
87+
DatabaseClass,
88+
DatabaseSchemaClass,
89+
DriveServiceClass,
90+
] as const;
91+
92+
const user = new UserClass();
93+
const user2 = new UserClass();
94+
const team = new TeamClass();
95+
const table = new TableClass();
96+
const table2 = new TableClass();
97+
const table3 = new TableClass();
98+
const domain = new Domain();
99+
const domain2 = new Domain();
100+
const testDataProducts = [new DataProduct([domain]), new DataProduct([domain])];
101+
const createdDataProducts: DataProduct[] = [];
102+
const glossary = new Glossary();
103+
const glossaryTerm = new GlossaryTerm(glossary);
104+
const glossaryTerm2 = new GlossaryTerm(glossary);
105+
106+
test.beforeAll('Setup pre-requests', async ({ browser }) => {
107+
test.slow(true);
108+
109+
const { apiContext, afterAction } = await performAdminLogin(browser);
110+
await user.create(apiContext);
111+
await user2.create(apiContext);
112+
await team.create(apiContext);
113+
await table.create(apiContext);
114+
await table2.create(apiContext);
115+
await table3.create(apiContext);
116+
await domain.create(apiContext);
117+
await domain2.create(apiContext);
118+
for (const dp of testDataProducts) {
119+
await dp.create(apiContext);
120+
createdDataProducts.push(dp);
121+
}
122+
await glossary.create(apiContext);
123+
await glossaryTerm.create(apiContext);
124+
await glossaryTerm2.create(apiContext);
125+
126+
// Enable All the Data Asset Rules
127+
await apiContext.put(`/api/v1/system/settings`, {
128+
data: {
129+
config_type: 'entityRulesSettings',
130+
config_value: {
131+
entitySemantics: DATA_ASSET_RULES.map((item) => ({
132+
...item,
133+
enabled: false,
134+
})),
135+
},
136+
},
137+
headers: {
138+
'Content-Type': 'application/json',
139+
},
140+
});
141+
142+
await afterAction();
143+
});
144+
145+
test.afterAll('Cleanup', async ({ browser }) => {
146+
const { apiContext, afterAction } = await performAdminLogin(browser);
147+
// Reset Rules to Default
148+
await apiContext.put(`/api/v1/system/settings`, {
149+
data: {
150+
config_type: 'entityRulesSettings',
151+
config_value: {
152+
entitySemantics: DEFAULT_DATA_ASSET_RULES,
153+
},
154+
},
155+
headers: {
156+
'Content-Type': 'application/json',
157+
},
158+
});
159+
160+
await afterAction();
161+
});
162+
163+
test.describe(
164+
`Data Asset Rules Disabled`,
165+
{
166+
tag: '@dataAssetRules',
167+
},
168+
() => {
169+
for (const EntityClass of entities) {
170+
const entity = new EntityClass();
171+
const entityName = entity.getType();
172+
173+
test(`Verify the ${entityName} entity item action after rules disabled`, async ({
174+
page,
175+
browser,
176+
}) => {
177+
test.slow(true);
178+
179+
const { apiContext, afterAction } = await performAdminLogin(browser);
180+
await entity.create(apiContext);
181+
await afterAction();
182+
183+
await redirectToHomePage(page);
184+
await entity.visitEntityPage(page);
185+
186+
// Assign and Team and User both owner together
187+
const teamName = team.responseData.displayName;
188+
await addMultiOwner({
189+
page,
190+
ownerNames: [user.getUserDisplayName(), user2.getUserDisplayName()],
191+
activatorBtnDataTestId: 'edit-owner',
192+
resultTestId: 'data-assets-header',
193+
endpoint: entity.endpoint,
194+
type: 'Users',
195+
});
196+
197+
await page.click(`[data-testid="edit-owner"]`);
198+
199+
await expect(
200+
page.locator("[data-testid='select-owner-tabs']")
201+
).toBeVisible();
202+
203+
await page.waitForSelector(
204+
'[data-testid="select-owner-tabs"] [data-testid="loader"]',
205+
{ state: 'detached' }
206+
);
207+
208+
await page
209+
.locator("[data-testid='select-owner-tabs']")
210+
.getByRole('tab', { name: 'Teams' })
211+
.click();
212+
213+
await page.waitForSelector(
214+
'[data-testid="select-owner-tabs"] [data-testid="loader"]',
215+
{ state: 'detached' }
216+
);
217+
218+
const searchUser = page.waitForResponse(
219+
`/api/v1/search/query?q=*${encodeURIComponent(teamName)}*`
220+
);
221+
await page.getByTestId(`owner-select-teams-search-bar`).fill(teamName);
222+
await searchUser;
223+
224+
const ownerItem = page.getByRole('listitem', {
225+
name: teamName,
226+
exact: true,
227+
});
228+
229+
await ownerItem.waitFor({ state: 'visible' });
230+
await ownerItem.click();
231+
const patchRequest = page.waitForResponse(
232+
`/api/v1/${entity.endpoint}/*`
233+
);
234+
await page
235+
.locator('[id^="rc-tabs-"][id$="-panel-teams"]')
236+
.getByTestId('selectable-list-update-btn')
237+
.click();
238+
await patchRequest;
239+
240+
await expect(
241+
page.getByTestId('data-assets-header').getByTestId(`${teamName}`)
242+
).toBeVisible();
243+
244+
for (const name of [
245+
user.getUserDisplayName(),
246+
user2.getUserDisplayName(),
247+
teamName,
248+
]) {
249+
await expect(
250+
page.getByTestId('data-assets-header').getByTestId(`${name}`)
251+
).toBeVisible();
252+
}
253+
254+
await assignDomain(page, domain.responseData);
255+
await assignDomain(page, domain2.responseData, false);
256+
257+
await expect(page.getByTestId('domain-count-button')).toBeVisible();
258+
259+
// Add Multiple DataProduct, since default single select is off
260+
if (!entityName.includes('Service')) {
261+
await assignDataProduct(page, domain.responseData, [
262+
createdDataProducts[0].responseData,
263+
]);
264+
265+
await assignDataProduct(
266+
page,
267+
domain.responseData,
268+
[createdDataProducts[1].responseData],
269+
'Edit'
270+
);
271+
}
272+
273+
// Add Multiple GlossaryTerm to Table
274+
await assignGlossaryTerm(page, glossaryTerm.responseData);
275+
await assignGlossaryTerm(page, glossaryTerm2.responseData, 'Edit');
276+
});
277+
}
278+
}
279+
);

0 commit comments

Comments
 (0)