Merge pull request #13083 from strapi/ML-folder/folder-tree

Ml folder/folder tree
This commit is contained in:
Pierre Noël 2022-04-29 16:43:14 +02:00 committed by GitHub
commit b4cd89646b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 155 additions and 9 deletions

View File

@ -178,7 +178,7 @@ Object {
class=" css-1hb7zxy-IndicatorsContainer"
>
<button
class="sc-hGnimi sc-bPVzhA sc-evQXBP iaYaJy cDFemq cOhtSo"
class="sc-iuqRDJ sc-hqyYpk sc-imVSVl cuYSdT kzYvLH iYRxVH"
type="button"
>
<svg

View File

@ -2,7 +2,6 @@ import { useQuery } from 'react-query';
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
import { useNotification, useQueryParams } from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import { axiosInstance, getRequestUrl } from '../utils';
export const useFolders = ({ enabled = true }) => {

View File

@ -1,7 +1,7 @@
'use strict';
module.exports = {
collectionName: 'folders',
collectionName: 'upload_folders',
info: {
singularName: 'folder',
pluralName: 'folders',

View File

@ -73,4 +73,14 @@ module.exports = {
data: deletedFolders,
};
},
async getStructure(ctx) {
const { getStructure } = getService('folder');
const structure = await getStructure();
ctx.body = {
data: structure,
};
},
};

View File

@ -17,13 +17,13 @@ const validateCreateFolderSchema = yup
.required(),
parent: yup.strapiID().nullable(),
})
.noUnknown()
.required()
.test('is-folder-unique', 'name already taken', async folder => {
const { exists } = getService('folder');
const doesExist = await exists({ parent: folder.parent || null, name: folder.name });
return !doesExist;
})
.noUnknown()
.required();
});
const validateDeleteManyFoldersSchema = yup
.object()

View File

@ -16,7 +16,9 @@ const fileInfoSchema = yup.object({
return true;
}
return getService('folder').exists({ id: folderId });
const exists = await getService('folder').exists({ id: folderId });
return exists;
}),
});

View File

@ -139,5 +139,21 @@ module.exports = {
],
},
},
{
method: 'GET',
path: '/folder-structure',
handler: 'admin-folder.getStructure',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
config: {
actions: ['plugin::upload.read'],
},
},
],
},
},
],
};

View File

@ -1,6 +1,7 @@
'use strict';
const uuid = require('uuid').v4;
const { keys, sortBy, omit } = require('lodash/fp');
const { joinBy } = require('@strapi/utils');
const folderModel = 'plugin::upload.folder';
@ -42,8 +43,41 @@ const exists = async (params = {}) => {
return count > 0;
};
const getStructure = async () => {
const joinTable = strapi.db.metadata.get('plugin::upload.folder').attributes.parent.joinTable;
const qb = strapi.db.queryBuilder('plugin::upload.folder');
const alias = qb.getAlias();
const folders = await qb
.select(['id', 'name', `${alias}.${joinTable.inverseJoinColumn.name} as parent`])
.join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.joinColumn.name,
rootColumn: joinTable.joinColumn.referencedColumn,
rootTable: qb.alias,
})
.execute({ mapResults: false });
const folderMap = folders.reduce((map, f) => {
f.children = [];
map[f.id] = f;
return map;
}, {});
folderMap.null = { children: [] };
for (const id of keys(omit('null', folderMap))) {
const parentId = folderMap[id].parent;
folderMap[parentId].children.push(folderMap[id]);
folderMap[parentId].children = sortBy('name', folderMap[parentId].children);
delete folderMap[id].parent;
}
return folderMap.null.children;
};
module.exports = {
exists,
deleteByIds,
setPathAndUID,
getStructure,
};

View File

@ -0,0 +1,77 @@
'use strict';
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
let data = {
folders: [],
};
const createFolder = async (name, parent = null) => {
const res = await rq({
method: 'POST',
url: '/upload/folders',
body: { name, parent },
});
return res.body.data;
};
describe('Folder structure', () => {
const builder = createTestBuilder();
beforeAll(async () => {
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
const rootFolder1 = await createFolder('folder1');
const rootFolder2 = await createFolder('folder2');
const nestedFolder1a = await createFolder('folder1A', rootFolder1.id);
const nestedFolder1b = await createFolder('folder1B', rootFolder1.id);
const nestedFolder1a1 = await createFolder('folder1A1', nestedFolder1a.id);
data.folders.push(rootFolder1, rootFolder2, nestedFolder1a, nestedFolder1b, nestedFolder1a1);
});
afterAll(async () => {
await rq({
method: 'POST',
url: '/upload/folders/batch-delete',
body: {
ids: data.folders.map(f => f.id),
},
});
await strapi.destroy();
await builder.cleanup();
});
describe('Read', () => {
test('get structure', async () => {
const res = await rq({
method: 'GET',
url: '/upload/folder-structure',
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: [
{
children: [
{
children: [{ children: [], id: expect.anything(), name: 'folder1A1' }],
id: expect.anything(),
name: 'folder1A',
},
{ children: [], id: expect.anything(), name: 'folder1B' },
],
id: expect.anything(),
name: 'folder1',
},
{ children: [], id: expect.anything(), name: 'folder2' },
],
});
});
});
});

View File

@ -31,6 +31,14 @@ describe('Folder', () => {
});
afterAll(async () => {
await rq({
method: 'POST',
url: '/upload/folders/batch-delete',
body: {
ids: data.folders.map(f => f.id),
},
});
await strapi.destroy();
await builder.cleanup();
});

View File

@ -762,7 +762,7 @@ describe('Admin | containers | RoleCreatePage', () => {
border: 1px solid #4945ff;
}
.c44:hover:not([aria-disabled='true']) .sc-hrJiTw {
.c44:hover:not([aria-disabled='true']) .sc-jcneYm {
color: #271fe0;
}

View File

@ -831,7 +831,7 @@ describe('Admin | containers | RoleEditPage', () => {
border: 1px solid #4945ff;
}
.c49:hover:not([aria-disabled='true']) .sc-gtPNqn {
.c49:hover:not([aria-disabled='true']) .sc-kGrBqp {
color: #271fe0;
}