Skip to content

Commit e43d809

Browse files
authored
Merge branch 'main' into fix-entity-spec-failure-aut
2 parents 717644e + 3accb96 commit e43d809

11 files changed

Lines changed: 319 additions & 53 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ jobs:
158158
--project=DataAssetRulesEnabled \
159159
--project=DataAssetRulesDisabled \
160160
--project=Basic \
161+
--project=SearchRBAC \
161162
162163
else
163164
# Shards 2-6 run common chromium tests equally distributed (5-way sharding)

openmetadata-spec/src/main/resources/json/schema/entity/services/connections/pipeline/nifi/clientCertificateAuth.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
"type": "object",
88
"properties": {
99
"certificateAuthorityPath": {
10-
"title": "Certificat Authority Path",
10+
"title": "Certificate Authority Path",
1111
"description": "Path to the root CA certificate",
1212
"type": "string"
1313
},
1414
"clientCertificatePath": {
15-
"title": "Client Certificat",
15+
"title": "Client Certificate",
1616
"description": "Path to the client certificate",
1717
"type": "string"
1818
},

openmetadata-spec/src/main/resources/json/schema/entity/services/connections/pipeline/nifiConnection.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@
4545
"javaType": "org.openmetadata.schema.services.connections.pipeline.NifiClientAuth",
4646
"properties": {
4747
"certificateAuthorityPath":{
48-
"title":"Certificat Authority Path",
48+
"title":"Certificate Authority Path",
4949
"description": "Path to the root CA certificate",
5050
"type": "string"
5151
},
5252
"clientCertificatePath":{
53-
"title":"Client Certificat",
53+
"title":"Client Certificate",
5454
"description": "Path to the client certificate",
5555
"type": "string"
5656
},

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default defineConfig({
8181
// 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
8282
dependencies: ['setup', 'entity-data-setup'],
8383
grepInvert: [/@data-insight/, /@basic/, /@knowledge-graph/],
84-
teardown: 'SearchRBAC',
84+
teardown: 'entity-data-teardown',
8585
testIgnore: [
8686
'**/nightly/**',
8787
'**/Auth/**',
@@ -91,12 +91,6 @@ export default defineConfig({
9191
'**/SearchRBAC.spec.ts',
9292
],
9393
},
94-
{
95-
name: 'SearchRBAC',
96-
testMatch: '**/SearchRBAC.spec.ts',
97-
use: { ...devices['Desktop Chrome'] },
98-
teardown: 'entity-data-teardown',
99-
},
10094
{
10195
name: 'entity-data-teardown',
10296
testMatch: '**/entity-data.teardown.ts',
@@ -141,6 +135,13 @@ export default defineConfig({
141135
dependencies: ['setup'],
142136
fullyParallel: true,
143137
},
138+
{
139+
name: 'SearchRBAC',
140+
testMatch: '**/SearchRBAC.spec.ts',
141+
dependencies: ['DataAssetRulesDisabled'],
142+
use: { ...devices['Desktop Chrome'] },
143+
teardown: 'entity-data-teardown',
144+
},
144145
// System Certification Tags tests modify global shared state (system tags like Gold, Silver, Bronze)
145146
// They must run in isolation after the main chromium project to avoid flakiness
146147
{

openmetadata-ui/src/main/resources/ui/playwright/e2e/nightly/ServiceIngestion.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,14 +523,23 @@ test.describe.serial(
523523
await pipelineRow.getByTestId('more-actions').click();
524524
await expect(page.getByTestId('run-button')).toBeVisible();
525525

526-
// Trigger a pipeline run via the run button
526+
// Trigger a pipeline run via the run button.
527+
// Also register a waiter for the pipelineStatus refresh that follows the trigger
528+
// (the route mock adds 8s latency, so we must await the response before asserting).
529+
// Both waiters are registered before the click to avoid race conditions.
527530
const triggerResponse = page.waitForResponse(
528531
(res) =>
529532
res.url().includes('/services/ingestionPipelines/trigger/') &&
530533
res.request().method() === 'POST'
531534
);
535+
const statusRefreshResponse = page.waitForResponse(
536+
(res) =>
537+
res.url().includes('/pipelineStatus') &&
538+
res.request().method() === 'GET'
539+
);
532540
await page.getByTestId('run-button').click();
533541
await triggerResponse;
542+
await statusRefreshResponse;
534543

535544
// Verify the run was triggered by checking the pipeline row shows a running state
536545
await expect(

openmetadata-ui/src/main/resources/ui/src/constants/Table.constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ export const TABLE_CONSTRAINTS_TYPE_OPTIONS = [
5353
},
5454
value: ConstraintType.DistKey,
5555
},
56+
{
57+
label: 'label.entity-key',
58+
labelData: {
59+
entity: 'label.cluster',
60+
},
61+
value: ConstraintType.ClusterKey,
62+
},
5663
{
5764
label: 'label.entity-key',
5865
labelData: {

openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableConstraints/ConstraintIcon.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,14 @@ const ConstraintIcon = ({
5656
entity: t('label.dist'),
5757
}),
5858
};
59-
59+
case ConstraintType.ClusterKey:
60+
return {
61+
icon: IconDistribution,
62+
title: t('label.entity-key', {
63+
entity: t('label.cluster'),
64+
}),
65+
};
66+
case ConstraintType.SortKey:
6067
default:
6168
return {
6269
icon: IconSort,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2026 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+
14+
import { render, screen } from '@testing-library/react';
15+
import { ConstraintType, Table } from '../../../generated/entity/data/table';
16+
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
17+
import { tableConstraintRendererBasedOnType } from '../../../utils/TableUtils';
18+
import TableConstraints from './TableConstraints';
19+
20+
const mockOnUpdate = jest.fn().mockResolvedValue(undefined);
21+
22+
const mockGenericContextProps = {
23+
data: {
24+
tableConstraints: [
25+
{
26+
constraintType: ConstraintType.ClusterKey,
27+
columns: ['Entity_ID', 'Entity_Type'],
28+
},
29+
],
30+
} as Table,
31+
permissions: {
32+
...DEFAULT_ENTITY_PERMISSION,
33+
EditAll: false,
34+
},
35+
onUpdate: mockOnUpdate,
36+
};
37+
38+
jest.mock('../../../utils/CommonUtils', () => ({
39+
getPartialNameFromTableFQN: jest.fn().mockImplementation((value) => value),
40+
}));
41+
42+
jest.mock('../../../utils/EntityUtilClassBase', () => ({
43+
getEntityLink: jest.fn().mockImplementation(() => '#'),
44+
}));
45+
46+
jest.mock('../../../utils/TableUtils', () => ({
47+
tableConstraintRendererBasedOnType: jest
48+
.fn()
49+
.mockImplementation((constraintType, columns) => (
50+
<div data-testid={`${constraintType}-container`} key={constraintType}>
51+
{(columns ?? []).join(',')}
52+
</div>
53+
)),
54+
}));
55+
56+
jest.mock(
57+
'./TableConstraintsModal/TableConstraintsModal.component',
58+
() => () => <div />
59+
);
60+
61+
jest.mock(
62+
'../../../components/Customization/GenericProvider/GenericProvider',
63+
() => ({
64+
useGenericContext: jest
65+
.fn()
66+
.mockImplementation(() => mockGenericContextProps),
67+
})
68+
);
69+
70+
const mockTableConstraintRendererBasedOnType =
71+
tableConstraintRendererBasedOnType as jest.MockedFunction<
72+
typeof tableConstraintRendererBasedOnType
73+
>;
74+
75+
describe('TableConstraints', () => {
76+
beforeEach(() => {
77+
mockTableConstraintRendererBasedOnType.mockClear();
78+
});
79+
80+
it('renders cluster key constraints in table details', () => {
81+
render(<TableConstraints renderAsExpandableCard={false} />);
82+
83+
expect(
84+
screen.getByTestId(`${ConstraintType.ClusterKey}-container`)
85+
).toBeInTheDocument();
86+
expect(mockTableConstraintRendererBasedOnType).toHaveBeenCalledWith(
87+
ConstraintType.ClusterKey,
88+
['Entity_ID', 'Entity_Type']
89+
);
90+
});
91+
});

openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableConstraints/TableConstraints.tsx

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -93,33 +93,19 @@ const TableConstraints = ({
9393
<Space className="w-full new-header-border-card" direction="vertical">
9494
{data?.tableConstraints?.map(
9595
({ constraintType, columns, referredColumns }) => {
96-
if (constraintType === ConstraintType.PrimaryKey) {
97-
return tableConstraintRendererBasedOnType(
96+
if (
97+
constraintType &&
98+
[
9899
ConstraintType.PrimaryKey,
99-
columns
100-
);
101-
}
102-
103-
if (constraintType === ConstraintType.SortKey) {
104-
return tableConstraintRendererBasedOnType(
105100
ConstraintType.SortKey,
106-
columns
107-
);
108-
}
109-
110-
if (constraintType === ConstraintType.DistKey) {
111-
return tableConstraintRendererBasedOnType(
112101
ConstraintType.DistKey,
113-
columns
114-
);
115-
}
116-
117-
if (constraintType === ConstraintType.Unique) {
118-
return tableConstraintRendererBasedOnType(
102+
ConstraintType.ClusterKey,
119103
ConstraintType.Unique,
120-
columns
121-
);
104+
].includes(constraintType)
105+
) {
106+
return tableConstraintRendererBasedOnType(constraintType, columns);
122107
}
108+
123109
if (constraintType === ConstraintType.ForeignKey) {
124110
return (
125111
<div
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2026 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+
14+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
15+
import { TABLE_CONSTRAINTS_TYPE_OPTIONS } from '../../../../constants/Table.constants';
16+
import {
17+
ConstraintType,
18+
DataType,
19+
Table,
20+
} from '../../../../generated/entity/data/table';
21+
import { searchQuery } from '../../../../rest/searchAPI';
22+
import TableConstraintsModal from './TableConstraintsModal.component';
23+
24+
const mockOnSave = jest.fn().mockResolvedValue(undefined);
25+
const mockOnClose = jest.fn();
26+
const mockSearchQuery = searchQuery as jest.MockedFunction<typeof searchQuery>;
27+
28+
const mockTableDetails = {
29+
service: {
30+
name: 'repro_cluster_service',
31+
},
32+
columns: [
33+
{
34+
name: 'Entity_ID',
35+
dataType: DataType.Int,
36+
},
37+
{
38+
name: 'Entity_Type',
39+
dataType: DataType.Int,
40+
},
41+
],
42+
} as Table;
43+
44+
const mockConstraint: Table['tableConstraints'] = [
45+
{
46+
constraintType: ConstraintType.ClusterKey,
47+
columns: ['Entity_ID', 'Entity_Type'],
48+
},
49+
];
50+
51+
jest.mock('../../../../utils/EntityUtils', () => ({
52+
getBreadcrumbsFromFqn: jest
53+
.fn()
54+
.mockImplementation(() => [
55+
{ name: 'service' },
56+
{ name: 'database' },
57+
{ name: 'schema' },
58+
{ name: 'table' },
59+
{ name: 'column' },
60+
]),
61+
}));
62+
63+
jest.mock('../../../../utils/ServiceUtils', () => ({
64+
getServiceNameQueryFilter: jest.fn().mockImplementation(() => ''),
65+
}));
66+
67+
jest.mock('../../../../utils/StringsUtils', () => ({
68+
escapeESReservedCharacters: jest.fn().mockImplementation((value) => value),
69+
getEncodedFqn: jest.fn().mockImplementation((value) => value),
70+
}));
71+
72+
jest.mock('../../../../utils/TableUtils', () => ({
73+
createTableConstraintObject: jest
74+
.fn()
75+
.mockImplementation((constraints, type) =>
76+
Array.isArray(constraints) && constraints.length > 0
77+
? [{ columns: constraints, constraintType: type }]
78+
: []
79+
),
80+
getColumnOptionsFromTableColumn: jest.fn().mockImplementation((columns) =>
81+
(columns ?? []).map((column: { name: string }) => ({
82+
label: column.name,
83+
value: column.name,
84+
}))
85+
),
86+
}));
87+
88+
jest.mock('../../../../rest/searchAPI', () => ({
89+
searchQuery: jest.fn().mockResolvedValue({ hits: { hits: [] } }),
90+
}));
91+
92+
describe('TableConstraintsModal', () => {
93+
beforeEach(() => {
94+
mockOnSave.mockClear();
95+
mockOnClose.mockClear();
96+
mockSearchQuery.mockClear();
97+
});
98+
99+
it('includes cluster key in available constraint types', () => {
100+
expect(
101+
TABLE_CONSTRAINTS_TYPE_OPTIONS.some(
102+
({ value }) => value === ConstraintType.ClusterKey
103+
)
104+
).toBe(true);
105+
});
106+
107+
it('preserves cluster key constraints on save', async () => {
108+
render(
109+
<TableConstraintsModal
110+
constraint={mockConstraint}
111+
tableDetails={mockTableDetails}
112+
onClose={mockOnClose}
113+
onSave={mockOnSave}
114+
/>
115+
);
116+
117+
fireEvent.click(screen.getByTestId('save-btn'));
118+
119+
await waitFor(() => {
120+
expect(mockOnSave).toHaveBeenCalledWith(
121+
expect.arrayContaining([
122+
expect.objectContaining({
123+
constraintType: ConstraintType.ClusterKey,
124+
columns: ['Entity_ID', 'Entity_Type'],
125+
}),
126+
])
127+
);
128+
});
129+
130+
expect(mockSearchQuery).not.toHaveBeenCalled();
131+
});
132+
});

0 commit comments

Comments
 (0)