Fix Account Owner Dropdown to Display Team Member Profile Pictures #11370 (#11385)

#11370  & #11402
### Changes made:
1. Updated search.service.ts to properly handle workspace member avatar
and Person Avatar URLs with authentication tokens
2. Integrated FileService for token generation
3. Added FileModule to SearchModule for dependency injection

### Implementation details:
- Used getImageUrlWithToken to append authentication tokens to avatar
URLs specifically for workspace members

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
This commit is contained in:
Vaibhav Devere
2025-04-16 21:16:37 +05:30
committed by GitHub
parent 4d78f5f97f
commit e1b99a6f39
6 changed files with 29 additions and 6 deletions

View File

@ -27,7 +27,7 @@ export class FileService {
});
}
async encodeFileToken(payloadToEncode: Record<string, any>) {
encodeFileToken(payloadToEncode: Record<string, any>) {
const fileTokenExpiresIn = this.twentyConfigService.get(
'FILE_TOKEN_EXPIRES_IN',
);

View File

@ -1,5 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/search/__mocks__/mockObjectMetadataItemsWithFieldMaps';
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
@ -8,7 +9,7 @@ describe('SearchService', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SearchService],
providers: [SearchService, { provide: FileService, useValue: {} }],
}).compile();
service = module.get<SearchService>(SearchService);

View File

@ -1,12 +1,13 @@
import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { SearchResolver } from 'src/engine/core-modules/search/search.resolver';
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@Module({
imports: [WorkspaceCacheStorageModule, FeatureFlagModule],
imports: [WorkspaceCacheStorageModule, FeatureFlagModule, FileModule],
providers: [SearchResolver, SearchService],
})
export class SearchModule {}

View File

@ -118,6 +118,7 @@ export class SearchResolver {
return this.searchService.computeSearchObjectResults(
allRecordsWithObjectMetadataItems,
limit,
workspace.id,
);
}
}

View File

@ -1,13 +1,14 @@
import { Injectable } from '@nestjs/common';
import { Brackets, ObjectLiteral } from 'typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { getLogoUrlFromDomainName } from 'twenty-shared/utils';
import { Brackets, ObjectLiteral } from 'typeorm';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { RESULTS_LIMIT_BY_OBJECT_WITHOUT_SEARCH_TERMS } from 'src/engine/core-modules/search/constants/results-limit-by-object-without-search-terms';
import { STANDARD_OBJECTS_BY_PRIORITY_RANK } from 'src/engine/core-modules/search/constants/standard-objects-by-priority-rank';
import { ObjectRecordFilterInput } from 'src/engine/core-modules/search/dtos/object-record-filter-input';
@ -24,6 +25,8 @@ import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.
@Injectable()
export class SearchService {
constructor(private readonly fileService: FileService) {}
filterObjectMetadataItems({
objectMetadataItemWithFieldMaps,
includedObjectNameSingulars,
@ -194,9 +197,18 @@ export class SearchService {
].name;
}
private getImageUrlWithToken(avatarUrl: string, workspaceId: string): string {
const avatarUrlToken = this.fileService.encodeFileToken({
workspaceId,
});
return `${avatarUrl}?token=${avatarUrlToken}`;
}
getImageIdentifierValue(
record: ObjectRecord,
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
workspaceId: string,
): string {
const imageIdentifierField =
this.getImageIdentifierColumn(objectMetadataItem);
@ -205,12 +217,15 @@ export class SearchService {
return getLogoUrlFromDomainName(record.domainNamePrimaryLinkUrl) || '';
}
return imageIdentifierField ? record[imageIdentifierField] : '';
return imageIdentifierField
? this.getImageUrlWithToken(record[imageIdentifierField], workspaceId)
: '';
}
computeSearchObjectResults(
recordsWithObjectMetadataItems: RecordsWithObjectMetadataItem[],
limit: number,
workspaceId: string,
) {
const searchRecords = recordsWithObjectMetadataItems.flatMap(
({ objectMetadataItem, records }) => {
@ -219,7 +234,11 @@ export class SearchService {
recordId: record.id,
objectNameSingular: objectMetadataItem.nameSingular,
label: this.getLabelIdentifierValue(record, objectMetadataItem),
imageUrl: this.getImageIdentifierValue(record, objectMetadataItem),
imageUrl: this.getImageIdentifierValue(
record,
objectMetadataItem,
workspaceId,
),
tsRankCD: record.tsRankCD,
tsRank: record.tsRank,
};

View File

@ -80,6 +80,7 @@ export const SEARCH_FIELDS_FOR_WORKSPACE_MEMBER: FieldTypeAndNameMetadata[] = [
description: msg`A workspace member`,
icon: STANDARD_OBJECT_ICONS.workspaceMember,
labelIdentifierStandardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.name,
imageIdentifierStandardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.avatarUrl,
})
@WorkspaceIsSystem()
@WorkspaceIsNotAuditLogged()