feat(ui-schema): nocobase-admin-menu & nocobase-mobile-container (#3213)

* feat(ui-schema): nocobase-admin-menu & nocobase-mobile-container

* fix: db.sync

* fix: error

* fix: error

* fix: error

* fix: add test case

* fix: migration error

* fix: test error
This commit is contained in:
chenos 2023-12-17 11:16:30 +08:00 committed by GitHub
parent b1610e6994
commit f82b4d8726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 229 additions and 66 deletions

View File

@ -218,11 +218,11 @@ export class Application {
}
loadFailed = true;
this.error = {
...error,
code: 'LOAD_ERROR',
message: error.message,
...error,
};
console.error(error);
console.error(this.error);
}
this.loading = false;
}

View File

@ -19,7 +19,9 @@ export const AppComponent: FC<AppComponentProps> = observer((props) => {
const AppError = app.getComponent('AppError');
if (app.loading) return app.renderComponent('AppSpin', { app });
if (!app.maintained && app.maintaining) return app.renderComponent('AppMaintaining', { app });
if (app.error?.code === 'LOAD_ERROR') return <AppError app={app} error={app.error} />;
if (app.error?.code === 'LOAD_ERROR' || app.error?.code === 'APP_ERROR') {
return <AppError app={app} error={app.error} />;
}
return (
<ErrorBoundary
FallbackComponent={(props) => <AppError app={app} error={app.error} {...props} />}

View File

@ -1,6 +1,3 @@
import { useSystemSettings } from '../system-settings';
export const useAdminSchemaUid = () => {
const ctx = useSystemSettings();
return ctx?.data?.data?.options?.adminSchemaUid;
return 'nocobase-admin-menu';
};

View File

@ -128,7 +128,7 @@ const getProps = (app: Application) => {
};
}
if (app.error.code === 'APP_ERROR') {
if (app.error.code === 'APP_ERROR' || app.error.code === 'LOAD_ERROR') {
return {
status: 'error',
title: 'App error',

View File

@ -1,3 +1,4 @@
import _ from 'lodash';
import { QueryInterface, Sequelize } from 'sequelize';
import Database from './database';
@ -70,7 +71,10 @@ export class Migrations {
callback() {
return async (ctx) => {
return this.items;
return _.sortBy(this.items, (item) => {
const keys = item.name.split('/');
return keys.pop() || item.name;
});
};
}
}

View File

@ -0,0 +1,61 @@
import { MockServer, mockServer } from '@nocobase/test';
import Migration from '../migrations/20231215215247-admin-menu-uid';
// 每个插件的 app 最小化安装的插件都不一样,需要插件根据自己的情况添加必备插件
async function createApp(options: any = {}) {
const app = mockServer({
...options,
plugins: ['client', 'ui-schema-storage', 'system-settings'].concat(options.plugins || []),
});
// 这里可以补充一些需要特殊处理的逻辑,比如导入测试需要的数据表
return app;
}
// 大部分的测试都需要启动应用,所以也可以提供一个通用的启动方法
async function startApp() {
const app = await createApp();
await app.quickstart({
// 运行测试前,清空数据库
clean: true,
});
return app;
}
describe('nocobase-admin-menu', () => {
let app: MockServer;
beforeEach(async () => {
app = await startApp();
});
afterEach(async () => {
// 运行测试后,清空数据库
await app.destroy();
// 只停止不清空数据库
// await app.stop();
});
test('migration', async () => {
const uiSchemas = app.db.getModel('uiSchemas');
const systemSettings = app.db.getRepository('systemSettings');
await uiSchemas.truncate();
await app.db.getModel('uiSchemaTreePath').truncate();
await uiSchemas.create({
'x-uid': 'abc',
name: 'abc',
});
const instance = await systemSettings.findOne();
instance.set('options', {
adminSchemaUid: 'abc',
});
await instance.save();
const migration = new Migration({
db: app.db,
// @ts-ignore
app: app,
});
await migration.up();
const schema = await uiSchemas.findOne();
expect(schema.toJSON()).toMatchObject({ 'x-uid': 'nocobase-admin-menu', name: 'abc' });
});
});

View File

@ -3,6 +3,10 @@ import { Migration } from '@nocobase/server';
export default class extends Migration {
async up() {
const result = await this.app.version.satisfies('<0.14.0-alpha.1');
if (!result) {
return;
}
await this.db.getCollection('systemSettings').sync({
force: false,
alter: {
@ -15,10 +19,10 @@ export default class extends Migration {
return;
}
const uiRoutes = this.db.getRepository('uiRoutes');
const routes = await uiRoutes.find();
if (!uiRoutes) {
return;
}
const routes = await uiRoutes.find();
for (const route of routes) {
if (route.uiSchemaUid && route?.options?.component === 'AdminLayout') {
const options = instance.options || {};

View File

@ -0,0 +1,53 @@
import { Model } from '@nocobase/database';
import { Migration } from '@nocobase/server';
export default class extends Migration {
async up() {
const result = await this.app.version.satisfies('<0.17.0-alpha.8');
if (!result) {
return;
}
const systemSettings = this.db.getRepository('systemSettings');
const instance: Model = await systemSettings.findOne();
if (!instance?.options?.adminSchemaUid) {
return;
}
const UiSchemas = this.db.getModel('uiSchemas');
await this.db.sequelize.transaction(async (transaction) => {
await UiSchemas.update(
{
'x-uid': 'nocobase-admin-menu',
},
{
transaction,
where: {
'x-uid': instance?.options?.adminSchemaUid,
},
},
);
await this.db.getModel('uiSchemaTreePath').update(
{
descendant: 'nocobase-admin-menu',
},
{
transaction,
where: {
descendant: instance?.options?.adminSchemaUid,
},
},
);
await this.db.getModel('uiSchemaTreePath').update(
{
ancestor: 'nocobase-admin-menu',
},
{
transaction,
where: {
ancestor: instance?.options?.adminSchemaUid,
},
},
);
});
console.log(instance?.options?.adminSchemaUid);
}
}

View File

@ -32,27 +32,24 @@ export class ClientPlugin extends Plugin {
//
}
});
}
this.db.on('systemSettings.beforeCreate', async (instance, { transaction }) => {
const uiSchemas = this.db.getRepository<any>('uiSchemas');
const schema = await uiSchemas.insert(
{
type: 'void',
'x-component': 'Menu',
'x-designer': 'Menu.Designer',
'x-initializer': 'MenuItemInitializers',
'x-component-props': {
mode: 'mix',
theme: 'dark',
// defaultSelectedUid: 'u8',
onSelect: '{{ onSelect }}',
sideMenuRefScopeKey: 'sideMenuRef',
},
properties: {},
},
{ transaction },
);
instance.set('options.adminSchemaUid', schema['x-uid']);
async install() {
const uiSchemas = this.db.getRepository<any>('uiSchemas');
await uiSchemas.insert({
type: 'void',
'x-uid': 'nocobase-admin-menu',
'x-component': 'Menu',
'x-designer': 'Menu.Designer',
'x-initializer': 'MenuItemInitializers',
'x-component-props': {
mode: 'mix',
theme: 'dark',
// defaultSelectedUid: 'u8',
onSelect: '{{ onSelect }}',
sideMenuRefScopeKey: 'sideMenuRef',
},
properties: {},
});
}

View File

@ -10,6 +10,7 @@ export default class extends Migration {
}
const r = this.db.getRepository<Repository>('storages');
await r.collection.sync();
const items = await r.find({
filter: {
type: 'local',

View File

@ -1,9 +1,9 @@
import { Context, Next } from '@nocobase/actions';
import { Database, Model, Op } from '@nocobase/database';
import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
import { NAMESPACE_COLLECTIONS, NAMESPACE_MENUS } from '../constans';
import LocalizationManagementPlugin from '../plugin';
import { getTextsFromDBRecord, getTextsFromUISchema } from '../utils';
import { NAMESPACE_COLLECTIONS, NAMESPACE_MENUS } from '../constans';
const getResourcesInstance = async (ctx: Context) => {
const plugin = ctx.app.getPlugin('localization-management') as LocalizationManagementPlugin;
@ -88,21 +88,24 @@ export const getTextsFromDB = async (db: Database) => {
return result;
};
const getSchemaUid = async (db: Database) => {
const systemSettings = await db.getRepository('systemSettings').findOne();
const options = systemSettings?.options || {};
const { adminSchemaUid, mobileSchemaUid } = options;
return { adminSchemaUid, mobileSchemaUid };
const getSchemaUid = async (db: Database, migrate = false) => {
if (migrate) {
const systemSettings = await db.getRepository('systemSettings').findOne();
const options = systemSettings?.options || {};
const { adminSchemaUid, mobileSchemaUid } = options;
return { adminSchemaUid, mobileSchemaUid };
}
return { adminSchemaUid: 'nocobase-admin-menu', mobileSchemaUid: 'nocobase-mobile-container' };
};
export const getTextsFromMenu = async (db: Database) => {
export const getTextsFromMenu = async (db: Database, migrate = false) => {
const result = {};
const { adminSchemaUid, mobileSchemaUid } = await getSchemaUid(db);
const { adminSchemaUid, mobileSchemaUid } = await getSchemaUid(db, migrate);
const repo = db.getRepository('uiSchemas') as UiSchemaRepository;
if (adminSchemaUid) {
const schema = await repo.getProperties(adminSchemaUid);
const extractTitle = (schema: any) => {
if (schema.properties) {
if (schema?.properties) {
Object.values(schema.properties).forEach((item: any) => {
if (item.title) {
result[item.title] = '';
@ -115,7 +118,7 @@ export const getTextsFromMenu = async (db: Database) => {
}
if (mobileSchemaUid) {
const schema = await repo.getProperties(mobileSchemaUid);
if (schema['properties']?.tabBar?.properties) {
if (schema?.['properties']?.tabBar?.properties) {
Object.values(schema['properties']?.tabBar?.properties).forEach((item: any) => {
const title = item['x-component-props']?.title;
if (title) {

View File

@ -1,7 +1,7 @@
import { Migration } from '@nocobase/server';
import { getTextsFromMenu, getTextsFromDB } from '../actions/localization';
import { NAMESPACE_COLLECTIONS, NAMESPACE_MENUS } from '../constans';
import { Op } from '@nocobase/database';
import { Migration } from '@nocobase/server';
import { getTextsFromDB, getTextsFromMenu } from '../actions/localization';
import { NAMESPACE_COLLECTIONS, NAMESPACE_MENUS } from '../constans';
export default class FixModuleMigration extends Migration {
async up() {
@ -12,10 +12,11 @@ export default class FixModuleMigration extends Migration {
}
const resources = await this.app.localeManager.getCacheResources('zh-CN');
const menus = await getTextsFromMenu(this.context.db);
const menus = await getTextsFromMenu(this.context.db, true);
const collections = await getTextsFromDB(this.context.db);
const db = this.context.db;
await db.getCollection('localizationTexts').sync();
await db.sequelize.transaction(async (t) => {
const menuTexts = Object.keys(menus);
await db.getModel('localizationTexts').update(

View File

@ -1,12 +1,4 @@
import {
ActionContextProvider,
AdminProvider,
css,
cx,
RemoteSchemaComponent,
useSystemSettings,
useViewport,
} from '@nocobase/client';
import { ActionContextProvider, AdminProvider, css, cx, RemoteSchemaComponent, useViewport } from '@nocobase/client';
import { DrawerProps, ModalProps } from 'antd';
import React, { useMemo } from 'react';
import { Outlet, useParams } from 'react-router-dom';
@ -76,8 +68,7 @@ const modalProps = {
};
const useMobileSchemaUid = () => {
const ctx = useSystemSettings();
return ctx?.data?.data?.options?.mobileSchemaUid;
return 'nocobase-mobile-container';
};
const MApplication: React.FC = (props) => {

View File

@ -3,6 +3,10 @@ import { Migration } from '@nocobase/server';
export default class extends Migration {
async up() {
const result = await this.app.version.satisfies('<0.14.0-alpha.1');
if (!result) {
return;
}
const systemSettings = this.db.getRepository('systemSettings');
let instance: Model = await systemSettings.findOne();
if (instance?.options?.mobileSchemaUid) {

View File

@ -0,0 +1,53 @@
import { Model } from '@nocobase/database';
import { Migration } from '@nocobase/server';
export default class extends Migration {
async up() {
const result = await this.app.version.satisfies('<0.17.0-alpha.8');
if (!result) {
return;
}
const systemSettings = this.db.getRepository('systemSettings');
const instance: Model = await systemSettings.findOne();
if (!instance?.options?.mobileSchemaUid) {
return;
}
const UiSchemas = this.db.getModel('uiSchemas');
await this.db.sequelize.transaction(async (transaction) => {
await UiSchemas.update(
{
'x-uid': 'nocobase-mobile-container',
},
{
transaction,
where: {
'x-uid': instance?.options?.mobileSchemaUid,
},
},
);
await this.db.getModel('uiSchemaTreePath').update(
{
descendant: 'nocobase-mobile-container',
},
{
transaction,
where: {
descendant: instance?.options?.mobileSchemaUid,
},
},
);
await this.db.getModel('uiSchemaTreePath').update(
{
ancestor: 'nocobase-mobile-container',
},
{
transaction,
where: {
ancestor: instance?.options?.mobileSchemaUid,
},
},
);
});
console.log(instance?.options?.mobileSchemaUid);
}
}

View File

@ -15,16 +15,10 @@ export class MobileClientPlugin extends Plugin {
}
async install() {
// const repository = this.app.db.getRepository('uiRoutes');
// for (const values of routes) {
// await repository.create({
// values,
// });
// }
const uiSchemas = this.db.getRepository<any>('uiSchemas');
const systemSettings = this.db.getRepository('systemSettings');
const schema = await uiSchemas.insert({
await uiSchemas.insert({
type: 'void',
'x-uid': 'nocobase-mobile-container',
'x-component': 'MContainer',
'x-designer': 'MContainer.Designer',
'x-component-props': {},
@ -47,9 +41,6 @@ export class MobileClientPlugin extends Plugin {
},
},
});
const instance = await systemSettings.findOne();
instance.set('options.mobileSchemaUid', schema['x-uid']);
await instance.save();
}
async afterEnable() {}

View File

@ -106,6 +106,7 @@ export default class extends Migration {
}
const { db } = this.context;
const NodeRepo = db.getRepository('flow_nodes');
await NodeRepo.collection.sync();
await db.sequelize.transaction(async (transaction) => {
const nodes = await NodeRepo.find({
filter: {