Merge branch 'main' into next

This commit is contained in:
xilesun 2024-07-01 10:24:26 +08:00
commit 74ef97343b
5 changed files with 194 additions and 79 deletions

View File

@ -1,25 +0,0 @@
## Description
### Steps to reproduce
<!-- Clear steps to reproduce the bug. -->
### Expected behavior
<!--- Describe what the expected behavior should be when the code is executed without the bug. -->
### Actual behavior
<!-- Describe what actually happens when the code is executed with the bug. -->
## Related issues
<!-- Include any related issues or previous bug reports related to this bug. -->
## Reason
<!-- Explain what caused the bug to occur. -->
## Solution
<!-- Describe solution to the bug clearly and consciously. -->

View File

@ -1,28 +0,0 @@
## Description
<!-- Describe the new feature or modification to an existing feature clearly and consciously. -->
## Motivation
<!-- Explain the reason for adding or modifying this feature. -->
## Key changes
<!-- Provide a technically detailed description of the key changes made. -->
- Frontend
- Backend
## Test plan
### Suggestions
<!-- Provide any suggestions or recommendations for improvements in the testing plan. -->
### Underlying risk
<!-- Identify any potential risks or issues that may arise from the new feature or modification. -->
## Showcase
<!-- Including any screenshots of the new feature or modification. -->

View File

@ -1,24 +1,39 @@
<!-- Note --> <!--
<!-- This is a template for submitting a new feature. First of all, thank you for your contribution!
Use the bug fix template if you're submitting a bug fix pull request by adding `template=bug_fix.md` to your pull request URL. --> For bug fixes or other non-feature modifications, please base your branch on the main branch.
For new features or API modifications, please make sure your branch is based on the next branch.
Thank you!
-->
# Description ### This is a ...
<!-- Describe the new feature or modification to an existing feature clearly and consciously. --> - [ ] New feature
- [ ] Bug fix
- [ ] Others
# Motivation ### Motivation
<!-- Explain the reason for adding or modifying this feature. --> <!-- Please explain the reason of the changes made in this PR. -->
# Key changes ### Description
<!-- Provide a technically detailed description of the key changes made. --> <!--
- Frontend Please describe the key changes made in this PR clearly and concisely,
- Backend mention any potential risks,
and provide some testing suggestions.
-->
# Test plan ### Showcase
## Suggestions <!-- Including any screenshots of the changes. -->
<!-- Provide any suggestions or recommendations for improvements in the testing plan. -->
## Underlying risk ### Changelog
<!-- Identify any potential risks or issues that may arise from the new feature or modification. -->
# Showcase | Language | Changelog |
<!-- Including any screenshots of the new feature or modification. --> | ---------- | --------- |
| 🇺🇸 English | |
| 🇨🇳 Chinese | |
### Checklists
- [ ] All changes have been self-tested and work as expected
- [ ] Test cases are updated/provided or not needed
- [ ] Doc is updated/provided or not needed
- [ ] Component demo is updated/provided or not needed
- [ ] Changelog is provided or not needed
- [ ] Request a code review if it is necessary

View File

@ -97,14 +97,15 @@ export class ApprovalTriggerNode {
configureFieldsButton: Locator; configureFieldsButton: Locator;
configureActionsButton: Locator; configureActionsButton: Locator;
saveDraftSwitch: Locator; saveDraftSwitch: Locator;
preloadAssociationsDropDown: Locator;
submitButton: Locator; submitButton: Locator;
cancelButton: Locator; cancelButton: Locator;
addNodeButton: Locator; addNodeButton: Locator;
constructor(page: Page, triggerName: string, collectionName: string) { constructor(page: Page, triggerName: string, collectionName: string) {
this.page = page; this.page = page;
this.node = page.getByText('TriggeraConfigure'); this.node = page.getByLabel(`Trigger-${triggerName}`);
this.nodeTitle = page.locator('textarea').filter({ hasText: triggerName }); this.nodeTitle = page.getByLabel(`Trigger-${triggerName}`).getByRole('textbox');
this.nodeConfigure = page.getByRole('button', { name: 'Configure' }); this.nodeConfigure = page.getByLabel(`Trigger-${triggerName}`).getByRole('button', { name: 'Configure' });
this.collectionDropDown = page this.collectionDropDown = page
.getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection')
.locator('.ant-select-selection-search-input'); .locator('.ant-select-selection-search-input');
@ -121,6 +122,7 @@ export class ApprovalTriggerNode {
`schema-initializer-ActionBar-ApprovalApplyAddActionButton-${collectionName}`, `schema-initializer-ActionBar-ApprovalApplyAddActionButton-${collectionName}`,
); );
this.saveDraftSwitch = page.getByRole('menuitem', { name: 'Save draft' }).getByRole('switch'); this.saveDraftSwitch = page.getByRole('menuitem', { name: 'Save draft' }).getByRole('switch');
this.preloadAssociationsDropDown = page.getByTestId('select-field-Preload associations');
this.submitButton = page.getByLabel('action-Action-Submit-workflows'); this.submitButton = page.getByLabel('action-Action-Submit-workflows');
this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); this.cancelButton = page.getByLabel('action-Action-Cancel-workflows');
this.addNodeButton = this.addNodeButton = page.getByLabel('add-button', { exact: true }); this.addNodeButton = this.addNodeButton = page.getByLabel('add-button', { exact: true });
@ -133,10 +135,13 @@ export class ApprovalPassthroughModeNode {
nodeTitle: Locator; nodeTitle: Locator;
nodeConfigure: Locator; nodeConfigure: Locator;
addAssigneesButton: Locator; addAssigneesButton: Locator;
addSelectAssigneesMenu: Locator;
addQueryAssigneesMenu: Locator;
assigneesDropDown: Locator; assigneesDropDown: Locator;
OrRadio: Locator; OrRadio: Locator;
AndRadio: Locator; AndRadio: Locator;
votingRadio: Locator; votingRadio: Locator;
votingThresholdEditBox: Locator;
parallellyRadio: Locator; parallellyRadio: Locator;
sequentiallyRadio: Locator; sequentiallyRadio: Locator;
goToconfigureButton: Locator; goToconfigureButton: Locator;
@ -161,17 +166,22 @@ export class ApprovalPassthroughModeNode {
.getByLabel(`Approval-${nodeName}`, { exact: true }) .getByLabel(`Approval-${nodeName}`, { exact: true })
.getByRole('button', { name: 'Configure' }); .getByRole('button', { name: 'Configure' });
this.addAssigneesButton = page.getByRole('button', { name: 'plus Add assignee' }); this.addAssigneesButton = page.getByRole('button', { name: 'plus Add assignee' });
this.addSelectAssigneesMenu = page.getByRole('button', { name: 'Select assignees' });
this.addQueryAssigneesMenu = page.getByRole('button', { name: 'Query assignees' });
this.assigneesDropDown = page.getByTestId('select-single'); this.assigneesDropDown = page.getByTestId('select-single');
this.OrRadio = page.getByLabel('Or', { exact: true }); this.OrRadio = page.getByLabel('Or', { exact: true });
this.AndRadio = page.getByLabel('And', { exact: true }); this.AndRadio = page.getByLabel('And', { exact: true });
this.votingRadio = page.getByLabel('Voting', { exact: true }); this.votingRadio = page.getByLabel('Voting', { exact: true });
this.votingThresholdEditBox = page
.getByLabel('block-item-NegotiationConfig-workflows-Negotiation mode')
.getByRole('spinbutton');
this.parallellyRadio = page.getByLabel('Parallelly', { exact: true }); this.parallellyRadio = page.getByLabel('Parallelly', { exact: true });
this.sequentiallyRadio = page.getByLabel('Sequentially', { exact: true }); this.sequentiallyRadio = page.getByLabel('Sequentially', { exact: true });
this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' }); this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' });
this.addBlockButton = page.getByLabel('schema-initializer-Grid-ApprovalProcessAddBlockButton-workflows'); this.addBlockButton = page.getByLabel('schema-initializer-Grid-ApprovalProcessAddBlockButton-workflows');
this.addDetailsMenu = page.getByRole('menuitem', { name: 'Details' }); this.addDetailsMenu = page.getByRole('menuitem', { name: 'Details' });
this.detailsConfigureFieldsButton = page.getByLabel( this.detailsConfigureFieldsButton = page.getByLabel(
`schema-initializer-Grid-ReadPrettyFormItemInitializers-${collectionName}`, `schema-initializer-Grid-details:configureFields-${collectionName}`,
); );
this.addActionsMenu = page.getByRole('menuitem', { name: 'Actions' }).getByRole('switch'); this.addActionsMenu = page.getByRole('menuitem', { name: 'Actions' }).getByRole('switch');
this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords'); this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords');
@ -188,6 +198,83 @@ export class ApprovalPassthroughModeNode {
} }
} }
export class ApprovalBranchModeNode {
readonly page: Page;
node: Locator;
nodeTitle: Locator;
nodeConfigure: Locator;
addAssigneesButton: Locator;
addSelectAssigneesMenu: Locator;
addQueryAssigneesMenu: Locator;
assigneesDropDown: Locator;
OrRadio: Locator;
AndRadio: Locator;
votingRadio: Locator;
votingThresholdEditBox: Locator;
parallellyRadio: Locator;
sequentiallyRadio: Locator;
goToconfigureButton: Locator;
addBlockButton: Locator;
addDetailsMenu: Locator;
detailsConfigureFieldsButton: Locator;
addActionsMenu: Locator;
actionsConfigureFieldsButton: Locator;
actionsConfigureActionsButton: Locator;
addApproveButton: Locator;
addRejectButton: Locator;
addReturnButton: Locator;
addNodeResult: Locator;
submitButton: Locator;
cancelButton: Locator;
addNodeButton: Locator;
addReturnBranchNodeButton: Locator;
addRejectBranchNodeButton: Locator;
addApproveBranchNodeButton: Locator;
endOnRejectCheckbox: Locator;
constructor(page: Page, nodeName: string, collectionName: string) {
this.page = page;
this.node = page.getByLabel(`Approval-${nodeName}`, { exact: true });
this.nodeTitle = page.getByLabel(`Approval-${nodeName}`, { exact: true }).getByRole('textbox');
this.nodeConfigure = page
.getByLabel(`Approval-${nodeName}`, { exact: true })
.getByRole('button', { name: 'Configure' });
this.addAssigneesButton = page.getByRole('button', { name: 'plus Add assignee' });
this.addSelectAssigneesMenu = page.getByRole('button', { name: 'Select assignees' });
this.addQueryAssigneesMenu = page.getByRole('button', { name: 'Query assignees' });
this.assigneesDropDown = page.getByTestId('select-single');
this.OrRadio = page.getByLabel('Or', { exact: true });
this.AndRadio = page.getByLabel('And', { exact: true });
this.votingRadio = page.getByLabel('Voting', { exact: true });
this.votingThresholdEditBox = page
.getByLabel('block-item-NegotiationConfig-workflows-Negotiation mode')
.getByRole('spinbutton');
this.parallellyRadio = page.getByLabel('Parallelly', { exact: true });
this.sequentiallyRadio = page.getByLabel('Sequentially', { exact: true });
this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' });
this.addBlockButton = page.getByLabel('schema-initializer-Grid-ApprovalProcessAddBlockButton-workflows');
this.addDetailsMenu = page.getByRole('menuitem', { name: 'Details' });
this.detailsConfigureFieldsButton = page.getByLabel(
`schema-initializer-Grid-details:configureFields-${collectionName}`,
);
this.addActionsMenu = page.getByRole('menuitem', { name: 'Actions' }).getByRole('switch');
this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords');
this.actionsConfigureActionsButton = page.getByLabel(
'schema-initializer-ActionBar-ApprovalProcessAddActionButton-approvalRecords',
);
this.addApproveButton = page.getByRole('menuitem', { name: 'Approve' }).getByRole('switch');
this.addRejectButton = page.getByRole('menuitem', { name: 'Reject' }).getByRole('switch');
this.addReturnButton = page.getByRole('menuitem', { name: 'Return' }).getByRole('switch');
this.addNodeResult = page.getByRole('menuitem', { name: 'Node result right' });
this.submitButton = page.getByLabel('action-Action-Submit-workflows');
this.cancelButton = page.getByLabel('action-Action-Cancel-workflows');
this.addNodeButton = page.getByLabel(`add-button-calculation-${nodeName}`, { exact: true });
this.addReturnBranchNodeButton = page.getByLabel(`add-button-approval-${nodeName}-1`);
this.addApproveBranchNodeButton = page.getByLabel(`add-button-approval-${nodeName}-2`);
this.addRejectBranchNodeButton = page.getByLabel(`add-button-approval-${nodeName}--1`);
this.endOnRejectCheckbox = page.getByLabel('End the workflow after');
}
}
export class ScheduleTriggerNode { export class ScheduleTriggerNode {
readonly page: Page; readonly page: Page;
node: Locator; node: Locator;
@ -656,4 +743,5 @@ export default module.exports = {
ConditionBranchNode, ConditionBranchNode,
SQLNode, SQLNode,
ParallelBranchNode, ParallelBranchNode,
ApprovalBranchModeNode,
}; };

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { request, Page } from '@nocobase/test/e2e'; import { request, Browser } from '@nocobase/test/e2e';
const PORT = process.env.APP_PORT || 20000; const PORT = process.env.APP_PORT || 20000;
const APP_BASE_URL = process.env.APP_BASE_URL || `http://localhost:${PORT}`; const APP_BASE_URL = process.env.APP_BASE_URL || `http://localhost:${PORT}`;
@ -881,6 +881,68 @@ export const apiApplyApprovalEvent = async (data: any) => {
return (await result.json()).data; return (await result.json()).data;
}; };
// 新增字段
export const apiCreateField = async (collectionName: string, data: any) => {
const api = await request.newContext({
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
});
const state = await api.storageState();
const headers = getHeaders(state);
/*
{
"sourceKey": "id",
"foreignKey": "orgid",
"onDelete": "SET NULL",
"name": "dept",
"type": "hasMany",
"uiSchema": {
"x-component": "AssociationField",
"x-component-props": {
"multiple": true
},
"title": "dept"
},
"interface": "o2m",
"target": "tt_mnt_dept",
"targetKey": "id"
}
*/
const result = await api.post(`/api/collections/${collectionName}/fields:create`, {
headers,
data,
});
if (!result.ok()) {
throw new Error(await result.text());
}
return (await result.json()).data;
};
/*
{
"data": {
"key": "np4llsa0fsx",
"name": "dept1",
"type": "hasMany",
"interface": "o2m",
"collectionName": "tt_mnt_org",
"description": null,
"parentKey": null,
"reverseKey": null,
"sourceKey": "id",
"foreignKey": "orgid",
"onDelete": "SET NULL",
"uiSchema": {
"x-component": "AssociationField",
"x-component-props": {
"multiple": true
},
"title": "dept1"
},
"target": "tt_mnt_dept",
"targetKey": "id"
}
}
*/
const getStorageItem = (key: string, storageState: any) => { const getStorageItem = (key: string, storageState: any) => {
return storageState.origins return storageState.origins
.find((item) => item.origin === APP_BASE_URL) .find((item) => item.origin === APP_BASE_URL)
@ -927,13 +989,15 @@ function getHeaders(storageState: any) {
} }
// 用户登录新会话 // 用户登录新会话
export const userLogin = async (page: Page, approvalUserEmail: string, approvalUser: string) => { export const userLogin = async (browser: Browser, approvalUserEmail: string, approvalUser: string) => {
await page.goto(`${process.env.APP_BASE_URL}/signin`); const context = await browser.newContext();
const page = await context.newPage();
await page.goto('signin');
await page.getByPlaceholder('Email').fill(approvalUserEmail); await page.getByPlaceholder('Email').fill(approvalUserEmail);
await page.getByPlaceholder('Password').fill(approvalUser); await page.getByPlaceholder('Password').fill(approvalUser);
await page.getByRole('button', { name: 'Sign in' }).click(); await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
return page; return context;
}; };
export default module.exports = { export default module.exports = {
@ -956,4 +1020,5 @@ export default module.exports = {
apiCreateRecordTriggerActionEvent, apiCreateRecordTriggerActionEvent,
apiApplyApprovalEvent, apiApplyApprovalEvent,
userLogin, userLogin,
apiCreateField,
}; };