Browse Source

Merge pull request #3962 from nocodb/feat/api-for-complete-meta

Feat: Aggregated api for complete meta
pull/4001/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
ef7b723dea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      packages/nc-gui/pages/index/index/index.vue
  2. 1
      packages/noco-docs/content/en/developer-resources/rest-apis.md
  3. 45
      packages/nocodb-sdk/src/lib/Api.ts
  4. 11
      packages/nocodb/src/lib/meta/NcMetaIO.ts
  5. 33
      packages/nocodb/src/lib/meta/NcMetaIOImpl.ts
  6. 197
      packages/nocodb/src/lib/meta/api/utilApis.ts
  7. 195
      packages/nocodb/tests/unit/rest/tests/project.test.ts
  8. 333
      scripts/sdk/swagger.json

11
packages/nc-gui/pages/index/index/index.vue

@ -16,6 +16,7 @@ import {
themeV2Colors, themeV2Colors,
useApi, useApi,
useBreakpoints, useBreakpoints,
useCopy,
useGlobal, useGlobal,
useNuxtApp, useNuxtApp,
useUIPermission, useUIPermission,
@ -130,12 +131,20 @@ const customRow = (record: ProjectType) => ({
}) })
onBeforeMount(loadProjects) onBeforeMount(loadProjects)
const { copy } = useCopy()
const copyProjectMeta = async () => {
const aggregatedMetaInfo = await $api.utils.aggregatedMetaInfo()
copy(JSON.stringify(aggregatedMetaInfo))
message.info('Copied aggregated project meta to clipboard')
}
</script> </script>
<template> <template>
<div class="relative flex flex-col justify-center gap-2 w-full p-8 md:(bg-white rounded-lg border-1 border-gray-200 shadow)"> <div class="relative flex flex-col justify-center gap-2 w-full p-8 md:(bg-white rounded-lg border-1 border-gray-200 shadow)">
<h1 class="flex items-center justify-center gap-2 leading-8 mb-8 mt-4"> <h1 class="flex items-center justify-center gap-2 leading-8 mb-8 mt-4">
<span class="text-4xl nc-project-page-title">{{ $t('title.myProject') }}</span> <span class="text-4xl nc-project-page-title" @dblclick="copyProjectMeta">{{ $t('title.myProject') }}</span>
</h1> </h1>
<div class="flex flex-wrap gap-2 mb-6"> <div class="flex flex-wrap gap-2 mb-6">

1
packages/noco-docs/content/en/developer-resources/rest-apis.md

@ -172,6 +172,7 @@ Currently, the default value for {orgs} is <b>noco</b>. Users will be able to ch
| Meta | Get | utils | appInfo | /api/v1/db/meta/nocodb/info | | Meta | Get | utils | appInfo | /api/v1/db/meta/nocodb/info |
| Meta | Get | utils | appVersion | /api/v1/version | | Meta | Get | utils | appVersion | /api/v1/version |
| Meta | Get | utils | appHealth | /api/v1/health | | Meta | Get | utils | appHealth | /api/v1/health |
| Meta | Get | utils | aggregatedMetaInfo | /api/v1/aggregated-meta-info |
## Query params ## Query params

45
packages/nocodb-sdk/src/lib/Api.ts

@ -3437,6 +3437,51 @@ export class Api<
...params, ...params,
}), }),
/**
* No description
*
* @tags Utils
* @name AggregatedMetaInfo
* @request GET:/api/v1/aggregated-meta-info
* @response `200` `{ projectCount?: number, projects?: ({ tableCount?: { table?: number, view?: number }, external?: boolean, viewCount?: { formCount?: number, gridCount?: number, galleryCount?: number, kanbanCount?: number, total?: number, sharedFormCount?: number, sharedGridCount?: number, sharedGalleryCount?: number, sharedKanbanCount?: number, sharedTotal?: number, sharedLockedCount?: number }, webhookCount?: number, filterCount?: number, sortCount?: number, rowCount?: ({ TotalRecords?: string })[], userCount?: number })[], userCount?: number, sharedBaseCount?: number }` OK
*/
aggregatedMetaInfo: (params: RequestParams = {}) =>
this.request<
{
projectCount?: number;
projects?: {
tableCount?: { table?: number; view?: number };
external?: boolean;
viewCount?: {
formCount?: number;
gridCount?: number;
galleryCount?: number;
kanbanCount?: number;
total?: number;
sharedFormCount?: number;
sharedGridCount?: number;
sharedGalleryCount?: number;
sharedKanbanCount?: number;
sharedTotal?: number;
sharedLockedCount?: number;
};
webhookCount?: number;
filterCount?: number;
sortCount?: number;
rowCount?: { TotalRecords?: string }[];
userCount?: number;
}[];
userCount?: number;
sharedBaseCount?: number;
},
any
>({
path: `/api/v1/aggregated-meta-info`,
method: 'GET',
format: 'json',
...params,
}),
/** /**
* @description Get All K/V pairs in NocoCache * @description Get All K/V pairs in NocoCache
* *

11
packages/nocodb/src/lib/meta/NcMetaIO.ts

@ -162,6 +162,17 @@ export default abstract class NcMetaIO {
} }
): Promise<any[]>; ): Promise<any[]>;
public abstract metaCount(
project_id: string,
base_id: string,
target: string,
args?: {
condition?: { [key: string]: any };
xcCondition?: XcCondition;
aggField?: string;
}
): Promise<number>;
public abstract metaPaginatedList( public abstract metaPaginatedList(
project_id: string, project_id: string,
dbAlias: string, dbAlias: string,

33
packages/nocodb/src/lib/meta/NcMetaIOImpl.ts

@ -366,6 +366,39 @@ export default class NcMetaIOImpl extends NcMetaIO {
return query; return query;
} }
public async metaCount(
project_id: string,
dbAlias: string,
target: string,
args?: {
condition?: { [p: string]: any };
xcCondition?;
aggField?: string;
}
): Promise<number> {
const query = this.knexConnection(target);
if (project_id !== null && project_id !== undefined) {
query.where('project_id', project_id);
}
if (dbAlias !== null && dbAlias !== undefined) {
query.where('base_id', dbAlias);
}
if (args?.condition) {
query.where(args.condition);
}
if (args?.xcCondition) {
(query as any).condition(args.xcCondition);
}
query.count(args?.aggField || 'id', { as: 'count' }).first();
return +(await query)?.['count'] || 0;
}
public async metaUpdate( public async metaUpdate(
project_id: string, project_id: string,
dbAlias: string, dbAlias: string,

197
packages/nocodb/src/lib/meta/api/utilApis.ts

@ -2,6 +2,11 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { packageVersion } from 'nc-help'; import { packageVersion } from 'nc-help';
import { ViewTypes } from 'nocodb-sdk';
import Project from '../../models/Project';
import Noco from '../../Noco';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import { MetaTable } from '../../utils/globals';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import SqlMgrv2 from '../../db/sql-mgr/v2/SqlMgrv2'; import SqlMgrv2 from '../../db/sql-mgr/v2/SqlMgrv2';
import NcConfigFactory, { import NcConfigFactory, {
@ -166,14 +171,198 @@ export async function axiosRequestMake(req: Request, res: Response) {
export async function urlToDbConfig(req: Request, res: Response) { export async function urlToDbConfig(req: Request, res: Response) {
const { url } = req.body; const { url } = req.body;
try { try {
let connectionConfig; const connectionConfig = NcConfigFactory.extractXcUrlFromJdbc(url, true);
connectionConfig = NcConfigFactory.extractXcUrlFromJdbc(url, true);
return res.json(connectionConfig); return res.json(connectionConfig);
} catch (error) { } catch (error) {
return res.sendStatus(500); return res.sendStatus(500);
} }
} }
interface ViewCount {
formCount: number | null;
gridCount: number | null;
galleryCount: number | null;
kanbanCount: number | null;
total: number | null;
sharedFormCount: number | null;
sharedGridCount: number | null;
sharedGalleryCount: number | null;
sharedKanbanCount: number | null;
sharedTotal: number | null;
sharedLockedCount: number | null;
}
interface AllMeta {
projectCount: number;
projects: (
| {
external?: boolean | null;
tableCount: {
table: number;
view: number;
} | null;
viewCount: ViewCount;
webhookCount: number | null;
filterCount: number | null;
sortCount: number | null;
rowCount: ({ totalRecords: number } | null)[] | null;
userCount: number | null;
}
| { error: string }
)[];
userCount: number;
sharedBaseCount: number;
}
export async function aggregatedMetaInfo(_req: Request, res: Response) {
const [projects, userCount] = await Promise.all([
Project.list({}),
Noco.ncMeta.metaCount(null, null, MetaTable.USERS),
]);
const result: AllMeta = {
projectCount: projects.length,
projects: [],
userCount,
sharedBaseCount: 0,
};
result.projects.push(
...extractResultOrNull(
await Promise.allSettled(
projects.map(async (project) => {
if (project.uuid) result.sharedBaseCount++;
const [
tableCount,
dbViewCount,
viewCount,
webhookCount,
filterCount,
sortCount,
rowCount,
userCount,
] = extractResultOrNull(
await Promise.allSettled([
// db tables count
Noco.ncMeta.metaCount(project.id, null, MetaTable.MODELS, {
condition: {
type: 'table',
},
}),
// db views count
Noco.ncMeta.metaCount(project.id, null, MetaTable.MODELS, {
condition: {
type: 'view',
},
}),
// views count
(async () => {
const views = await Noco.ncMeta.metaList2(
project.id,
null,
MetaTable.VIEWS
);
// grid, form, gallery, kanban and shared count
return views.reduce<ViewCount>(
(out, view) => {
out.total++;
switch (view.type) {
case ViewTypes.GRID:
out.gridCount++;
if (view.uuid) out.sharedGridCount++;
break;
case ViewTypes.FORM:
out.formCount++;
if (view.uuid) out.sharedFormCount++;
break;
case ViewTypes.GALLERY:
out.galleryCount++;
if (view.uuid) out.sharedGalleryCount++;
break;
case ViewTypes.KANBAN:
out.kanbanCount++;
if (view.uuid) out.sharedKanbanCount++;
}
if (view.uuid) {
if (view.password) out.sharedLockedCount++;
out.sharedTotal++;
}
return out;
},
{
formCount: 0,
gridCount: 0,
galleryCount: 0,
kanbanCount: 0,
total: 0,
sharedFormCount: 0,
sharedGridCount: 0,
sharedGalleryCount: 0,
sharedKanbanCount: 0,
sharedTotal: 0,
sharedLockedCount: 0,
}
);
})(),
// webhooks count
Noco.ncMeta.metaCount(project.id, null, MetaTable.HOOKS),
// filters count
Noco.ncMeta.metaCount(project.id, null, MetaTable.FILTER_EXP),
// sorts count
Noco.ncMeta.metaCount(project.id, null, MetaTable.SORT),
// row count per base
project.getBases().then(async (bases) => {
return extractResultOrNull(
await Promise.allSettled(
bases.map((base) =>
NcConnectionMgrv2.getSqlClient(base)
.totalRecords?.()
?.then((result) => result?.data)
)
)
);
}),
// project users count
Noco.ncMeta.metaCount(null, null, MetaTable.PROJECT_USERS, {
condition: {
project_id: project.id,
},
aggField: '*',
}),
])
);
return {
tableCount: { table: tableCount, view: dbViewCount },
external: !project.is_meta,
viewCount,
webhookCount,
filterCount,
sortCount,
rowCount,
userCount,
};
})
)
)
);
res.json(result);
}
const extractResultOrNull = (results: PromiseSettledResult<any>[]) => {
return results.map((result) => {
if (result.status === 'fulfilled') {
return result.value;
}
console.log(result.reason);
return null;
});
};
export default (router) => { export default (router) => {
router.post( router.post(
'/api/v1/db/meta/connection/test', '/api/v1/db/meta/connection/test',
@ -185,4 +374,8 @@ export default (router) => {
router.get('/api/v1/health', catchError(appHealth)); router.get('/api/v1/health', catchError(appHealth));
router.get('/api/v1/feedback_form', catchError(feedbackFormGet)); router.get('/api/v1/feedback_form', catchError(feedbackFormGet));
router.post('/api/v1/url_to_config', catchError(urlToDbConfig)); router.post('/api/v1/url_to_config', catchError(urlToDbConfig));
router.get(
'/api/v1/aggregated-meta-info',
ncMetaAclMw(aggregatedMetaInfo, 'aggregatedMetaInfo')
);
}; };

195
packages/nocodb/tests/unit/rest/tests/project.test.ts

@ -1,28 +1,30 @@
import 'mocha'; import 'mocha'
import request from 'supertest'; import request from 'supertest'
import init from '../../init/index'; import { createTable } from '../../factory/table'
import { createProject, createSharedBase } from '../../factory/project'; import init from '../../init/index'
import { beforeEach } from 'mocha'; import { createProject, createSharedBase } from '../../factory/project'
import { Exception } from 'handlebars'; import { beforeEach } from 'mocha'
import Project from '../../../../src/lib/models/Project'; import { Exception } from 'handlebars'
import Project from '../../../../src/lib/models/Project'
import { expect } from 'chai'
function projectTest() { function projectTest() {
let context; let context
let project; let project
beforeEach(async function() { beforeEach(async function() {
context = await init(); context = await init()
project = await createProject(context); project = await createProject(context)
}); })
it('Get project info', async () => { it('Get project info', async () => {
await request(context.app) await request(context.app)
.get(`/api/v1/db/meta/projects/${project.id}/info`) .get(`/api/v1/db/meta/projects/${project.id}/info`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send({}) .send({})
.expect(200); .expect(200)
}); })
// todo: Test by creating models under project and check if the UCL is working // todo: Test by creating models under project and check if the UCL is working
it('UI ACL', async () => { it('UI ACL', async () => {
@ -30,8 +32,8 @@ function projectTest() {
.get(`/api/v1/db/meta/projects/${project.id}/visibility-rules`) .get(`/api/v1/db/meta/projects/${project.id}/visibility-rules`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send({}) .send({})
.expect(200); .expect(200)
}); })
// todo: Test creating visibility set // todo: Test creating visibility set
it('List projects', async () => { it('List projects', async () => {
@ -39,11 +41,11 @@ function projectTest() {
.get('/api/v1/db/meta/projects/') .get('/api/v1/db/meta/projects/')
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send({}) .send({})
.expect(200); .expect(200)
if (response.body.list.length !== 1) new Error('Should list only 1 project'); if (response.body.list.length !== 1) new Error('Should list only 1 project')
if (!response.body.pageInfo) new Error('Should have pagination info'); if (!response.body.pageInfo) new Error('Should have pagination info')
}); })
it('Create project', async () => { it('Create project', async () => {
const response = await request(context.app) const response = await request(context.app)
@ -52,11 +54,11 @@ function projectTest() {
.send({ .send({
title: 'Title1', title: 'Title1',
}) })
.expect(200); .expect(200)
const newProject = await Project.getByTitleOrId(response.body.id); const newProject = await Project.getByTitleOrId(response.body.id)
if (!newProject) return new Error('Project not created'); if (!newProject) return new Error('Project not created')
}); })
it('Create projects with existing title', async () => { it('Create projects with existing title', async () => {
await request(context.app) await request(context.app)
@ -65,8 +67,8 @@ function projectTest() {
.send({ .send({
title: project.title, title: project.title,
}) })
.expect(400); .expect(400)
}); })
// todo: fix passport user role popluation bug // todo: fix passport user role popluation bug
// it('Delete project', async async () => { // it('Delete project', async async () => {
@ -97,10 +99,10 @@ function projectTest() {
.get(`/api/v1/db/meta/projects/${project.id}`) .get(`/api/v1/db/meta/projects/${project.id}`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send() .send()
.expect(200); .expect(200)
if (response.body.id !== project.id) return new Error('Got the wrong project'); if (response.body.id !== project.id) return new Error('Got the wrong project')
}); })
it('Update projects', async () => { it('Update projects', async () => {
await request(context.app) await request(context.app)
@ -109,18 +111,18 @@ function projectTest() {
.send({ .send({
title: 'NewTitle', title: 'NewTitle',
}) })
.expect(200); .expect(200)
const newProject = await Project.getByTitleOrId(project.id); const newProject = await Project.getByTitleOrId(project.id)
if (newProject.title !== 'NewTitle') { if (newProject.title !== 'NewTitle') {
return new Error('Project not updated'); return new Error('Project not updated')
} }
}); })
it('Update projects with existing title', async function() { it('Update projects with existing title', async function() {
const newProject = await createProject(context, { const newProject = await createProject(context, {
title: 'NewTitle1', title: 'NewTitle1',
}); })
await request(context.app) await request(context.app)
.patch(`/api/v1/db/meta/projects/${project.id}`) .patch(`/api/v1/db/meta/projects/${project.id}`)
@ -128,8 +130,8 @@ function projectTest() {
.send({ .send({
title: newProject.title, title: newProject.title,
}) })
.expect(400); .expect(400)
}); })
it('Create project shared base', async () => { it('Create project shared base', async () => {
await request(context.app) await request(context.app)
@ -139,18 +141,18 @@ function projectTest() {
roles: 'viewer', roles: 'viewer',
password: 'test', password: 'test',
}) })
.expect(200); .expect(200)
const updatedProject = await Project.getByTitleOrId(project.id); const updatedProject = await Project.getByTitleOrId(project.id)
if ( if (
!updatedProject.uuid || !updatedProject.uuid ||
updatedProject.roles !== 'viewer' || updatedProject.roles !== 'viewer' ||
updatedProject.password !== 'test' updatedProject.password !== 'test'
) { ) {
return new Error('Shared base not configured properly'); return new Error('Shared base not configured properly')
} }
}); })
it('Created project shared base should have only editor or viewer role', async () => { it('Created project shared base should have only editor or viewer role', async () => {
await request(context.app) await request(context.app)
@ -160,17 +162,17 @@ function projectTest() {
roles: 'commenter', roles: 'commenter',
password: 'test', password: 'test',
}) })
.expect(200); .expect(200)
const updatedProject = await Project.getByTitleOrId(project.id); const updatedProject = await Project.getByTitleOrId(project.id)
if (updatedProject.roles === 'commenter') { if (updatedProject.roles === 'commenter') {
return new Error('Shared base not configured properly'); return new Error('Shared base not configured properly')
} }
}); })
it('Updated project shared base should have only editor or viewer role', async () => { it('Updated project shared base should have only editor or viewer role', async () => {
await createSharedBase(context.app, context.token, project); await createSharedBase(context.app, context.token, project)
await request(context.app) await request(context.app)
.patch(`/api/v1/db/meta/projects/${project.id}/shared`) .patch(`/api/v1/db/meta/projects/${project.id}/shared`)
@ -179,17 +181,17 @@ function projectTest() {
roles: 'commenter', roles: 'commenter',
password: 'test', password: 'test',
}) })
.expect(200); .expect(200)
const updatedProject = await Project.getByTitleOrId(project.id); const updatedProject = await Project.getByTitleOrId(project.id)
if (updatedProject.roles === 'commenter') { if (updatedProject.roles === 'commenter') {
throw new Exception('Shared base not updated properly'); throw new Exception('Shared base not updated properly')
} }
}); })
it('Updated project shared base', async () => { it('Updated project shared base', async () => {
await createSharedBase(context.app, context.token, project); await createSharedBase(context.app, context.token, project)
await request(context.app) await request(context.app)
.patch(`/api/v1/db/meta/projects/${project.id}/shared`) .patch(`/api/v1/db/meta/projects/${project.id}/shared`)
@ -198,42 +200,42 @@ function projectTest() {
roles: 'editor', roles: 'editor',
password: 'test', password: 'test',
}) })
.expect(200); .expect(200)
const updatedProject = await Project.getByTitleOrId(project.id); const updatedProject = await Project.getByTitleOrId(project.id)
if (updatedProject.roles !== 'editor') { if (updatedProject.roles !== 'editor') {
throw new Exception('Shared base not updated properly'); throw new Exception('Shared base not updated properly')
} }
}); })
it('Get project shared base', async () => { it('Get project shared base', async () => {
await createSharedBase(context.app, context.token, project); await createSharedBase(context.app, context.token, project)
await request(context.app) await request(context.app)
.get(`/api/v1/db/meta/projects/${project.id}/shared`) .get(`/api/v1/db/meta/projects/${project.id}/shared`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send() .send()
.expect(200); .expect(200)
const updatedProject = await Project.getByTitleOrId(project.id); const updatedProject = await Project.getByTitleOrId(project.id)
if (!updatedProject.uuid) { if (!updatedProject.uuid) {
throw new Exception('Shared base not created'); throw new Exception('Shared base not created')
} }
}); })
it('Delete project shared base', async () => { it('Delete project shared base', async () => {
await createSharedBase(context.app, context.token, project); await createSharedBase(context.app, context.token, project)
await request(context.app) await request(context.app)
.delete(`/api/v1/db/meta/projects/${project.id}/shared`) .delete(`/api/v1/db/meta/projects/${project.id}/shared`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send() .send()
.expect(200); .expect(200)
const updatedProject = await Project.getByTitleOrId(project.id); const updatedProject = await Project.getByTitleOrId(project.id)
if (updatedProject.uuid) { if (updatedProject.uuid) {
throw new Exception('Shared base not deleted'); throw new Exception('Shared base not deleted')
} }
}); })
// todo: Do compare api test // todo: Do compare api test
@ -242,16 +244,16 @@ function projectTest() {
.get(`/api/v1/db/meta/projects/${project.id}/meta-diff`) .get(`/api/v1/db/meta/projects/${project.id}/meta-diff`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send() .send()
.expect(200); .expect(200)
}); })
it('Meta diff sync', async () => { it('Meta diff sync', async () => {
await request(context.app) await request(context.app)
.post(`/api/v1/db/meta/projects/${project.id}/meta-diff`) .post(`/api/v1/db/meta/projects/${project.id}/meta-diff`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send() .send()
.expect(200); .expect(200)
}); })
// todo: improve test. Check whether the all the actions are present in the response and correct as well // todo: improve test. Check whether the all the actions are present in the response and correct as well
it('Meta diff sync', async () => { it('Meta diff sync', async () => {
@ -259,10 +261,61 @@ function projectTest() {
.get(`/api/v1/db/meta/projects/${project.id}/audits`) .get(`/api/v1/db/meta/projects/${project.id}/audits`)
.set('xc-auth', context.token) .set('xc-auth', context.token)
.send() .send()
.expect(200); .expect(200)
}); })
it('Get all projects meta', async () => {
await createTable(context, project, { table_name: 'table1', title: 'table1' })
await createTable(context, project, { table_name: 'table2', title: 'table2' })
await createTable(context, project, { table_name: 'table3', title: 'table3' })
await request(context.app)
.get(`/api/v1/aggregated-meta-info`)
.set('xc-auth', context.token)
.send({})
.expect(200)
.then(res => {
expect(res.body).to.have.all.keys(
'userCount',
'sharedBaseCount',
'projectCount',
'projects',
)
expect(res.body).to.have.property('projectCount').to.eq(1)
expect(res.body).to.have.property('projects').to.be.an('array')
expect(res.body.projects[0].tableCount.table).to.be.eq(3)
expect(res.body).to.have.nested.property('projects[0].tableCount.table').to.be.a('number')
expect(res.body).to.have.nested.property('projects[0].tableCount.view').to.be.a('number')
expect(res.body).to.have.nested.property('projects[0].viewCount').to.be.an('object')
.have.keys(
'formCount',
'gridCount',
'galleryCount',
'kanbanCount',
'total',
'sharedFormCount',
'sharedGridCount',
'sharedGalleryCount',
'sharedKanbanCount',
'sharedTotal',
'sharedLockedCount')
expect(res.body.projects[0]).have.keys(
'external',
'webhookCount',
'filterCount',
'sortCount',
'userCount',
'rowCount',
'tableCount',
'viewCount',
)
expect(res.body).to.have.nested.property('projects[0].rowCount').to.be.an('array')
expect(res.body).to.have.nested.property('projects[0].external').to.be.an('boolean')
})
})
} }
export default function() { export default function() {
describe('Project', projectTest); describe('Project', projectTest)
} }

333
scripts/sdk/swagger.json

@ -5672,6 +5672,339 @@
"description": "" "description": ""
} }
}, },
"/api/v1/aggregated-meta-info": {
"parameters": [],
"get": {
"summary": "",
"operationId": "utils-aggregated-meta-info",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"projectCount": {
"type": "integer"
},
"projects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tableCount": {
"type": "object",
"properties": {
"table": {
"type": "integer"
},
"view": {
"type": "integer"
}
}
},
"external": {
"type": "boolean"
},
"viewCount": {
"type": "object",
"properties": {
"formCount": {
"type": "integer"
},
"gridCount": {
"type": "integer"
},
"galleryCount": {
"type": "integer"
},
"kanbanCount": {
"type": "integer"
},
"total": {
"type": "integer"
},
"sharedFormCount": {
"type": "integer"
},
"sharedGridCount": {
"type": "integer"
},
"sharedGalleryCount": {
"type": "integer"
},
"sharedKanbanCount": {
"type": "integer"
},
"sharedTotal": {
"type": "integer"
},
"sharedLockedCount": {
"type": "integer"
}
}
},
"webhookCount": {
"type": "integer"
},
"filterCount": {
"type": "integer"
},
"sortCount": {
"type": "integer"
},
"rowCount": {
"type": "array",
"items": {
"type": "object",
"properties": {
"TotalRecords": {
"type": "string"
}
}
}
},
"userCount": {
"type": "integer"
}
}
}
},
"userCount": {
"type": "integer"
},
"sharedBaseCount": {
"type": "integer"
}
},
"x-examples": {
"Example 1": {
"projectCount": 1,
"projects": [
{
"tableCount": {
"table": 3,
"view": 0
},
"external": false,
"viewCount": {
"formCount": 0,
"gridCount": 3,
"galleryCount": 0,
"kanbanCount": 0,
"total": 3,
"sharedFormCount": 0,
"sharedGridCount": 0,
"sharedGalleryCount": 0,
"sharedKanbanCount": 0,
"sharedTotal": 0,
"sharedLockedCount": 0
},
"webhookCount": 0,
"filterCount": 0,
"sortCount": 0,
"rowCount": [
{
"TotalRecords": "76"
}
],
"userCount": 1
}
],
"userCount": 1,
"sharedBaseCount": 0
}
}
},
"examples": {
"example-1": {
"value": {
"projectCount": 1,
"projects": [
{
"tableCount": {
"table": 3,
"view": 0
},
"external": false,
"viewCount": {
"formCount": 0,
"gridCount": 3,
"galleryCount": 0,
"kanbanCount": 0,
"total": 3,
"sharedFormCount": 0,
"sharedGridCount": 0,
"sharedGalleryCount": 0,
"sharedKanbanCount": 0,
"sharedTotal": 0,
"sharedLockedCount": 0
},
"webhookCount": 0,
"filterCount": 0,
"sortCount": 0,
"rowCount": [
{
"TotalRecords": "76"
}
],
"userCount": 1
}
],
"userCount": 1,
"sharedBaseCount": 0
}
}
}
},
"application/xml": {
"schema": {
"type": "object",
"properties": {
"projectCount": {
"type": "integer"
},
"projects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tableCount": {
"type": "object",
"properties": {
"table": {
"type": "integer"
},
"view": {
"type": "integer"
}
}
},
"viewCount": {
"type": "object",
"properties": {
"formCount": {
"type": "integer"
},
"gridCount": {
"type": "integer"
},
"galleryCount": {
"type": "integer"
},
"kanbanCount": {
"type": "integer"
},
"total": {
"type": "integer"
},
"sharedFormCount": {
"type": "integer"
},
"sharedGridCount": {
"type": "integer"
},
"sharedGalleryCount": {
"type": "integer"
},
"sharedKanbanCount": {
"type": "integer"
},
"sharedTotal": {
"type": "integer"
},
"sharedLockedCount": {
"type": "integer"
}
}
},
"webhookCount": {
"type": "integer"
},
"filterCount": {
"type": "integer"
},
"sortCount": {
"type": "integer"
},
"rowCount": {
"type": "array",
"items": {
"type": "object",
"properties": {
"TotalRecords": {
"type": "string"
}
}
}
},
"userCount": {
"type": "integer"
}
}
}
},
"userCount": {
"type": "integer"
},
"sharedBaseCount": {
"type": "integer"
}
},
"x-examples": {
"Example 1": {
"projectCount": 1,
"projects": [
{
"tableCount": {
"table": 3,
"view": 0
},
"viewCount": {
"formCount": 0,
"gridCount": 3,
"galleryCount": 0,
"kanbanCount": 0,
"total": 3,
"sharedFormCount": 0,
"sharedGridCount": 0,
"sharedGalleryCount": 0,
"sharedKanbanCount": 0,
"sharedTotal": 0,
"sharedLockedCount": 0
},
"webhookCount": 0,
"filterCount": 0,
"sortCount": 0,
"rowCount": [
{
"TotalRecords": "76"
}
],
"userCount": 1
}
],
"userCount": 1,
"sharedBaseCount": 0
}
}
},
"examples": {
"example-1": {
"value": "{\n \"projectCount\": 1,\n \"projects\": [\n {\n \"tableCount\": {\n \"table\": 3,\n \"view\": 0\n },\n \"viewCount\": {\n \"formCount\": 0,\n \"gridCount\": 3,\n \"galleryCount\": 0,\n \"kanbanCount\": 0,\n \"total\": 3,\n \"sharedFormCount\": 0,\n \"sharedGridCount\": 0,\n \"sharedGalleryCount\": 0,\n \"sharedKanbanCount\": 0,\n \"sharedTotal\": 0,\n \"sharedLockedCount\": 0\n },\n \"webhookCount\": 0,\n \"filterCount\": 0,\n \"sortCount\": 0,\n \"rowCount\": [\n {\n \"TotalRecords\": \"76\"\n }\n ],\n \"userCount\": 1\n }\n ],\n \"userCount\": 1,\n \"sharedBaseCount\": 0\n}"
}
}
}
}
}
},
"tags": [
"Utils"
],
"description": ""
}
},
"/api/v1/db/meta/cache": { "/api/v1/db/meta/cache": {
"get": { "get": {
"summary": "Your GET endpoint", "summary": "Your GET endpoint",

Loading…
Cancel
Save