Merge branch 'main' into develop

This commit is contained in:
xilesun 2024-10-15 09:59:52 +08:00
commit b9d5ad5645
52 changed files with 1085 additions and 240 deletions

View File

@ -1,4 +1,4 @@
name: auto-merge
name: Auto merge main -> next
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -13,17 +13,29 @@ jobs:
push-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.NOCOBASE_APP_ID }}
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }}
skip-token-revoke: true
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
- name: Checkout
uses: actions/checkout@v4
with:
repository: nocobase/nocobase
ssh-key: ${{ secrets.NOCOBASE_DEPLOY_KEY }}
token: ${{ steps.app-token.outputs.token }}
persist-credentials: true
fetch-depth: 0
- name: main -> next(nocobase)
run: |
git config --global user.email "actions@github.com"
git config --global user.name "GitHub Actions Bot"
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>'
git checkout main
git pull origin main
git checkout next
@ -33,7 +45,7 @@ jobs:
uses: ad-m/github-push-action@master
with:
branch: next
ssh: true
github_token: ${{ steps.app-token.outputs.token }}
repository: nocobase/nocobase
tags: true
atomic: true

View File

@ -1,4 +1,4 @@
name: Build Docker Image
name: Build docker image
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: Build Pro Image
name: Build pro image
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -70,7 +70,7 @@ jobs:
- name: Run script
shell: bash
run: |
node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }}
node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }} --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }}
env:
PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -1,4 +1,4 @@
name: deploy client docs
name: Deploy client docs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -25,30 +25,30 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'
- run: yarn install
- name: Build zh-CN
run: yarn doc build core/client --lang=zh-CN
- name: Build en-US
run: yarn doc build core/client --lang=en-US
- name: Set tags
id: set-tags
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "::set-output name=tags::${{ github.ref_name }}"
else
echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}"
fi
- name: copy files via ssh - ${{ steps.set-tags.outputs.tags }}
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.CN_CLIENT_HOST }}
username: ${{ secrets.CN_CLIENT_USERNAME }}
key: ${{ secrets.CN_CLIENT_KEY }}
port: ${{ secrets.CN_CLIENT_PORT }}
source: "packages/core/client/dist/*"
target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }}
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'
- run: yarn install
- name: Build zh-CN
run: yarn doc build core/client --lang=zh-CN
- name: Build en-US
run: yarn doc build core/client --lang=en-US
- name: Set tags
id: set-tags
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "::set-output name=tags::${{ github.ref_name }}"
else
echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}"
fi
- name: copy files via ssh - ${{ steps.set-tags.outputs.tags }}
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.CN_CLIENT_HOST }}
username: ${{ secrets.CN_CLIENT_USERNAME }}
key: ${{ secrets.CN_CLIENT_KEY }}
port: ${{ secrets.CN_CLIENT_PORT }}
source: 'packages/core/client/dist/*'
target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }}

View File

@ -1,39 +0,0 @@
name: Get nocobase app github token
on:
workflow_call:
outputs:
token:
value: ${{ jobs.get-app-token.outputs.token }}
user-id:
value: ${{ jobs.get-app-token.outputs.user-id }}
app-slug:
value: ${{ jobs.get-app-token.outputs.app-slug }}
jobs:
get-app-token:
runs-on: ubuntu-latest
outputs:
token: ${{ steps.encrypt-token.outputs.token }}
app-slug: ${{ steps.app-token.outputs.app-slug }}
user-id: ${{ steps.get-user-id.outputs.user-id }}
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.NOCOBASE_APP_ID }}
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }}
skip-token-revoke: true
- name: Encrypt token
id: encrypt-token
shell: bash
run: |
APP_TOKEN=${{ steps.app-token.outputs.token }};
ENCRYPTED_SECRET=$(echo -n "$APP_TOKEN" | openssl enc -aes-256-cbc -pbkdf2 -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}" | base64 -w 0);
echo "token=$ENCRYPTED_SECRET" >> $GITHUB_OUTPUT
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -1,4 +1,4 @@
name: manual-build-pr-docker-image
name: Manual build pr docker image
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: manual-build-pro-image
name: Manual build pro image
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: Build Pro Plugin Docker Image
name: Build pro plugin docker image
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: manual-e2e
name: Manual e2e
on:
workflow_dispatch:

View File

@ -1,4 +1,4 @@
name: manual-release
name: Manual release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: NocoBase Backend Test
name: NocoBase backend test
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@ -1,4 +1,4 @@
name: NocoBase FrontEnd Test
name: NocoBase frontEnd test
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -33,7 +33,7 @@ jobs:
frontend-test:
strategy:
matrix:
node_version: [ '18' ]
node_version: ['18']
runs-on: ubuntu-latest
container: node:${{ matrix.node_version }}
steps:

View File

@ -1,4 +1,4 @@
name: Release Next
name: Release next
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -148,7 +148,7 @@ jobs:
shell: bash
run: |
git fetch
node scripts/release/changelogAndRelease.js --ver alpha
node scripts/release/changelogAndRelease.js --ver alpha --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }}
env:
PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.3.32-beta](https://github.com/nocobase/nocobase/compare/v1.3.31-beta...v1.3.32-beta) - 2024-10-13
### 🐛 Bug Fixes
- **[client]** required relational field still triggers validation error after selecting a value with a variable in data scope ([#5399](https://github.com/nocobase/nocobase/pull/5399)) by @katherinehhh
## [v1.3.31-beta](https://github.com/nocobase/nocobase/compare/v1.3.30-beta...v1.3.31-beta) - 2024-10-11
### 🐛 Bug Fixes

View File

@ -5,6 +5,12 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
## [v1.3.32-beta](https://github.com/nocobase/nocobase/compare/v1.3.31-beta...v1.3.32-beta) - 2024-10-13
### 🐛 修复
- **[client]** 关系字段设置必填,数据范围中设置变量后,选中值却报字段必填不通过 ([#5399](https://github.com/nocobase/nocobase/pull/5399)) by @katherinehhh
## [v1.3.31-beta](https://github.com/nocobase/nocobase/compare/v1.3.30-beta...v1.3.31-beta) - 2024-10-11
### 🐛 修复

View File

@ -100,7 +100,7 @@ export class CacheManager {
async createCache(options: { name: string; prefix?: string; store?: string; [key: string]: any }) {
const { name, prefix, store = this.defaultStore, ...config } = options;
if (!lodash.isEmpty(config)) {
if (!lodash.isEmpty(config) || store === 'memory') {
const newStore = await this.createStore({ name, storeType: store, ...config });
return this.newCache({ name, prefix, store: newStore });
}

View File

@ -267,7 +267,7 @@ describe('remotePlugins', () => {
expect(requirejs.requirejs.config).toBeCalledTimes(0);
});
it('If there is devDynamicImport and devDynamicImport returns partial, remote API will be requested', async () => {
it.skip('If there is devDynamicImport and devDynamicImport returns partial, remote API will be requested', async () => {
const remoteFn = vi.fn();
const mockPluginsModules = (pluginData, resolve) => {
remoteFn();

View File

@ -7,10 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import type { DevDynamicImport } from '../Application';
import type { Plugin } from '../Plugin';
import type { PluginData } from '../PluginManager';
import type { RequireJS } from './requirejs';
import type { DevDynamicImport } from '../Application';
/**
* @internal
@ -131,7 +131,10 @@ export async function getPlugins(options: GetPluginsOption): Promise<Array<[stri
return res;
}
const remotePluginList = await getRemotePlugins(requirejs, remotePlugins);
res.push(...remotePluginList);
if (res.length === 0) {
const remotePluginList = await getRemotePlugins(requirejs, remotePlugins);
res.push(...remotePluginList);
}
return res;
}

View File

@ -1509,7 +1509,8 @@ export const useAssociationNames = (dataSource?: string) => {
['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type);
// 根据联动规则中条件的字段获取一些 appends
if (s['x-linkage-rules']) {
// 需要排除掉子表格和子表单中的联动规则
if (s['x-linkage-rules'] && !isSubMode(s)) {
const collectAppends = (obj) => {
const type = Object.keys(obj)[0] || '$and';
const list = obj[type];

View File

@ -8,7 +8,11 @@
*/
import { expect, test } from '@nocobase/test/e2e';
import { shouldRefreshDataWhenSubpageIsClosedByPageMenu, submitInReferenceTemplateBlock } from './templates';
import {
shouldRefreshDataWhenSubpageIsClosedByPageMenu,
submitInReferenceTemplateBlock,
createFormSubmit,
} from './templates';
test.describe('Submit: should refresh data after submit', () => {
test('submit in reference template block', async ({ page, mockPage, clearBlockTemplates, mockRecord }) => {
@ -108,3 +112,34 @@ test.describe('Submit: should refresh data after submit', () => {
await page.getByRole('button', { name: '1234567890', exact: true }).click();
});
});
test.describe('Submit: After successful submission', () => {
test('return to the previous popup or page as default value', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(createFormSubmit).waitForInit();
await nocoPage.goto();
await page.getByLabel('action-Action-Add new-create-').click();
await page.getByLabel('action-Action-Submit-submit-').click();
await expect(page.getByTestId('drawer-Action.Container-users-Add record')).not.toBeVisible();
});
test('return to the previous popup or page change to stay on the current popup or page', async ({
page,
mockPage,
mockRecord,
}) => {
const nocoPage = await mockPage(createFormSubmit).waitForInit();
await nocoPage.goto();
await page.getByLabel('action-Action-Add new-create-').click();
await page.getByLabel('action-Action-Submit-submit-').hover();
await page.getByLabel('designer-schema-settings-Action-actionSettings:createSubmit-users').hover();
await page.getByText('After successful submission').click();
await page.getByLabel('Stay on the current popup or').check();
await page.getByRole('button', { name: 'OK' }).click();
await page.getByLabel('action-Action-Submit-submit-').click();
await expect(page.getByTestId('drawer-Action.Container-users-Add record')).toBeVisible();
});
});

View File

@ -1602,3 +1602,360 @@ export const shouldRefreshDataWhenSubpageIsClosedByPageMenu = {
'x-index': 1,
},
};
export const createFormSubmit = {
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
'x-app-version': '1.4.0-alpha',
properties: {
c9ar217262w: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-app-version': '1.4.0-alpha',
properties: {
'943651fwz8t': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.4.0-alpha',
properties: {
g9fq98c2s4n: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.4.0-alpha',
properties: {
'4d3afoagc5g': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '1.4.0-alpha',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '1.4.0-alpha',
properties: {
afp94e3l423: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-action': 'create',
'x-acl-action': 'create',
title: "{{t('Add new')}}",
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:addNew',
'x-component': 'Action',
'x-decorator': 'ACLActionProvider',
'x-component-props': {
openMode: 'drawer',
type: 'primary',
component: 'CreateRecordAction',
icon: 'PlusOutlined',
},
'x-action-context': {
dataSource: 'main',
collection: 'users',
},
'x-align': 'right',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-app-version': '1.4.0-alpha',
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Add record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
'x-app-version': '1.4.0-alpha',
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'popup:addTab',
'x-initializer-props': {
gridInitializer: 'popup:addNew:addBlock',
},
'x-app-version': '1.4.0-alpha',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Add new")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
'x-app-version': '1.4.0-alpha',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:addNew:addBlock',
'x-app-version': '1.4.0-alpha',
properties: {
umjudxvb3ri: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.4.0-alpha',
properties: {
to6yrk48a1e: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.4.0-alpha',
properties: {
zfuiyepfpo8: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-acl-action': 'users:create',
'x-decorator': 'FormBlockProvider',
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
'x-decorator-props': {
dataSource: 'main',
collection: 'users',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:createForm',
'x-component': 'CardItem',
'x-app-version': '1.4.0-alpha',
properties: {
'67zq3yb6rg0': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useCreateFormBlockProps',
'x-app-version': '1.4.0-alpha',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'form:configureFields',
'x-app-version': '1.4.0-alpha',
'x-uid': 'tyztr48d01m',
'x-async': false,
'x-index': 1,
},
'1cslc0bzwan': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'createForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
},
'x-app-version': '1.4.0-alpha',
properties: {
rjfxzva092d: {
_isJSONSchemaObject: true,
version: '2.0',
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-use-component-props': 'useCreateActionProps',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:createSubmit',
'x-component-props': {
type: 'primary',
htmlType: 'submit',
},
'x-action-settings': {
triggerWorkflows: [],
},
type: 'void',
'x-app-version': '1.4.0-alpha',
'x-uid': 'jpbq9zyexb9',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'xsv37mjii6o',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '2j67lyyth1f',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'zcli14m52d6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '63hxohd7td3',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'm6uu6mtlopn',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'x5nqkk6whbz',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kaxuorp0kwz',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qka0657zo5s',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '857bxboz38t',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qxh2sf5itxw',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3nsvi44j20t',
'x-async': false,
'x-index': 1,
},
bk1pbg52rcs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '1.4.0-alpha',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
'x-app-version': '1.4.0-alpha',
properties: {
'9tql6edotky': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '1.4.0-alpha',
'x-uid': 'zrlg6n04kd0',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'uv7q2tnh9mc',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'g76idw88ik8',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'h0mnhu6ihsd',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3xdo6vt2c07',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'et8gznfo7r7',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '18w03j8qcv6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qvl1ar53zpz',
'x-async': true,
'x-index': 1,
},
};

View File

@ -25,7 +25,7 @@ export const UpdateRecordActionInitializer = (props) => {
'x-action-settings': {
assignedValues: {},
onSuccess: {
manualClose: true,
manualClose: false,
redirecting: false,
successMessage: '{{t("Updated successfully")}}',
},

View File

@ -76,6 +76,10 @@ test.describe('set default value', () => {
await expect(
page.getByLabel('block-item-CollectionField-general-form-general.longText-longText').getByRole('textbox'),
).toHaveValue('new value');
// 再次打开设置默认值的弹窗,变量输入框应该显示设置的变量值
await openDialog('longText');
await expect(page.getByLabel('block-item-VariableInput-')).toHaveText(/Current form \/ singleLineText/);
});
test('subform: basic fields', async ({ page, mockPage }) => {

View File

@ -843,7 +843,6 @@ test.describe('actions schema settings', () => {
const expectNewValue = async (value: string) => {
await page.getByLabel('action-Action.Link-Update record-customize:update-users2-table-0').click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await page.getByLabel('action-Action-Refresh-refresh').click();
await expect(page.getByLabel('block-item-CardItem-users2-').getByText(value)).toBeVisible();
};

View File

@ -5124,3 +5124,236 @@ export const zIndexOfSubpage = {
'x-index': 1,
},
};
export const zIndexEditProfile = {
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
'x-app-version': '1.3.32-beta',
properties: {
'17v3l3wra1q': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-app-version': '1.3.32-beta',
properties: {
tpentznu41j: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.3.32-beta',
properties: {
'16wj734kw8c': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.3.32-beta',
properties: {
gh2evps4nft: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '1.3.32-beta',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '1.3.32-beta',
'x-uid': 'jlclfunyxlo',
'x-async': false,
'x-index': 1,
},
juqs9wupfcc: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '1.3.32-beta',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
'x-app-version': '1.3.32-beta',
properties: {
onge8etkwkq: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '1.3.32-beta',
properties: {
'44abnpgy9hl': {
'x-uid': '2m4j1wrmt2k',
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: 'open subpage',
'x-action': 'view',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:view',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'page',
iconColor: '#1677FF',
danger: false,
},
'x-action-context': {
dataSource: 'main',
collection: 'users',
},
'x-decorator': 'ACLActionProvider',
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'popup:addTab',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
'x-uid': 'pncs8uoz02h',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'i1n4wf3wqt8',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '537y7twqq1x',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'j652bs2ocx7',
'x-async': false,
'x-index': 1,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'lmmfco4ti1k',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qxsdgdxd7zd',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'rxecd82tr2t',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '8mdf3toqg65',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'x2e6vc3l16a',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'eqlzylp6c53',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '2e8trd4olie',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ay7xv8zc868',
'x-async': true,
'x-index': 1,
},
};

View File

@ -8,7 +8,7 @@
*/
import { expect, test } from '@nocobase/test/e2e';
import { T2797, T2838, zIndexOfSubpage } from './templatesOfBug';
import { T2797, T2838, zIndexEditProfile, zIndexOfSubpage } from './templatesOfBug';
test.describe('z-index of dialog', () => {
test('edit block title', async ({ page, mockPage }) => {
@ -73,4 +73,32 @@ test.describe('z-index of dialog', () => {
.click();
await expect(page.getByText('Add condition', { exact: true })).toBeVisible();
});
test('edit profile', async ({ page, mockPage }) => {
await mockPage(zIndexEditProfile).goto();
// open subpage, and then open the Edit Profile drawer
await page.getByLabel('action-Action.Link-open').click();
await page.getByTestId('user-center-button').hover();
await page.getByRole('menuitem', { name: 'Edit profile' }).click();
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).toBeVisible();
// click the Cancel button to close the drawer
await page.getByLabel('action-Action-Cancel').click();
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).not.toBeVisible();
});
test('change password', async ({ page, mockPage }) => {
await mockPage(zIndexEditProfile).goto();
// open subpage, and then open the Change password drawer
await page.getByLabel('action-Action.Link-open').click();
await page.getByTestId('user-center-button').hover();
await page.getByRole('menuitem', { name: 'Change password' }).click();
await expect(page.getByTestId('drawer-Action.Drawer-Change password')).toBeVisible();
// click the Cancel button to close the drawer
await page.getByLabel('action-Action-Cancel').click();
await expect(page.getByTestId('drawer-Action.Drawer-Change password')).not.toBeVisible();
});
});

View File

@ -37,7 +37,7 @@ const openSizeWidthMap = new Map<OpenSize, string>([
]);
export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
(props) => {
const { footerNodeName = 'Action.Drawer.Footer', ...others } = props;
const { footerNodeName = 'Action.Drawer.Footer', zIndex: _zIndex, ...others } = props;
const { visible, setVisible, openSize = 'middle', drawerProps, modalProps } = useActionContext();
const schema = useFieldSchema();
const field = useField();
@ -63,7 +63,7 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
useSetAriaLabelForDrawer(visible);
}
const zIndex = parentZIndex + (props.level || 0);
const zIndex = _zIndex || parentZIndex + (props.level || 0);
return (
<zIndexContext.Provider value={zIndex}>

View File

@ -39,7 +39,7 @@ const openSizeWidthMap = new Map<OpenSize, string>([
export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = observer(
(props) => {
const { footerNodeName = 'Action.Modal.Footer', width, ...others } = props;
const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, ...others } = props;
const { visible, setVisible, openSize = 'middle', modalProps } = useActionContext();
const actualWidth = width ?? openSizeWidthMap.get(openSize);
const schema = useFieldSchema();
@ -71,7 +71,7 @@ export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = obse
useSetAriaLabelForModal(visible);
}
const zIndex = parentZIndex + (props.level || 0);
const zIndex = _zIndex || parentZIndex + (props.level || 0);
return (
<zIndexContext.Provider value={zIndex}>

View File

@ -47,6 +47,13 @@ const useStyles = genStyleHook('nb-action', (token) => {
},
},
},
'.ant-btn-icon': {
marginInlineEnd: '0px !important',
},
'.nb-action-title': {
marginInlineStart: '8px',
},
},
};
});

View File

@ -377,7 +377,6 @@ function RenderButton({
if (!designable && (field?.data?.hidden || !aclCtx)) {
return null;
}
return (
<SortableItem
role="button"
@ -393,7 +392,7 @@ function RenderButton({
className={classnames(componentCls, hashId, className, 'nb-action')}
type={type === 'danger' ? undefined : type}
>
{actionTitle}
{actionTitle && <span className={icon ? 'nb-action-title' : null}>{actionTitle}</span>}
<Designer {...designerProps} />
</SortableItem>
);

View File

@ -82,7 +82,8 @@ const InternalAssociationSelect = observer(
if (
linkageFields.includes(fieldPath?.props?.name) &&
field.value &&
fieldPath?.indexes?.[0] === field?.indexes?.[0]
fieldPath?.indexes?.[0] === field?.indexes?.[0] &&
fieldPath?.props?.name !== field.props.name
) {
field.setValue(undefined);
setInnerValue(undefined);

View File

@ -255,11 +255,11 @@ export const getDateRanges = (props?: {
};
function withParams(value: any[], params: { fieldOperator?: string; isParsingVariable?: boolean }) {
if (params?.fieldOperator === '$dateBetween' || !params?.isParsingVariable) {
return value;
if (params?.isParsingVariable && params?.fieldOperator && params.fieldOperator !== '$dateBetween') {
return value[0];
}
return value[0];
return value;
}
export function inferPickerType(dateString: string): 'year' | 'month' | 'quarter' | 'date' {

View File

@ -325,7 +325,7 @@ const usePaginationProps = (pagination1, pagination2) => {
},
};
}
}, [pagination, t, showTotal]);
}, [pagination, t, showTotal, field.value?.length]);
if (pagination2 === false) {
return false;
@ -333,7 +333,6 @@ const usePaginationProps = (pagination1, pagination2) => {
if (!pagination2 && pagination1 === false) {
return false;
}
return result.total <= result.pageSize ? false : result;
};

View File

@ -93,11 +93,7 @@ const testOpts = (ext: RegExp, options: { exclude?: string[]; include?: string[]
};
export function getThumbnailPlaceholderURL(file, options: any = {}) {
if (file.url) {
return file.url;
}
for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) {
// console.log(UPLOAD_PLACEHOLDER[i].ext, testOpts(UPLOAD_PLACEHOLDER[i].ext, options));
if (UPLOAD_PLACEHOLDER[i].ext.test(file.extname || file.filename || file.url || file.name)) {
if (testOpts(UPLOAD_PLACEHOLDER[i].ext, options)) {
return UPLOAD_PLACEHOLDER[i].icon || UNKNOWN_FILE_ICON;

View File

@ -43,6 +43,7 @@ import React, {
} from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { VariablesContext } from '../';
import { APIClientProvider } from '../api-client/APIClientProvider';
import { useAPIClient } from '../api-client/hooks/useAPIClient';
import { ApplicationContext, LocationSearchContext, useApp, useLocationSearch } from '../application';
@ -103,7 +104,6 @@ import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent
import { FormLinkageRules } from './LinkageRules';
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './LinkageRules/type';
import { VariablesContext } from '../';
export interface SchemaSettingsProps {
title?: any;
dn?: Designable;
@ -747,6 +747,8 @@ export interface SchemaSettingsModalItemProps {
hide?: boolean;
/** 上下文中不需要当前记录 */
noRecord?: boolean;
/** 自定义 Modal 上下文 */
ModalContextProvider?: React.FC;
}
export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props) => {
const {
@ -760,6 +762,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
initialValues,
width = 'fit-content',
noRecord = false,
ModalContextProvider = (props) => <>{props.children}</>,
...others
} = props;
const options = useContext(SchemaOptionsContext);
@ -778,7 +781,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
const blockOptions = useBlockContext();
const { getOperators } = useOperators();
const locationSearch = useLocationSearch();
const variableOptions = useContext(VariablesContext);
const variableOptions = useVariables();
// 解决变量`当前对象`值在弹窗中丢失的问题
const { formValue: subFormValue, collection: subFormCollection } = useSubFormValue();
@ -802,72 +805,74 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
{ title: schema.title || title, width },
() => {
return (
<CollectOperators defaultOperators={getOperators()}>
<VariablesContext.Provider value={variableOptions}>
<BlockContext.Provider value={blockOptions}>
<VariablePopupRecordProvider
recordData={popupRecordVariable?.value}
collection={popupRecordVariable?.collection}
parent={{
recordData: parentPopupRecordVariable?.value,
collection: parentPopupRecordVariable?.collection,
}}
>
<CollectionRecordProvider record={noRecord ? null : record}>
<FormBlockContext.Provider value={formCtx}>
<SubFormProvider value={{ value: subFormValue, collection: subFormCollection }}>
<FormActiveFieldsProvider
name="form"
getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}
>
<LocationSearchContext.Provider value={locationSearch}>
<BlockRequestContext_deprecated.Provider value={ctx}>
<DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}>
<AssociationOrCollectionProvider
allowNull
collection={collection.name}
association={association}
>
<SchemaComponentOptions scope={options.scope} components={options.components}>
<FormLayout
layout={'vertical'}
className={css`
// screen > 576px
@media (min-width: 576px) {
min-width: 520px;
}
<ModalContextProvider>
<CollectOperators defaultOperators={getOperators()}>
<VariablesContext.Provider value={variableOptions}>
<BlockContext.Provider value={blockOptions}>
<VariablePopupRecordProvider
recordData={popupRecordVariable?.value}
collection={popupRecordVariable?.collection}
parent={{
recordData: parentPopupRecordVariable?.value,
collection: parentPopupRecordVariable?.collection,
}}
>
<CollectionRecordProvider record={noRecord ? null : record}>
<FormBlockContext.Provider value={formCtx}>
<SubFormProvider value={{ value: subFormValue, collection: subFormCollection }}>
<FormActiveFieldsProvider
name="form"
getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}
>
<LocationSearchContext.Provider value={locationSearch}>
<BlockRequestContext_deprecated.Provider value={ctx}>
<DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}>
<AssociationOrCollectionProvider
allowNull
collection={collection.name}
association={association}
>
<SchemaComponentOptions scope={options.scope} components={options.components}>
<FormLayout
layout={'vertical'}
className={css`
// screen > 576px
@media (min-width: 576px) {
min-width: 520px;
}
// screen <= 576px
@media (max-width: 576px) {
min-width: 320px;
}
`}
>
<ApplicationContext.Provider value={app}>
<APIClientProvider apiClient={apiClient}>
<ConfigProvider locale={locale}>
<SchemaComponent
components={components}
scope={scope}
schema={schema}
/>
</ConfigProvider>
</APIClientProvider>
</ApplicationContext.Provider>
</FormLayout>
</SchemaComponentOptions>
</AssociationOrCollectionProvider>
</DataSourceApplicationProvider>
</BlockRequestContext_deprecated.Provider>
</LocationSearchContext.Provider>
</FormActiveFieldsProvider>
</SubFormProvider>
</FormBlockContext.Provider>
</CollectionRecordProvider>
</VariablePopupRecordProvider>
</BlockContext.Provider>
</VariablesContext.Provider>
</CollectOperators>
// screen <= 576px
@media (max-width: 576px) {
min-width: 320px;
}
`}
>
<ApplicationContext.Provider value={app}>
<APIClientProvider apiClient={apiClient}>
<ConfigProvider locale={locale}>
<SchemaComponent
components={components}
scope={scope}
schema={schema}
/>
</ConfigProvider>
</APIClientProvider>
</ApplicationContext.Provider>
</FormLayout>
</SchemaComponentOptions>
</AssociationOrCollectionProvider>
</DataSourceApplicationProvider>
</BlockRequestContext_deprecated.Provider>
</LocationSearchContext.Provider>
</FormActiveFieldsProvider>
</SubFormProvider>
</FormBlockContext.Provider>
</CollectionRecordProvider>
</VariablePopupRecordProvider>
</BlockContext.Provider>
</VariablesContext.Provider>
</CollectOperators>
</ModalContextProvider>
);
},
theme,

View File

@ -109,17 +109,15 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
FormLayout,
VariableInput: (inputProps) => {
return (
<FlagProvider isInSubForm={isInSubForm} isInSubTable={isInSubTable} isInSetDefaultValueDialog>
<VariableInput
{...inputProps}
value={inputProps.value || undefined}
hideVariableButton={props?.hideVariableButton}
/>
</FlagProvider>
<VariableInput
{...inputProps}
value={inputProps.value || undefined}
hideVariableButton={props?.hideVariableButton}
/>
);
},
};
}, [isInSubForm, isInSubTable]);
}, []);
const schema = useMemo(() => {
return {
@ -246,6 +244,13 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
width={800}
schema={schema}
onSubmit={handleSubmit}
ModalContextProvider={(props) => {
return (
<FlagProvider isInSubForm={isInSubForm} isInSubTable={isInSubTable} isInSetDefaultValueDialog>
{props.children}
</FlagProvider>
);
}}
/>
);
};

View File

@ -9,7 +9,8 @@
import { Form } from '@formily/core';
import { ISchema, Schema } from '@formily/react';
import { useContext, useMemo } from 'react';
import { useMemo } from 'react';
import { useVariables } from '../../../';
import { CollectionFieldOptions_deprecated } from '../../../collection-manager';
import { useAPITokenVariable } from './useAPITokenVariable';
import { useDatetimeVariable } from './useDateVariable';
@ -22,7 +23,6 @@ import { useCurrentRecordVariable } from './useRecordVariable';
import { useCurrentRoleVariable } from './useRoleVariable';
import { useURLSearchParamsVariable } from './useURLSearchParamsVariable';
import { useCurrentUserVariable } from './useUserVariable';
import { VariablesContext } from '../../../';
interface Props {
/**
@ -59,7 +59,7 @@ export const useVariableOptions = ({
targetFieldSchema,
record,
}: Props) => {
const { filterVariables = () => true } = useContext(VariablesContext) || {};
const { filterVariables = () => true } = useVariables() || {};
const blockParentCollectionName = record?.__parent?.__collectionName;
const { currentUserSettings } = useCurrentUserVariable({
maxDepth: 3,

View File

@ -53,6 +53,9 @@ const schema: ISchema = {
[uid()]: {
'x-decorator': 'Form',
'x-component': 'Action.Drawer',
'x-component-props': {
zIndex: 10000,
},
type: 'void',
title: '{{t("Change password")}}',
properties: {

View File

@ -71,6 +71,9 @@ const schema: ISchema = {
useValues: '{{ useCurrentUserValues }}',
},
'x-component': 'Action.Drawer',
'x-component-props': {
zIndex: 10000,
},
type: 'void',
title: '{{t("Edit profile")}}',
properties: {

View File

@ -49,6 +49,10 @@ const getFieldPath = (variablePath: string, variablesStore: Record<string, Varia
};
};
/**
* @internal
* Note: There can only be one VariablesProvider in the entire context. It cannot be used in plugins.
*/
const VariablesProvider = ({ children, filterVariables }: any) => {
const ctxRef = useRef<Record<string, any>>({});
const api = useAPIClient();

View File

@ -298,7 +298,11 @@ describe('useVariables', () => {
});
await waitFor(async () => {
expect(await result.current.parseVariable('{{ $date.today }}').then(({ value }) => typeof value)).toBe('string');
expect(
await result.current
.parseVariable('{{ $date.today }}', [], { fieldOperator: '$dateOn' })
.then(({ value }) => typeof value),
).toBe('string');
expect(
Array.isArray(
await result.current

View File

@ -138,10 +138,10 @@ export class OptionsParser {
sort = sort.split(',');
}
let defaultSortField = this.model.primaryKeyAttribute;
let defaultSortField: Array<string> | string = this.model.primaryKeyAttribute;
if (Array.isArray(this.collection.filterTargetKey) && this.collection.filterTargetKey.length == 1) {
defaultSortField = this.collection.filterTargetKey[0];
if (Array.isArray(this.collection.filterTargetKey)) {
defaultSortField = this.collection.filterTargetKey;
}
if (!defaultSortField && this.collection.filterTargetKey && !Array.isArray(this.collection.filterTargetKey)) {
@ -149,8 +149,11 @@ export class OptionsParser {
}
if (defaultSortField && !this.options?.group) {
if (!sort.includes(defaultSortField)) {
sort.push(defaultSortField);
defaultSortField = lodash.castArray(defaultSortField);
for (const key of defaultSortField) {
if (!sort.includes(key)) {
sort.push(key);
}
}
}

View File

@ -18,6 +18,7 @@ export interface ResourceStorer {
getResources(lang: string): Promise<{
[ns: string]: Record<string, string>;
}>;
reset?: () => Promise<void>;
}
export class Locale {
@ -41,7 +42,7 @@ export class Locale {
this.app.syncMessageManager.subscribe('localeManager', async (message) => {
switch (message.type) {
case 'reload':
await this.cache.reset();
await this.reset();
return;
}
});
@ -52,14 +53,19 @@ export class Locale {
name: 'locale',
prefix: 'locale',
store: 'memory',
max: 2000
});
await this.get(this.defaultLang);
}
async reset() {
const storers = Array.from(this.resourceStorers.getValues());
const promises = storers.map((storer) => storer.reset());
await Promise.all([this.cache.reset(), ...promises]);
}
async reload() {
await this.cache.reset();
await this.reset();
this.app.syncMessageManager.publish('localeManager', { type: 'reload' });
}

View File

@ -64,7 +64,7 @@ const InternalIcons = () => {
window.removeEventListener('resize', calculateGap);
};
}, [Object.keys(fieldSchema?.properties || {}).length]);
console.log(gap);
return (
<div style={{ marginBottom: designable ? '1rem' : 0 }}>
<DndContext>
@ -91,7 +91,6 @@ const InternalIcons = () => {
text-overflow: ellipsis;
}
.ant-list-item-meta-title button {
font-weight: 700;
font-size: 16px;
overflow: hidden;
text-overflow: ellipsis;

View File

@ -0,0 +1,40 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { expect, test } from '@nocobase/test/e2e';
test.describe('defaultValue', () => {
test('date variables', async ({ page, mockPage }) => {
await mockPage().goto();
// 1. First, create a chart filter block and add a custom date field (date range)
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'line-chart Charts' }).click();
await page.getByLabel('schema-initializer-Grid-charts:addBlock').hover();
await page.getByRole('menuitem', { name: 'Filter' }).click();
await page.getByTestId('configure-fields-button-of-chart-filter-item').hover();
await page.getByRole('menuitem', { name: 'Custom' }).click();
await page.getByLabel('block-item-Input-Field title').getByRole('textbox').fill('date');
await page.getByLabel('block-item-Select-Field').getByTestId('select-single').click();
await page.getByRole('option', { name: 'Date range' }).click();
await page.getByRole('button', { name: 'OK' }).click();
// 2. Set a date variable default value for the custom field, after saving, the date input box should display the default value
await page.getByLabel('block-item-DatePicker.').hover();
await page.getByLabel('designer-schema-settings-DatePicker.RangePicker-ChartFilterItemDesigner').hover();
await page.getByRole('menuitem', { name: 'Set default value' }).click();
await page.getByLabel('variable-button').click();
await page.getByRole('menuitemcheckbox', { name: 'Date variables right' }).click();
await page.getByRole('menuitemcheckbox', { name: 'Last week' }).click();
await page.getByRole('button', { name: 'OK' }).click();
await expect(page.getByPlaceholder('Start date')).toHaveValue(/[0-9]{4}-[0-9]{2}-[0-9]{2}/);
await expect(page.getByPlaceholder('End date')).toHaveValue(/[0-9]{4}-[0-9]{2}-[0-9]{2}/);
});
});

View File

@ -27,9 +27,11 @@ describe('actions', () => {
};
beforeAll(async () => {
process.env.APP_ENV = 'production';
app = await createMockServer({
plugins: ['localization'],
});
await app.emitAsync('afterLoad');
db = app.db;
repo = db.getRepository('localizationTexts');
agent = app.agent();
@ -106,5 +108,28 @@ describe('actions', () => {
expect(res.body.data[0].translation).toBeUndefined();
});
});
it('publish', async () => {
await repo.create({
values: [
{
module: 'test',
text: 'text',
translations: [
{
locale: 'en-US',
translation: 'translation',
},
],
},
],
});
const { resources } = await app.localeManager.get('en-US');
expect(resources.test).toBeUndefined();
await agent.resource('localization').publish();
const { resources: resources2 } = await app.localeManager.get('en-US');
expect(resources2.test).toBeDefined();
expect(resources2.test.text).toBe('translation');
});
});
});

View File

@ -133,6 +133,7 @@ export class PluginLocalizationServer extends Plugin {
this.app.localeManager.registerResourceStorer('plugin-localization', {
getResources: (lang: string) => this.resources.getResources(lang),
reset: () => this.resources.reset(),
});
}

View File

@ -83,7 +83,7 @@ export default class Resources {
await this.cache.set(`texts`, [...existTexts, ...newTexts]);
}
async resetCache(locale: string) {
await this.cache.del(`translations:${locale}`);
async reset() {
await this.cache.reset();
}
}

View File

@ -45,7 +45,7 @@ const PublicFormQRCode = () => {
onOpenChange={handleQRCodeOpen}
content={open ? <QRCode value={link} bordered={false} /> : ' '}
>
{t('QR code', { ns: NAMESPACE })}
<a>{t('QR code', { ns: NAMESPACE })}</a>
</Popover>
);
};
@ -140,25 +140,22 @@ export function AdminPublicFormPage() {
{
key: 'enabled',
label: (
<span
<a
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
onClick={() => handleEditPublicForm({ enabled: !enabled })}
>
<span style={{ marginRight: '10px' }}>{t('Enable form', { ns: NAMESPACE })}</span>
<Switch
size={'small'}
checked={enabled}
onChange={(checked) => handleEditPublicForm({ enabled: checked })}
/>
</span>
<Switch size={'small'} checked={enabled} />
</a>
),
},
{
key: 'password',
label: <span onClick={handleSetPassword}> {t('Set password')}</span>,
label: <a onClick={handleSetPassword}> {t('Set password')}</a>,
},
{
key: 'divider1',
@ -166,7 +163,7 @@ export function AdminPublicFormPage() {
},
{
key: 'copyLink',
label: <span onClick={handleCopyLink}>{t('Copy link')}</span>,
label: <a onClick={handleCopyLink}>{t('Copy link')}</a>,
},
{
key: 'qrcode',

View File

@ -0,0 +1,26 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Migration } from '@nocobase/server';
export default class extends Migration {
on = 'beforeLoad';
appVersion = '<1.5.0-beta';
async up() {
await this.pm.repository.update({
values: {
builtIn: false,
},
filter: {
name: 'backup-restore',
},
});
}
}

View File

@ -3,8 +3,15 @@ const fs = require('fs/promises');
const path = require('path');
const { Command } = require('commander');
const program = new Command();
const axios = require('axios');
program.option('-f, --from [from]').option('-t, --to [to]').option('-v, --ver [ver]', '', 'beta').option('--test');
program
.option('-f, --from [from]')
.option('-t, --to [to]')
.option('-v, --ver [ver]', '', 'beta')
.option('--test')
.option('--cmsURL [url]')
.option('--cmsToken [token]');
program.parse(process.argv);
const header = {
@ -99,7 +106,9 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
// gh pr view 5112 --json author,body,files
let res;
try {
const { stdout } = await execa('gh', ['pr', 'view', number, '--json', 'author,body,files,baseRefName'], { cwd });
const { stdout } = await execa('gh', ['pr', 'view', number, '--json', 'author,body,files,baseRefName,url'], {
cwd,
});
res = stdout;
} catch (error) {
console.error(`Get PR #${number} failed, error: ${error.message}`);
@ -110,7 +119,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
}
return { number };
}
const { author, body, files, baseRefName } = JSON.parse(res);
const { author, body, files, baseRefName, url } = JSON.parse(res);
if (ver === 'alpha' && baseRefName !== 'next') {
return { number };
}
@ -130,6 +139,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
author: author.login,
moduleType: name?.includes('plugin-') ? 'plugin' : 'core',
module: name,
url,
en: {
module: displayName || pkgName,
description,
@ -187,17 +197,7 @@ function arrangeChangelogs(changelogs) {
return result;
}
async function collect() {
let { from, to, ver = 'beta' } = program.opts();
if (!from || !to) {
// git tag -l --sort=version:refname | grep "v*-ver" | tail -2
const tagPattern = `v*-${ver}`;
const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, {
shell: true,
});
[from, to] = tags.split('\n');
}
console.log(`From: ${from}, To: ${to}`);
async function collect(from, to) {
const changelogs = [];
const get = async (changelogs, pkgType, cwd, pkg) => {
const prs = await getPRList(from, to, cwd);
@ -231,11 +231,11 @@ async function collect() {
}
}
}
return { changelogs: arrangeChangelogs(changelogs), from, to };
return { changelogs: arrangeChangelogs(changelogs) };
}
async function generateChangelog() {
const { changelogs, from, to } = await collect();
async function generateChangelog(changelogs) {
const { test } = program.opts();
const prTypeLocale = {
'New feature': {
en: '🎉 New Features',
@ -275,13 +275,13 @@ async function generateChangelog() {
const moduleResults = [];
const lists = [];
for (const changelog of moduleChangelogs) {
const { number, author, pro } = changelog;
const { number, author, pro, url } = changelog;
const { description, docTitle, docLink } = changelog[lang];
if (!description) {
console.warn(`PR #${number} has no ${lang} changelog`);
continue;
}
const pr = pro ? '' : ` ([#${number}](https://github.com/nocobase/nocobase/pull/${number}))`;
const pr = pro && !test ? '' : ` ([#${number}](${url}))`;
const doc = docTitle && docLink ? `${referenceLocale[lang]}[${docTitle}](${docLink})` : '';
lists.push(`${description}${pr} by @${author}\n${doc}`);
}
@ -314,7 +314,7 @@ async function generateChangelog() {
const cn = generate(changelogs, 'cn');
const en = generate(changelogs, 'en');
return { cn, en, from, to };
return { cn, en };
}
async function writeChangelog(cn, en, from, to) {
@ -335,30 +335,107 @@ async function writeChangelog(cn, en, from, to) {
}
async function createRelease(cn, en, to) {
const { stdout } = await execa('gh', ['release', 'list', '--json', 'tagName']);
const releases = JSON.parse(stdout);
const tags = releases.map((release) => release.tagName);
if (tags.includes(to)) {
console.log(`Release ${to} already exists`);
return;
}
let { ver = 'beta' } = program.opts();
// gh release create -t title -n note
if (ver === 'alpha') {
await execa('gh', ['release', 'create', to, '-t', to, '-n', `${en}\n---\n${cn}`, '-p']);
await execa('gh', ['release', 'create', to, '-t', to, '-n', en, '-p']);
return;
}
await execa('gh', ['release', 'create', to, '-t', to, '-n', `${en}\n---\n${cn}`]);
await execa('gh', ['release', 'create', to, '-t', to, '-n', en]);
}
async function getExistsChangelog(from, to) {
const get = async (lang) => {
const file = lang === 'cn' ? 'CHANGELOG.zh-CN.md' : 'CHANGELOG.md';
const oldChangelog = await fs.readFile(path.join(__dirname, `../../${file}`), 'utf8');
if (!oldChangelog.includes(`## [${to}]`)) {
return null;
}
const fromIndex = oldChangelog.indexOf(`## [${from}]`);
const toIndex = oldChangelog.indexOf(`## [${to}]`);
return oldChangelog.slice(toIndex, fromIndex);
};
const cn = await get('cn');
const en = await get('en');
return { cn, en };
}
async function getVersion() {
let { from, to, ver = 'beta' } = program.opts();
if (!from || !to) {
// git tag -l --sort=version:refname | grep "v*-ver" | tail -2
const tagPattern = `v*-${ver}`;
const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, {
shell: true,
});
[from, to] = tags.split('\n');
}
console.log(`From: ${from}, To: ${to}`);
return { from, to };
}
async function postCMS(title, content, contentCN) {
const { cmsToken, cmsURL } = program.opts();
if (!cmsToken || !cmsURL) {
console.error('No cmsToken or cmsURL provided');
return;
}
await axios.request({
method: 'post',
url: `${cmsURL}/api/articles:updateOrCreate`,
headers: {
Authorization: `Bearer ${cmsToken}`,
},
params: {
filterKeys: ['title'],
},
data: {
title,
title_cn: title,
content,
content_cn: contentCN,
tags: [4],
status: 'drafted',
author: 'nocobase [bot]',
},
});
}
async function writeChangelogAndCreateRelease() {
let { ver = 'beta', test } = program.opts();
const { cn, en, from, to } = await generateChangelog();
if (!cn && !en) {
throw new Error('No changelog generated');
const { from, to } = await getVersion();
let { cn, en } = await getExistsChangelog(from, to);
let exists = false;
if (cn || en) {
exists = true;
console.log('Changelog already exists');
} else {
const { changelogs } = await collect(from, to);
const c = await generateChangelog(changelogs);
cn = c.cn;
en = c.en;
if (!cn && !en) {
console.error('No changelog generated');
return;
}
}
console.log(en);
console.log(cn);
if (test) {
console.log(en);
console.log(cn);
return;
}
if (ver === 'beta') {
if (ver === 'beta' && !exists) {
await writeChangelog(cn, en, from, to);
}
await createRelease(cn, en, to);
await postCMS(to, en, cn);
}
writeChangelogAndCreateRelease();