Add shortcut metadata to data models & CommandMenu (#7977)

Resolves https://github.com/twentyhq/twenty/issues/7503

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Florian Liebig 2024-10-25 11:38:30 +02:00 committed by GitHub
parent 7edfa61571
commit bf2ba25a6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 80 additions and 13 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -27,11 +27,17 @@ export const GotoHotkeysEffectsProvider = () => {
),
});
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => (
<GoToHotkeyItemEffect
key={`go-to-hokey-item-${objectMetadataItem.id}`}
hotkey={objectMetadataItem.namePlural[0]}
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
/>
));
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => {
if (!objectMetadataItem.shortcut) {
return null;
}
return (
<GoToHotkeyItemEffect
key={`go-to-hokey-item-${objectMetadataItem.id}`}
hotkey={objectMetadataItem.shortcut}
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
/>
);
});
};

View File

@ -125,6 +125,7 @@ describe('useCommandMenu', () => {
labelSingular: 'Task',
labelPlural: 'Tasks',
shouldSyncLabelAndName: true,
shortcut: 'T',
description: 'A task',
icon: 'IconCheckbox',
isCustom: false,

View File

@ -83,8 +83,8 @@ export const useCommandMenu = () => {
to: `/objects/${item.namePlural}`,
label: `Go to ${item.labelPlural}`,
type: CommandType.Navigate,
firstHotKey: 'G',
secondHotKey: item.labelPlural[0],
firstHotKey: item.shortcut ? 'G' : undefined,
secondHotKey: item.shortcut,
Icon: ALL_ICONS[
(item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight'
],

View File

@ -24,6 +24,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
updatedAt
labelIdentifierFieldMetadataId
imageIdentifierFieldMetadataId
shortcut
shouldSyncLabelAndName
indexMetadatas(paging: { first: 100 }) {
edges {

View File

@ -24,6 +24,8 @@ export const query = gql`
updatedAt
labelIdentifierFieldMetadataId
imageIdentifierFieldMetadataId
shortcut
shouldSyncLabelAndName
fields(paging: { first: 1000 }, filter: $fieldFilter) {
edges {
node {

View File

@ -26,5 +26,6 @@ export const objectMetadataItemSchema = z.object({
namePlural: camelCaseStringSchema,
nameSingular: camelCaseStringSchema,
updatedAt: z.string().datetime(),
shortcut: z.string().nullable().optional(),
shouldSyncLabelAndName: z.boolean(),
}) satisfies z.ZodType<ObjectMetadataItem>;

View File

@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddObjectShortcut1729676165199 implements MigrationInterface {
name = 'AddObjectShortcut1729676165199';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."objectMetadata" ADD "shortcut" character varying`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_objectMetadata_shortcut_upper_workspace" ON "metadata"."objectMetadata" (UPPER("shortcut"), "workspaceId")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "metadata"."IDX_objectMetadata_shortcut_upper_workspace"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."objectMetadata" DROP COLUMN "shortcut"`,
);
}
}

View File

@ -51,6 +51,11 @@ export class CreateObjectInput {
@Field({ nullable: true })
icon?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
shortcut?: string;
@HideField()
dataSourceId: string;

View File

@ -53,6 +53,9 @@ export class ObjectMetadataDTO {
@Field({ nullable: true })
icon: string;
@Field({ nullable: true })
shortcut: string;
@FilterableField()
isCustom: boolean;

View File

@ -47,6 +47,11 @@ export class UpdateObjectPayload {
@Field({ nullable: true })
icon?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
shortcut?: string;
@IsBoolean()
@IsOptional()
@Field({ nullable: true })

View File

@ -69,6 +69,9 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
@Column({ default: true })
isAuditLogged: boolean;
@Column({ nullable: true })
shortcut: string;
@Column({ nullable: true, type: 'uuid' })
labelIdentifierFieldMetadataId?: string | null;

View File

@ -10,6 +10,7 @@ interface WorkspaceEntityOptions {
labelPlural: string;
description?: string;
icon?: string;
shortcut?: string;
labelIdentifierStandardId?: string;
imageIdentifierStandardId?: string;
}
@ -44,6 +45,7 @@ export function WorkspaceEntity(
options.labelIdentifierStandardId ?? BASE_OBJECT_STANDARD_FIELD_IDS.id,
imageIdentifierStandardId: options.imageIdentifierStandardId ?? null,
icon: options.icon,
shortcut: options.shortcut,
isAuditLogged,
isSystem,
gate,

View File

@ -36,6 +36,11 @@ export interface WorkspaceEntityMetadataArgs {
*/
readonly icon?: string;
/**
* Entity shortcut.
*/
readonly shortcut?: string;
/**
* Is audit logged.
*/

View File

@ -54,6 +54,7 @@ export const SEARCH_FIELDS_FOR_COMPANY: FieldTypeAndNameMetadata[] = [
labelPlural: 'Companies',
description: 'A company',
icon: 'IconBuildingSkyscraper',
shortcut: 'C',
labelIdentifierStandardId: COMPANY_STANDARD_FIELD_IDS.name,
})
export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {

View File

@ -44,6 +44,7 @@ export const SEARCH_FIELDS_FOR_NOTES: FieldTypeAndNameMetadata[] = [
labelPlural: 'Notes',
description: 'A note',
icon: 'IconNotes',
shortcut: 'N',
labelIdentifierStandardId: NOTE_STANDARD_FIELD_IDS.title,
})
export class NoteWorkspaceEntity extends BaseWorkspaceEntity {

View File

@ -50,6 +50,7 @@ export const SEARCH_FIELDS_FOR_OPPORTUNITY: FieldTypeAndNameMetadata[] = [
labelPlural: 'Opportunities',
description: 'An opportunity',
icon: 'IconTargetArrow',
shortcut: 'O',
labelIdentifierStandardId: OPPORTUNITY_STANDARD_FIELD_IDS.name,
})
@WorkspaceIsNotAuditLogged()

View File

@ -59,6 +59,7 @@ export const SEARCH_FIELDS_FOR_PERSON: FieldTypeAndNameMetadata[] = [
labelPlural: 'People',
description: 'A person',
icon: 'IconUser',
shortcut: 'P',
labelIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.name,
imageIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.avatarUrl,
})

View File

@ -31,6 +31,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
labelPlural: 'Tasks',
description: 'A task',
icon: 'IconCheckbox',
shortcut: 'T',
labelIdentifierStandardId: TASK_STANDARD_FIELD_IDS.title,
})
export class TaskWorkspaceEntity extends BaseWorkspaceEntity {

View File

@ -55,6 +55,7 @@ const WorkflowStatusOptions = [
labelPlural: 'Workflows',
description: 'A workflow',
icon: 'IconSettingsAutomation',
shortcut: 'W',
labelIdentifierStandardId: WORKFLOW_STANDARD_FIELD_IDS.name,
})
@WorkspaceGate({