🐛 Not all the data is shown in the shared with me section (#376)

* 🐛Not all the data is shown in the shared with me section
This commit is contained in:
Anton Shepilov
2024-02-26 18:07:59 +01:00
committed by GitHub
parent 62845798b9
commit 07fc285721
11 changed files with 124 additions and 75 deletions
@@ -283,11 +283,12 @@ export default class ESAndOpenSearch extends SearchAdapter implements SearchAdap
const esParamsWithScroll = { const esParamsWithScroll = {
...esParams, ...esParams,
size: parseInt(options.pagination.limitStr || "100"), size: parseInt(options.pagination.limitStr || "100"),
scroll: "1m", scroll: "10m",
}; };
let esResponse: any; let esResponse: any;
let nextToken;
if (options.pagination.page_token) { if (options.pagination.page_token) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
@@ -297,18 +298,23 @@ export default class ESAndOpenSearch extends SearchAdapter implements SearchAdap
}, },
esOptions, esOptions,
); );
//the scroll token is also the same, and we do not get it from response
nextToken = options.pagination.page_token;
} else { } else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
esResponse = await this.client.search(esParamsWithScroll, esOptions); esResponse = await this.client.search(esParamsWithScroll, esOptions);
nextToken = esResponse.body?._scroll_id || "";
} }
if (esResponse.statusCode !== 200) { if (esResponse.statusCode !== 200) {
logger.error(`${this.name} - ${JSON.stringify(esResponse.body)}`); logger.error(`${this.name} - ${JSON.stringify(esResponse.body)}`);
} }
const nextToken = esResponse.body?._scroll_id || "";
const hits = esResponse.body?.hits?.hits || []; const hits = esResponse.body?.hits?.hits || [];
if (hits.length == 0) {
nextToken = false;
}
logger.debug(`${this.name} got response: ${JSON.stringify(esResponse)}`); logger.debug(`${this.name} got response: ${JSON.stringify(esResponse)}`);
@@ -200,9 +200,10 @@ export default class MongoSearch extends SearchAdapter implements SearchAdapterI
const { query, sort, project } = buildSearchQuery<EntityType>(entityType, filters, options); const { query, sort, project } = buildSearchQuery<EntityType>(entityType, filters, options);
logger.info(`Search query: ${JSON.stringify(query)}`);
console.log(query); console.log(query);
let cursor = collection.find({ ...query }).sort(sort); let cursor = collection.find(query).sort(sort);
if (project) { if (project) {
cursor = cursor.project(project); cursor = cursor.project(project);
} }
@@ -230,7 +231,7 @@ export default class MongoSearch extends SearchAdapter implements SearchAdapterI
const nextToken = const nextToken =
entities.length === parseInt(options.pagination.limitStr) && entities.length === parseInt(options.pagination.limitStr) &&
(parseInt(options.pagination.page_token) + 1).toString(10); (parseInt(options.pagination.page_token || "0") + 1).toString(10);
const nextPage: Paginable = new Pagination(nextToken, options.pagination.limitStr || "100"); const nextPage: Paginable = new Pagination(nextToken, options.pagination.limitStr || "100");
logger.info(`Found ${entities.length} results on entity ${searchPrefix}${index}`); logger.info(`Found ${entities.length} results on entity ${searchPrefix}${index}`);
@@ -40,7 +40,7 @@ export default class SearchRepository<EntityType> {
options, options,
); );
//2. Get database original objects from theses primary keys //2. Get database original objects from these primary keys
for (const searchEntity of searchResults.getEntities().sort((a, b) => b.score - a.score)) { for (const searchEntity of searchResults.getEntities().sort((a, b) => b.score - a.score)) {
const sourceEntity = await repository.findOne(searchEntity.primaryKey, {}, context); const sourceEntity = await repository.findOne(searchEntity.primaryKey, {}, context);
if (sourceEntity) { if (sourceEntity) {
@@ -1,6 +1,10 @@
import SearchRepository from "../../../core/platform/services/search/repository"; import SearchRepository from "../../../core/platform/services/search/repository";
import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework"; import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework";
import { CrudException, ListResult } from "../../../core/platform/framework/api/crud-service"; import {
CrudException,
ListResult,
Pagination,
} from "../../../core/platform/framework/api/crud-service";
import Repository, { import Repository, {
comparisonType, comparisonType,
inType, inType,
@@ -96,13 +100,7 @@ export class DocumentsService {
context: DriveExecutionContext & { public_token?: string }, context: DriveExecutionContext & { public_token?: string },
): Promise<BrowseDetails> => { ): Promise<BrowseDetails> => {
if (isSharedWithMeFolder(id)) { if (isSharedWithMeFolder(id)) {
const children = await this.search(options, context); return this.sharedWithMe(options, context);
return {
access: "read",
children: children.getEntities(),
nextPage: children.nextPage,
path: [] as Array<DriveFile>,
};
} else { } else {
return { return {
nextPage: null, nextPage: null,
@@ -111,6 +109,25 @@ export class DocumentsService {
} }
}; };
sharedWithMe = async (
options: SearchDocumentsOptions,
context: DriveExecutionContext & { public_token?: string },
): Promise<BrowseDetails> => {
const result = [];
let fileList: ListResult<DriveFile>;
do {
fileList = await this.search(options, context);
result.push(...fileList.getEntities());
options.pagination = fileList.nextPage;
} while (fileList.nextPage?.page_token);
return {
access: "read",
children: result,
nextPage: null,
path: [] as Array<DriveFile>,
};
};
userQuota = async (context: CompanyExecutionContext): Promise<number> => { userQuota = async (context: CompanyExecutionContext): Promise<number> => {
const children = await this.repository.find({ const children = await this.repository.find({
parent_id: "user_" + context.user.id, parent_id: "user_" + context.user.id,
@@ -955,9 +972,9 @@ export class DocumentsService {
const result = await this.searchRepository.search( const result = await this.searchRepository.search(
{}, {},
{ {
pagination: { pagination: options.pagination
limitStr: "100", ? Pagination.fromPaginable(options.pagination)
}, : new Pagination(),
$in: [ $in: [
...(options.onlyDirectlyShared ? [["access_entities", [context.user.id]] as inType] : []), ...(options.onlyDirectlyShared ? [["access_entities", [context.user.id]] as inType] : []),
...(options.company_id ? [["company_id", [options.company_id]] as inType] : []), ...(options.company_id ? [["company_id", [options.company_id]] as inType] : []),
@@ -999,7 +1016,7 @@ export class DocumentsService {
if (!options.onlyDirectlyShared) { if (!options.onlyDirectlyShared) {
const filteredResult = await this.filter(result.getEntities(), async item => { const filteredResult = await this.filter(result.getEntities(), async item => {
try { try {
// Check access for each item //skip all the fiels
return await checkAccess(item.id, null, "read", this.repository, context); return await checkAccess(item.id, null, "read", this.repository, context);
} catch (error) { } catch (error) {
this.logger.warn("failed to check item access", error); this.logger.warn("failed to check item access", error);
@@ -1017,7 +1034,6 @@ export class DocumentsService {
return new ListResult(result.type, filteredResult, result.nextPage); return new ListResult(result.type, filteredResult, result.nextPage);
} }
return result; return result;
}; };
@@ -52,6 +52,7 @@ export type SearchDocumentsOptions = {
fields?: string[]; fields?: string[];
onlyDirectlyShared?: boolean; onlyDirectlyShared?: boolean;
onlyUploadedNotByMe?: boolean; onlyUploadedNotByMe?: boolean;
pagination?: Paginable;
}; };
export type SearchDocumentsBody = { export type SearchDocumentsBody = {
@@ -6,9 +6,9 @@ import { File } from "../../../../services/files/entities/file";
import { UploadOptions } from "../../../../services/files/types"; import { UploadOptions } from "../../../../services/files/types";
import globalResolver from "../../../../services/global-resolver"; import globalResolver from "../../../../services/global-resolver";
import { import {
CompanyUserRole,
PaginationQueryParameters, PaginationQueryParameters,
ResourceWebsocket, ResourceWebsocket,
CompanyUserRole,
} from "../../../../utils/types"; } from "../../../../utils/types";
import { DriveFile } from "../../entities/drive-file"; import { DriveFile } from "../../entities/drive-file";
import { FileVersion } from "../../entities/file-version"; import { FileVersion } from "../../entities/file-version";
@@ -199,48 +199,10 @@ export class DocumentsController {
onlyUploadedNotByMe: true, onlyUploadedNotByMe: true,
}; };
const data = { return {
...(await globalResolver.services.documents.documents.browse(id, options, context)), ...(await globalResolver.services.documents.documents.browse(id, options, context)),
websockets: request.currentUser?.id websockets: [],
? globalResolver.platformServices.realtime.sign(
[{ room: `/companies/${context.company.id}/documents/item/${id}` }],
request.currentUser?.id,
)
: [],
}; };
return data;
};
sharedWithMe = async (
request: FastifyRequest<{
Params: RequestParams;
Body: SearchDocumentsBody;
Querystring: { public_token?: string };
}>,
): Promise<ListResult<DriveFileDTO>> => {
try {
const context = getDriveExecutionContext(request);
const options: SearchDocumentsOptions = {
...request.body,
company_id: request.body.company_id || context.company.id,
view: DriveFileDTOBuilder.VIEW_SHARED_WITH_ME,
onlyDirectlyShared: true,
onlyUploadedNotByMe: true,
};
if (!Object.keys(options).length) {
this.throw500Search();
}
const fileList = await globalResolver.services.documents.documents.search(options, context);
return this.driveFileDTOBuilder.build(fileList, context, options.fields, options.view);
} catch (error) {
logger.error({ error: `${error}` }, "error while searching for document");
this.throw500Search();
}
}; };
/** /**
@@ -107,13 +107,6 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
handler: documentsController.search.bind(documentsController), handler: documentsController.search.bind(documentsController),
}); });
fastify.route({
method: "POST",
url: `${baseUrl}/shared-with-me`,
preValidation: [fastify.authenticate],
handler: documentsController.sharedWithMe.bind(documentsController),
});
fastify.route({ fastify.route({
method: "POST", method: "POST",
url: `${baseUrl}/browse/:id`, url: `${baseUrl}/browse/:id`,
@@ -343,15 +343,15 @@ export default class UserApi {
) { ) {
const response = await this.platform.app.inject({ const response = await this.platform.app.inject({
method: "POST", method: "POST",
url: `${UserApi.DOC_URL}/companies/${this.platform.workspace.company_id}/shared-with-me`, url: `${UserApi.DOC_URL}/companies/${this.platform.workspace.company_id}/browse/shared_with_me`,
headers: { headers: {
authorization: `Bearer ${this.jwt}` authorization: `Bearer ${this.jwt}`
}, },
payload payload
}); });
return deserialize<SearchResultMockClass>( return deserialize<DriveItemDetailsMockClass>(
SearchResultMockClass, DriveItemDetailsMockClass,
response.body); response.body);
}; };
@@ -126,10 +126,32 @@ describe("The Documents Browser Window and API", () => {
await new Promise(r => setTimeout(r, 3000)); await new Promise(r => setTimeout(r, 3000));
//then file become searchable //then file become searchable
expect((await anotherUser.browseDocuments("shared_with_me", {})).children).toHaveLength(1); expect((await anotherUser.browseDocuments("shared_with_me", {pageSize: 1})).children).toHaveLength(1);
});
it("Share With Me should return all the files that was share by user at one", async () => {
const sharedWIthMeFolder = "shared_with_me";
const oneUser = await UserApi.getInstance(platform, true, {companyRole: "admin"});
const anotherUser = await UserApi.getInstance(platform, true, {companyRole: "admin"});
let files = await oneUser.uploadAllFilesOneByOne();
await anotherUser.uploadAllFilesOneByOne();
await new Promise(r => setTimeout(r, 5000));
//give permissions to the file
files[2].access_info.entities.push({
type: "user",
id: anotherUser.user.id,
level: "read",
grantor: null,
});
await oneUser.updateDocument(files[2].id, files[2]);
await new Promise(r => setTimeout(r, 3000));
//then file become searchable
expect((await anotherUser.browseDocuments("shared_with_me", {pagination: {limitStr: 100}})).children).toHaveLength(1);
}); });
}); });
}); });
@@ -106,7 +106,7 @@ describe("the Drive Search feature", () => {
const searchResponse = await userTwo.sharedWithMeDocuments({}); const searchResponse = await userTwo.sharedWithMeDocuments({});
//then:: //then::
expect(searchResponse.entities?.length).toEqual(1); expect(searchResponse.children?.length).toEqual(1);
}) })
it("did search for an item that doesn't exist", async () => { it("did search for an item that doesn't exist", async () => {
@@ -146,7 +146,7 @@ describe("the Drive Search feature", () => {
it("did search a file shared by another user", async () => { it("did search a file shared by another user", async () => {
//then file become searchable //then file become searchable
expect((await userTwo.sharedWithMeDocuments({})).entities).toHaveLength(1); expect((await userTwo.sharedWithMeDocuments({})).children).toHaveLength(1);
}); });
it("did search a file by file owner", async () => { it("did search a file by file owner", async () => {
@@ -20,13 +20,61 @@ Content-Type: application/json
} }
} }
### Query data
GET https://localhost:9200/drive_files/_search
Authorization: Basic admin admin
Content-Type: application/json
{
"query": {
"bool": {
"boost": 1,
"must": [
{
"bool": {
"should": [
{
"match": {
"access_entities": {
"query": "7f2e4e80-d4be-11ee-8a11-0544b51cf428",
"operator": "AND"
}
}
}
],
"minimum_should_match": 1
}
},
{
"bool": {
"should": [
{
"match": {
"company_id": {
"query": "7efb7eb1-d4be-11ee-8a11-0544b51cf428",
"operator": "AND"
}
}
}
],
"minimum_should_match": 1
}
}
]
}
}
}
### Check that it is created ### Check that it is created
GET https://localhost:9200/_cat/indices GET https://localhost:9200/_cat/indices
Authorization: Basic admin admin
Content-Type: application/json
### GET index info ### GET index info
GET https://localhost:9200/drive_files GET https://localhost:9200/user
Authorization: Basic admin admin
Content-Type: application/json
### GET data to the index ### GET data to the index
GET https://localhost:9200/drive_files/_doc/8c03c5a1-0146-11ee-82d9-f503f8a58e9d GET https://localhost:9200/drive_files/_doc/8c03c5a1-0146-11ee-82d9-f503f8a58e9d