Fixes Empty Label Identifer Preview in Settings/DataModel/Object/Edit (#6370)

fixes #6143

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Faisal-imtiyaz123
2024-08-08 22:00:02 +05:30
committed by GitHub
parent 320742cdea
commit b4e2ada3b0
11 changed files with 149 additions and 85 deletions

View File

@ -96,7 +96,10 @@ export const SettingsDataModelFieldPreview = ({
return ( return (
<> <>
{previewRecord ? ( {previewRecord ? (
<SettingsDataModelSetRecordEffect record={previewRecord} /> <SettingsDataModelSetRecordEffect
fieldName={fieldName}
record={previewRecord}
/>
) : ( ) : (
<SettingsDataModelSetFieldValueEffect <SettingsDataModelSetFieldValueEffect
recordId={recordId} recordId={recordId}

View File

@ -1,8 +1,10 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useSetRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { useSetRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { previewRecordIdState } from '@/settings/data-model/fields/preview/states/previewRecordIdState';
import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from '~/utils/isDefined';
type SettingsDataModelSetFieldValueEffectProps = { type SettingsDataModelSetFieldValueEffectProps = {
recordId: string; recordId: string;
@ -15,19 +17,43 @@ export const SettingsDataModelSetFieldValueEffect = ({
fieldName, fieldName,
value, value,
}: SettingsDataModelSetFieldValueEffectProps) => { }: SettingsDataModelSetFieldValueEffectProps) => {
const previewRecordId = useRecoilValue(previewRecordIdState);
const upsertedPreviewRecord = useRecoilValue(
recordStoreFamilyState(previewRecordId ?? ''),
);
const setFieldValue = useSetRecoilState( const setFieldValue = useSetRecoilState(
recordStoreFamilySelector({ recordStoreFamilySelector({
recordId, recordId,
fieldName, fieldName,
}), }),
); );
const setRecordFieldValue = useSetRecordFieldValue(); const setRecordFieldValue = useSetRecordFieldValue();
useEffect(() => { useEffect(() => {
setFieldValue(value); if (
setRecordFieldValue(recordId, fieldName, value); isDefined(upsertedPreviewRecord) &&
}, [value, setFieldValue, setRecordFieldValue, recordId, fieldName]); !!upsertedPreviewRecord[fieldName]
) {
setFieldValue(upsertedPreviewRecord[fieldName]);
setRecordFieldValue(
recordId,
fieldName,
upsertedPreviewRecord[fieldName],
);
} else {
setFieldValue(value);
setRecordFieldValue(recordId, fieldName, value);
}
}, [
value,
setFieldValue,
setRecordFieldValue,
recordId,
fieldName,
upsertedPreviewRecord,
]);
return null; return null;
}; };

View File

@ -1,20 +1,35 @@
import { useEffect } from 'react'; import { useSetRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { previewRecordIdState } from '@/settings/data-model/fields/preview/states/previewRecordIdState';
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
type SettingsDataModelSetRecordEffectProps = { type SettingsDataModelSetRecordEffectProps = {
record: ObjectRecord; record: ObjectRecord;
fieldName: string;
}; };
export const SettingsDataModelSetRecordEffect = ({ export const SettingsDataModelSetRecordEffect = ({
record, record,
fieldName,
}: SettingsDataModelSetRecordEffectProps) => { }: SettingsDataModelSetRecordEffectProps) => {
const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore(); const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore();
const setRecordFieldValue = useSetRecordFieldValue();
const setPreviewRecordId = useSetRecoilState(previewRecordIdState);
useEffect(() => { useEffect(() => {
upsertRecordsInStore([record]); upsertRecordsInStore([record]);
}, [record, upsertRecordsInStore]); setRecordFieldValue(record.id, fieldName, record[fieldName]);
setPreviewRecordId(record.id);
}, [
record,
upsertRecordsInStore,
setRecordFieldValue,
fieldName,
setPreviewRecordId,
]);
return null; return null;
}; };

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const previewRecordIdState = createState<string | null>({
key: 'previewRecordId',
defaultValue: null,
});

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import styled from '@emotion/styled'; import { IconCircleOff, isDefined, useIcons } from 'twenty-ui';
import { IconCircleOff, useIcons } from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes'; import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes';
@ -22,6 +22,7 @@ export type SettingsDataModelObjectIdentifiersFormValues = z.infer<
type SettingsDataModelObjectIdentifiersFormProps = { type SettingsDataModelObjectIdentifiersFormProps = {
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
defaultLabelIdentifierFieldMetadataId: string;
}; };
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -31,6 +32,7 @@ const StyledContainer = styled.div`
export const SettingsDataModelObjectIdentifiersForm = ({ export const SettingsDataModelObjectIdentifiersForm = ({
objectMetadataItem, objectMetadataItem,
defaultLabelIdentifierFieldMetadataId,
}: SettingsDataModelObjectIdentifiersFormProps) => { }: SettingsDataModelObjectIdentifiersFormProps) => {
const { control } = const { control } =
useFormContext<SettingsDataModelObjectIdentifiersFormValues>(); useFormContext<SettingsDataModelObjectIdentifiersFormValues>();
@ -58,7 +60,6 @@ export const SettingsDataModelObjectIdentifiersForm = ({
label: 'None', label: 'None',
value: null, value: null,
}; };
return ( return (
<StyledContainer> <StyledContainer>
{[ {[
@ -77,7 +78,13 @@ export const SettingsDataModelObjectIdentifiersForm = ({
key={fieldName} key={fieldName}
name={fieldName} name={fieldName}
control={control} control={control}
defaultValue={objectMetadataItem[fieldName]} defaultValue={
fieldName === 'labelIdentifierFieldMetadataId'
? isDefined(objectMetadataItem[fieldName])
? objectMetadataItem[fieldName]
: defaultLabelIdentifierFieldMetadataId
: objectMetadataItem[fieldName]
}
render={({ field: { onBlur, onChange, value } }) => { render={({ field: { onBlur, onChange, value } }) => {
return ( return (
<Select <Select

View File

@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
@ -78,6 +78,9 @@ export const SettingsDataModelObjectSettingsFormCard = ({
<CardContent> <CardContent>
<SettingsDataModelObjectIdentifiersForm <SettingsDataModelObjectIdentifiersForm
objectMetadataItem={objectMetadataItem} objectMetadataItem={objectMetadataItem}
defaultLabelIdentifierFieldMetadataId={
labelIdentifierFieldMetadataItem?.id
}
/> />
</CardContent> </CardContent>
</Card> </Card>

View File

@ -1,6 +1,6 @@
import { useMemo, useRef, useState } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useMemo, useRef, useState } from 'react';
import { IconChevronDown, IconComponent } from 'twenty-ui'; import { IconChevronDown, IconComponent } from 'twenty-ui';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';

View File

@ -1,3 +1,5 @@
/* eslint-disable react/jsx-props-no-spreading */
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import pick from 'lodash.pick'; import pick from 'lodash.pick';
import { useEffect } from 'react'; import { useEffect } from 'react';
@ -28,6 +30,7 @@ import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
const objectEditFormSchema = z const objectEditFormSchema = z
.object({}) .object({})
@ -103,66 +106,67 @@ export const SettingsObjectEdit = () => {
}; };
return ( return (
// eslint-disable-next-line react/jsx-props-no-spreading <RecordFieldValueSelectorContextProvider>
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer Icon={IconSettings} title="Settings"> <SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer> <SettingsPageContainer>
<SettingsHeaderContainer> <SettingsHeaderContainer>
<Breadcrumb <Breadcrumb
links={[ links={[
{ {
children: 'Objects', children: 'Objects',
href: settingsObjectsPagePath, href: settingsObjectsPagePath,
}, },
{ {
children: activeObjectMetadataItem.labelPlural, children: activeObjectMetadataItem.labelPlural,
href: `${settingsObjectsPagePath}/${objectSlug}`, href: `${settingsObjectsPagePath}/${objectSlug}`,
}, },
{ children: 'Edit' }, { children: 'Edit' },
]} ]}
/>
{activeObjectMetadataItem.isCustom && (
<SaveAndCancelButtons
isSaveDisabled={!canSave}
isCancelDisabled={isSubmitting}
onCancel={() =>
navigate(`${settingsObjectsPagePath}/${objectSlug}`)
}
onSave={formConfig.handleSubmit(handleSave)}
/> />
)} {activeObjectMetadataItem.isCustom && (
</SettingsHeaderContainer> <SaveAndCancelButtons
<Section> isSaveDisabled={!canSave}
<H2Title isCancelDisabled={isSubmitting}
title="About" onCancel={() =>
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms." navigate(`${settingsObjectsPagePath}/${objectSlug}`)
/> }
<SettingsDataModelObjectAboutForm onSave={formConfig.handleSubmit(handleSave)}
disabled={!activeObjectMetadataItem.isCustom} />
disableNameEdit )}
objectMetadataItem={activeObjectMetadataItem} </SettingsHeaderContainer>
/> <Section>
</Section> <H2Title
<Section> title="About"
<H2Title description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
title="Settings" />
description="Choose the fields that will identify your records" <SettingsDataModelObjectAboutForm
/> disabled={!activeObjectMetadataItem.isCustom}
<SettingsDataModelObjectSettingsFormCard disableNameEdit
objectMetadataItem={activeObjectMetadataItem} objectMetadataItem={activeObjectMetadataItem}
/> />
</Section> </Section>
<Section> <Section>
<H2Title title="Danger zone" description="Deactivate object" /> <H2Title
<Button title="Settings"
Icon={IconArchive} description="Choose the fields that will identify your records"
title="Deactivate" />
size="small" <SettingsDataModelObjectSettingsFormCard
onClick={handleDisable} objectMetadataItem={activeObjectMetadataItem}
/> />
</Section> </Section>
</SettingsPageContainer> <Section>
</SubMenuTopBarContainer> <H2Title title="Danger zone" description="Deactivate object" />
</FormProvider> <Button
Icon={IconArchive}
title="Deactivate"
size="small"
onClick={handleDisable}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
</RecordFieldValueSelectorContextProvider>
); );
}; };

View File

@ -18,9 +18,9 @@ import {
SaveOptions, SaveOptions,
UpdateResult, UpdateResult,
} from 'typeorm'; } from 'typeorm';
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';

View File

@ -1,17 +1,17 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { EntityManager, ILike } from 'typeorm';
import uniqBy from 'lodash.uniqby'; import uniqBy from 'lodash.uniqby';
import { EntityManager, ILike } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity'; import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util'; import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util';
import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util'; import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeDisplayName } from 'src/utils/compute-display-name'; import { computeDisplayName } from 'src/utils/compute-display-name';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
type CompanyToCreate = { type CompanyToCreate = {
domainName: string; domainName: string;

View File

@ -3,13 +3,13 @@ import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm'; import { EntityManager } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/contact-creation-manager/utils/get-first-name-and-last-name-from-handle-and-display-name.util'; import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/contact-creation-manager/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { computeDisplayName } from 'src/utils/compute-display-name'; import { computeDisplayName } from 'src/utils/compute-display-name';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
type ContactToCreate = { type ContactToCreate = {
handle: string; handle: string;