Update Seeds while pre-fi

lling a new workspace
This commit is contained in:
Charles Bochet 2023-11-17 21:54:32 +01:00
parent e90beef91f
commit aa2596c572
66 changed files with 476 additions and 668 deletions

View File

@ -415,6 +415,11 @@ export type DeleteOneObjectInput = {
id: Scalars['ID']['input'];
};
export type DeleteOneRelationInput = {
/** The id of the record to delete. */
id: Scalars['ID']['input'];
};
export type Favorite = {
__typename?: 'Favorite';
company?: Maybe<Company>;
@ -463,6 +468,7 @@ export enum FieldMetadataType {
Date = 'DATE',
Email = 'EMAIL',
Enum = 'ENUM',
FullName = 'FULL_NAME',
Link = 'LINK',
Number = 'NUMBER',
Phone = 'PHONE',
@ -496,6 +502,7 @@ export type Mutation = {
createOneRelation: Relation;
deleteOneField: FieldDeleteResponse;
deleteOneObject: ObjectDeleteResponse;
deleteOneRelation: RelationDeleteResponse;
updateOneField: Field;
updateOneObject: Object;
};
@ -526,6 +533,11 @@ export type MutationDeleteOneObjectArgs = {
};
export type MutationDeleteOneRelationArgs = {
input: DeleteOneRelationInput;
};
export type MutationUpdateOneFieldArgs = {
input: UpdateOneFieldInput;
};
@ -711,6 +723,18 @@ export type RelationConnection = {
totalCount: Scalars['Int']['output'];
};
export type RelationDeleteResponse = {
__typename?: 'RelationDeleteResponse';
createdAt?: Maybe<Scalars['DateTime']['output']>;
fromFieldMetadataId?: Maybe<Scalars['String']['output']>;
fromObjectMetadataId?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
relationType?: Maybe<RelationMetadataType>;
toFieldMetadataId?: Maybe<Scalars['String']['output']>;
toObjectMetadataId?: Maybe<Scalars['String']['output']>;
updatedAt?: Maybe<Scalars['DateTime']['output']>;
};
/** Type of the relation */
export enum RelationMetadataType {
ManyToMany = 'MANY_TO_MANY',

View File

@ -1336,6 +1336,7 @@ export enum FieldMetadataType {
Date = 'DATE',
Email = 'EMAIL',
Enum = 'ENUM',
FullName = 'FULL_NAME',
Link = 'LINK',
Number = 'NUMBER',
Phone = 'PHONE',
@ -1443,6 +1444,7 @@ export type Mutation = {
deleteOneField: FieldDeleteResponse;
deleteOneObject: ObjectDeleteResponse;
deleteOnePipelineStage: PipelineStage;
deleteOneRelation: RelationDeleteResponse;
deleteOneWebHook: WebHook;
deleteUserAccount: User;
deleteUserV2: UserV2;
@ -2626,6 +2628,18 @@ export type RelationConnection = {
totalCount: Scalars['Int'];
};
export type RelationDeleteResponse = {
__typename?: 'RelationDeleteResponse';
createdAt?: Maybe<Scalars['DateTime']>;
fromFieldMetadataId?: Maybe<Scalars['String']>;
fromObjectMetadataId?: Maybe<Scalars['String']>;
id?: Maybe<Scalars['ID']>;
relationType?: Maybe<RelationMetadataType>;
toFieldMetadataId?: Maybe<Scalars['String']>;
toObjectMetadataId?: Maybe<Scalars['String']>;
updatedAt?: Maybe<Scalars['DateTime']>;
};
/** Type of the relation */
export enum RelationMetadataType {
ManyToMany = 'MANY_TO_MANY',

View File

@ -67,7 +67,7 @@ export const CommentHeader = ({ comment, actionBar }: CommentHeaderProps) => {
const showDate = beautifiedCreatedAt !== '';
const author = comment.author;
const authorName = author.firstName + ' ' + author.lastName;
const authorName = author.name.firstName + ' ' + author.name.lastName;
const avatarUrl = author.avatarUrl;
const commentId = comment.id;

View File

@ -10,8 +10,10 @@ export const mockComment: Pick<
body: 'Hello, this is a comment.',
author: {
id: 'fake_comment_1_author_uuid',
firstName: 'Jony' ?? '',
lastName: 'Ive' ?? '',
name: {
firstName: 'Jony' ?? '',
lastName: 'Ive' ?? '',
},
avatarUrl: null,
},
createdAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '',
@ -26,8 +28,10 @@ export const mockCommentWithLongValues: Pick<
body: 'Hello, this is a comment. Hello, this is a comment. Hello, this is a comment. Hello, this is a comment. Hello, this is a comment. Hello, this is a comment.',
author: {
id: 'fake_comment_1_author_uuid',
firstName: 'Jony' ?? '',
lastName: 'Ive' ?? '',
name: {
firstName: 'Jony' ?? '',
lastName: 'Ive' ?? '',
},
avatarUrl: null,
},
createdAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '',

View File

@ -14,10 +14,7 @@ import {
export type ActivityAssigneePickerProps = {
activity: Pick<Activity, 'id'> & {
accountOwner?: Pick<
WorkspaceMember,
'id' | 'firstName' | 'lastName'
> | null;
accountOwner?: Pick<WorkspaceMember, 'id' | 'name'> | null;
};
onSubmit?: () => void;
onCancel?: () => void;

View File

@ -60,10 +60,7 @@ type ActivityEditorProps = {
> & {
comments?: Array<Comment> | null;
} & {
assignee?: Pick<
WorkspaceMember,
'id' | 'firstName' | 'lastName' | 'avatarUrl'
> | null;
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
} & {
activityTargets?: Array<
Pick<ActivityTarget, 'id' | 'companyId' | 'personId'>

View File

@ -13,10 +13,7 @@ import { Company, User } from '~/generated/graphql';
type ActivityAssigneeEditableFieldProps = {
activity: Pick<Company, 'id' | 'accountOwnerId'> & {
assignee?: Pick<
WorkspaceMember,
'id' | 'firstName' | 'lastName' | 'avatarUrl'
> | null;
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
};
};

View File

@ -7,6 +7,7 @@ import { Activity } from '@/activities/types/Activity';
import { IconNotes } from '@/ui/display/icon';
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import {
beautifyExactDateTime,
beautifyPastDateRelativeToNow,
@ -127,11 +128,8 @@ type TimelineActivityProps = {
| 'type'
| 'comments'
| 'dueAt'
> & { author: Pick<Activity['author'], 'firstName' | 'lastName'> } & {
assignee?: Pick<
Activity['author'],
'id' | 'firstName' | 'lastName' | 'avatarUrl'
> | null;
> & { author: Pick<WorkspaceMember, 'name'> } & {
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
};
};
@ -151,7 +149,9 @@ export const TimelineActivity = ({ activity }: TimelineActivityProps) => {
</StyledIconContainer>
<StyledItemTitleContainer>
<span>
{activity.author.firstName + ' ' + activity.author.lastName}
{activity.author.name.firstName +
' ' +
activity.author.name.lastName}
</span>
created a {activity.type.toLowerCase()}
</StyledItemTitleContainer>

View File

@ -9,10 +9,7 @@ import { beautifyExactDate } from '~/utils/date-utils';
type TimelineActivityCardFooterProps = {
activity: Pick<Activity, 'id' | 'dueAt' | 'comments'> & {
assignee?: Pick<
WorkspaceMember,
'id' | 'firstName' | 'lastName' | 'avatarUrl'
> | null;
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
};
};
@ -48,9 +45,9 @@ export const TimelineActivityCardFooter = ({
<UserChip
id={activity.assignee.id}
name={
activity.assignee.firstName +
activity.assignee.name.firstName +
' ' +
activity.assignee.lastName ?? ''
activity.assignee.name.lastName ?? ''
}
pictureUrl={activity.assignee.avatarUrl ?? ''}
/>

View File

@ -15,12 +15,9 @@ export type Activity = {
type: ActivityType;
title: string;
body: string;
author: Pick<WorkspaceMember, 'id' | 'firstName' | 'lastName' | 'avatarUrl'>;
author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
authorId: string;
assignee: Pick<
WorkspaceMember,
'id' | 'firstName' | 'lastName' | 'avatarUrl'
> | null;
assignee: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
assigneeId: string | null;
comments: Comment[];
};

View File

@ -6,5 +6,5 @@ export type Comment = {
body: string;
updatedAt: string;
activityId: string;
author: Pick<WorkspaceMember, 'id' | 'firstName' | 'lastName' | 'avatarUrl'>;
author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
};

View File

@ -169,8 +169,10 @@ export const useAuth = () => {
mutation: CREATE_ONE_WORKSPACE_MEMBER_V2,
variables: {
input: {
firstName: user.firstName ?? '',
lastName: user.lastName ?? '',
name: {
firstName: user.firstName ?? '',
lastName: user.lastName ?? '',
},
colorScheme: 'Light',
userId: user.id,
allowImpersonation: true,

View File

@ -25,7 +25,10 @@ export const getOnboardingStatus = (
if (!currentWorkspace?.displayName) {
return OnboardingStatus.OngoingWorkspaceCreation;
}
if (!currentWorkspaceMember.firstName || !currentWorkspaceMember.lastName) {
if (
!currentWorkspaceMember.name.firstName ||
!currentWorkspaceMember.name.lastName
) {
return OnboardingStatus.OngoingProfileCreation;
}

View File

@ -1,4 +1,7 @@
import { Company, Favorite, User } from '../../../../generated/graphql';
import { Favorite } from '@/favorites/types/Favorite';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { Company } from '../../../../generated/graphql';
type MockedCompany = Pick<
Company,
@ -15,16 +18,7 @@ type MockedCompany = Pick<
| 'idealCustomerProfile'
| '_activityCount'
> & {
accountOwner: Pick<
User,
| 'id'
| 'email'
| 'displayName'
| 'avatarUrl'
| '__typename'
| 'firstName'
| 'lastName'
> | null;
accountOwner: Pick<WorkspaceMember, 'id' | 'avatarUrl' | 'name'> | null;
} & { Favorite: Pick<Favorite, 'id'> | null };
export const mockedCompaniesData: Array<MockedCompany> = [
@ -42,13 +36,12 @@ export const mockedCompaniesData: Array<MockedCompany> = [
Favorite: null,
_activityCount: 0,
accountOwner: {
email: 'charles@test.com',
displayName: 'Charles Test',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: null,
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
__typename: 'User',
},
__typename: 'Company',
},

View File

@ -91,6 +91,14 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
currencyCode
}
`;
} else if (fieldType === 'FULL_NAME') {
return `
${field.name}
{
firstName
lastName
}
`;
}
};

View File

@ -91,9 +91,10 @@ export const RecordShowPage = () => {
if (isFavorite) deleteFavorite(object?.id);
else {
const additionalData =
objectNameSingular === 'peopleV2'
objectNameSingular === 'personV2'
? {
labelIdentifier: object.firstName + ' ' + object.lastName,
labelIdentifier:
object.name.firstName + ' ' + object.name.lastName,
avatarUrl: object.avatarUrl,
avatarType: 'rounded',
link: `/object/personV2/${object.id}`,
@ -114,11 +115,16 @@ export const RecordShowPage = () => {
if (!object) return <></>;
const pageName =
objectNameSingular === 'personV2'
? object.name.firstName + ' ' + object.name.lastName
: object.name;
return (
<PageContainer>
<PageTitle title={object.name || 'No Name'} />
<PageTitle title={pageName} />
<PageHeader
title={object.name ?? ''}
title={pageName ?? ''}
hasBackButton
Icon={IconBuildingSkyscraper}
>

View File

@ -4,8 +4,10 @@ export const CREATE_ONE_WORKSPACE_MEMBER_V2 = gql`
mutation CreateOneWorkspaceMemberV2($input: WorkspaceMemberV2CreateInput!) {
createWorkspaceMemberV2(data: $input) {
id
firstName
lastName
name {
firstName
lastName
}
}
}
`;

View File

@ -6,8 +6,10 @@ export const FIND_ONE_WORKSPACE_MEMBER_V2 = gql`
edges {
node {
id
firstName
lastName
name {
firstName
lastName
}
colorScheme
avatarUrl
locale

View File

@ -29,7 +29,6 @@ export const useRecordTableContextMenuEntries = () => {
const { scopeId: objectNamePlural, resetTableRowSelection } =
useRecordTable();
const { data } = useGetFavoritesQuery();
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
objectNamePlural,
});

View File

@ -31,7 +31,7 @@ export const useGenerateFindManyCustomObjectsQuery = ({
node {
id
${objectMetadataItem.fields
.map(mapFieldMetadataToGraphQLQuery)
.map((field) => mapFieldMetadataToGraphQLQuery(field))
.join('\n')}
}
cursor

View File

@ -16,7 +16,7 @@ const PeopleTableEffect = () => {
setViewObjectMetadataId,
} = useView();
const { setAvailableTableColumns, setTableColumns } = useRecordTable();
const { setAvailableTableColumns } = useRecordTable();
useEffect(() => {
setAvailableSortDefinitions?.(personTableSortDefinitions);
@ -31,7 +31,6 @@ const PeopleTableEffect = () => {
setAvailableFilterDefinitions,
setAvailableSortDefinitions,
setAvailableTableColumns,
setTableColumns,
setViewObjectMetadataId,
setViewType,
]);

View File

@ -62,13 +62,26 @@ export const useFilteredSearchEntityQueryV2 = ({
}
return {
or: fieldNames.map((fieldName) => ({
[fieldName]: {
like: `%${filter}%`,
// TODO: fix mode
// mode: QueryMode.Insensitive,
},
})),
or: fieldNames.map((fieldName) => {
const fieldNameParts = fieldName.split('.');
if (fieldNameParts.length > 1) {
// Composite field
return {
[fieldNameParts[0]]: {
[fieldNameParts[1]]: {
ilike: `%${filter}%`,
},
},
};
}
return {
[fieldName]: {
ilike: `%${filter}%`,
},
};
}),
};
})
.filter(isDefined);

View File

@ -9,6 +9,7 @@ import {
IconPhone,
IconPlug,
IconTextSize,
IconUser,
} from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { CurrencyCode, FieldMetadataType } from '~/generated-metadata/graphql';
@ -64,5 +65,6 @@ export const dataTypes: Record<
Icon: IconNumbers,
defaultValue: 50,
},
[FieldMetadataType.FullName]: { label: 'Full Name', Icon: IconUser },
[FieldMetadataType.Enum]: { label: 'Enum', Icon: IconPlug },
};

View File

@ -34,10 +34,10 @@ export const NameFields = ({
);
const [firstName, setFirstName] = useState(
currentWorkspaceMember?.firstName ?? '',
currentWorkspaceMember?.name.firstName ?? '',
);
const [lastName, setLastName] = useState(
currentWorkspaceMember?.lastName ?? '',
currentWorkspaceMember?.name.lastName ?? '',
);
const { updateOneObject, objectNotFoundInMetadata } =
@ -65,15 +65,19 @@ export const NameFields = ({
await updateOneObject({
idToUpdate: currentWorkspaceMember?.id,
input: {
firstName,
lastName,
name: {
firstName: firstName,
lastName: lastName,
},
},
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
firstName,
lastName,
name: {
firstName,
lastName,
},
});
}
} catch (error) {
@ -87,8 +91,8 @@ export const NameFields = ({
}
if (
currentWorkspaceMember.firstName !== firstName ||
currentWorkspaceMember.lastName !== lastName
currentWorkspaceMember.name.firstName !== firstName ||
currentWorkspaceMember.name.lastName !== lastName
) {
debouncedUpdate();
}

View File

@ -40,7 +40,7 @@ const SupportChat = () => {
(
chatId: string,
currentUser: Pick<User, 'email' | 'supportUserHash'>,
currentWorkspaceMember: Pick<WorkspaceMember, 'firstName' | 'lastName'>,
currentWorkspaceMember: Pick<WorkspaceMember, 'name'>,
) => {
const url = 'https://chat-assets.frontapp.com/v1/chat.bundle.js';
const script = document.querySelector(`script[src="${url}"]`);
@ -54,9 +54,9 @@ const SupportChat = () => {
useDefaultLauncher: false,
email: currentUser.email,
name:
currentWorkspaceMember.firstName +
currentWorkspaceMember.name.firstName +
' ' +
currentWorkspaceMember.lastName,
currentWorkspaceMember.name.lastName,
userHash: currentUser?.supportUserHash,
});
setIsFrontChatLoaded(true);

View File

@ -1,7 +1,9 @@
import { useContext } from 'react';
import { FullNameFieldDisplay } from '@/ui/object/field/meta-types/display/components/FullNameFieldDisplay';
import { RelationFieldDisplay } from '@/ui/object/field/meta-types/display/components/RelationFieldDisplay';
import { UuidFieldDisplay } from '@/ui/object/field/meta-types/display/components/UuidFieldDisplay';
import { isFieldFullName } from '@/ui/object/field/types/guards/isFieldFullName';
import { isFieldUuid } from '@/ui/object/field/types/guards/isFieldUuid';
import { FieldContext } from '../contexts/FieldContext';
@ -9,7 +11,6 @@ import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisp
import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay';
import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay';
import { DoubleTextChipFieldDisplay } from '../meta-types/display/components/DoubleTextChipFieldDisplay';
import { DoubleTextFieldDisplay } from '../meta-types/display/components/DoubleTextFieldDisplay';
import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay';
import { LinkFieldDisplay } from '../meta-types/display/components/LinkFieldDisplay';
import { MoneyFieldDisplay } from '../meta-types/display/components/MoneyFieldDisplay';
@ -20,7 +21,6 @@ import { URLFieldDisplay } from '../meta-types/display/components/URLFieldDispla
import { isFieldChip } from '../types/guards/isFieldChip';
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDoubleText } from '../types/guards/isFieldDoubleText';
import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip';
import { isFieldEmail } from '../types/guards/isFieldEmail';
import { isFieldLink } from '../types/guards/isFieldLink';
@ -56,14 +56,14 @@ export const FieldDisplay = () => {
<LinkFieldDisplay />
) : isFieldCurrency(fieldDefinition) ? (
<CurrencyFieldDisplay />
) : isFieldFullName(fieldDefinition) ? (
<FullNameFieldDisplay />
) : isFieldPhone(fieldDefinition) ? (
<PhoneFieldDisplay />
) : isFieldChip(fieldDefinition) ? (
<ChipFieldDisplay />
) : isFieldDoubleTextChip(fieldDefinition) ? (
<DoubleTextChipFieldDisplay />
) : isFieldDoubleText(fieldDefinition) ? (
<DoubleTextFieldDisplay />
) : (
<></>
)}

View File

@ -8,7 +8,6 @@ import { ChipFieldInput } from '../meta-types/input/components/ChipFieldInput';
import { CurrencyFieldInput } from '../meta-types/input/components/CurrencyFieldInput';
import { DateFieldInput } from '../meta-types/input/components/DateFieldInput';
import { DoubleTextChipFieldInput } from '../meta-types/input/components/DoubleTextChipFieldInput';
import { DoubleTextFieldInput } from '../meta-types/input/components/DoubleTextFieldInput';
import { EmailFieldInput } from '../meta-types/input/components/EmailFieldInput';
import { LinkFieldInput } from '../meta-types/input/components/LinkFieldInput';
import { MoneyFieldInput } from '../meta-types/input/components/MoneyFieldInput';
@ -23,7 +22,6 @@ import { isFieldBoolean } from '../types/guards/isFieldBoolean';
import { isFieldChip } from '../types/guards/isFieldChip';
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDoubleText } from '../types/guards/isFieldDoubleText';
import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip';
import { isFieldEmail } from '../types/guards/isFieldEmail';
import { isFieldLink } from '../types/guards/isFieldLink';
@ -146,14 +144,6 @@ export const FieldInput = ({
onTab={onTab}
onShiftTab={onShiftTab}
/>
) : isFieldDoubleText(fieldDefinition) ? (
<DoubleTextFieldInput
onEnter={onEnter}
onEscape={onEscape}
onClickOutside={onClickOutside}
onTab={onTab}
onShiftTab={onShiftTab}
/>
) : isFieldMoney(fieldDefinition) ? (
<MoneyFieldInput
onEnter={onEnter}

View File

@ -1,10 +0,0 @@
import { useDoubleTextField } from '../../hooks/useDoubleTextField';
import { TextDisplay } from '../content-display/components/TextDisplay';
export const DoubleTextFieldDisplay = () => {
const { firstValue, secondValue } = useDoubleTextField();
const content = [firstValue, secondValue].filter(Boolean).join(' ');
return <TextDisplay text={content} />;
};

View File

@ -0,0 +1,13 @@
import { useFullNameField } from '@/ui/object/field/meta-types/hooks/useFullNameField';
import { TextDisplay } from '../content-display/components/TextDisplay';
export const FullNameFieldDisplay = () => {
const { fieldValue } = useFullNameField();
const content = [fieldValue.firstName, fieldValue.lastName]
.filter(Boolean)
.join(' ');
return <TextDisplay text={content} />;
};

View File

@ -1,79 +0,0 @@
import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { FieldContext } from '../../../../contexts/FieldContext';
import { useDoubleTextField } from '../../../hooks/useDoubleTextField';
import { DoubleTextFieldDisplay } from '../DoubleTextFieldDisplay'; // Import your component
const DoubleTextFieldDisplayValueSetterEffect = ({
firstValue,
secondValue,
}: {
firstValue: string;
secondValue: string;
}) => {
const { setFirstValue, setSecondValue } = useDoubleTextField();
useEffect(() => {
setFirstValue(firstValue);
setSecondValue(secondValue);
}, [setFirstValue, setSecondValue, firstValue, secondValue]);
return null;
};
const meta: Meta = {
title: 'UI/Data/Field/Display/DoubleTextFieldDisplay',
decorators: [
(Story, { args }) => (
<FieldContext.Provider
value={{
entityId: '',
fieldDefinition: {
fieldMetadataId: 'double-text',
label: 'Double-Text',
type: 'DOUBLE_TEXT',
metadata: {
firstValueFieldName: 'First-text',
firstValuePlaceholder: 'First-text',
secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text',
},
},
hotkeyScope: 'hotkey-scope',
}}
>
<DoubleTextFieldDisplayValueSetterEffect
firstValue={args.firstValue}
secondValue={args.secondValue}
/>
<Story />
</FieldContext.Provider>
),
ComponentDecorator,
],
component: DoubleTextFieldDisplay,
args: {
firstValue: 'Lorem',
secondValue: 'ipsum',
},
};
export default meta;
type Story = StoryObj<typeof DoubleTextFieldDisplay>;
export const Default: Story = {};
export const Elipsis: Story = {
args: {
firstValue:
'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
secondValue: 'ipsum dolor sit amet, consectetur adipiscing elit.',
},
parameters: {
container: { width: 100 },
},
};

View File

@ -21,12 +21,12 @@ export const getEntityChipFromFieldMetadata = (
// TODO: use every
if (fieldName === 'accountOwner' && fieldValue) {
chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName;
chipValue.name = fieldValue.name.firstName + ' ' + fieldValue.name.lastName;
} else if (fieldName === 'company' && fieldValue) {
chipValue.name = fieldValue.name;
chipValue.pictureUrl = getLogoUrlFromDomainName(fieldValue.domainName);
} else if (fieldName === 'person' && fieldValue) {
chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName;
chipValue.name = fieldValue.name.firstName + ' ' + fieldValue.name.lastName;
}
return chipValue;

View File

@ -1,54 +0,0 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { FieldContext } from '../../contexts/FieldContext';
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldDoubleText } from '../../types/guards/isFieldDoubleText';
export const useDoubleTextField = () => {
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
assertFieldMetadata('DOUBLE_TEXT', isFieldDoubleText, fieldDefinition);
const [firstValue, setFirstValue] = useRecoilState<string>(
entityFieldsFamilySelector({
entityId: entityId,
fieldName: fieldDefinition.metadata.firstValueFieldName,
}),
);
const [secondValue, setSecondValue] = useRecoilState<string>(
entityFieldsFamilySelector({
entityId: entityId,
fieldName: fieldDefinition.metadata.secondValueFieldName,
}),
);
const fieldInitialValue = useFieldInitialValue();
const initialFirstValue = fieldInitialValue?.isEmpty
? ''
: fieldInitialValue?.value ?? firstValue;
const initialSecondValue = fieldInitialValue?.isEmpty
? ''
: fieldInitialValue?.value
? ''
: secondValue;
const fullValue = [firstValue, secondValue].filter(Boolean).join(' ');
return {
fieldDefinition,
secondValue,
setSecondValue,
firstValue,
initialFirstValue,
initialSecondValue,
setFirstValue,
fullValue,
hotkeyScope,
};
};

View File

@ -0,0 +1,51 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { FieldContext } from '../../contexts/FieldContext';
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
import { usePersistField } from '../../hooks/usePersistField';
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
import { FieldFullNameValue } from '../../types/FieldMetadata';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldFullName } from '../../types/guards/isFieldFullName';
import { isFieldFullNameValue } from '../../types/guards/isFieldFullNameValue';
export const useFullNameField = () => {
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
assertFieldMetadata('FULL_NAME', isFieldFullName, fieldDefinition);
const fieldName = fieldDefinition.metadata.fieldName;
const [fieldValue, setFieldValue] = useRecoilState<FieldFullNameValue>(
entityFieldsFamilySelector({
entityId: entityId,
fieldName: fieldName,
}),
);
const persistField = usePersistField();
const persistFullNameField = (newValue: FieldFullNameValue) => {
if (!isFieldFullNameValue(newValue)) {
return;
}
persistField(newValue);
};
const fieldInitialValue = useFieldInitialValue();
const initialValue: FieldFullNameValue = fieldInitialValue?.isEmpty
? { firstName: '', lastName: '' }
: fieldValue;
return {
fieldDefinition,
fieldValue,
initialValue,
setFieldValue,
hotkeyScope,
persistFullNameField,
};
};

View File

@ -1,73 +0,0 @@
import { DoubleTextInput } from '@/ui/object/field/meta-types/input/components/internal/DoubleTextInput';
import { FieldDoubleText } from '@/ui/object/field/types/FieldDoubleText';
import { usePersistField } from '../../../hooks/usePersistField';
import { useDoubleTextField } from '../../hooks/useDoubleTextField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type DoubleTextFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;
onTab?: FieldInputEvent;
onShiftTab?: FieldInputEvent;
};
export const DoubleTextFieldInput = ({
onEnter,
onEscape,
onClickOutside,
onTab,
onShiftTab,
}: DoubleTextFieldInputProps) => {
const {
fieldDefinition,
initialFirstValue,
initialSecondValue,
hotkeyScope,
} = useDoubleTextField();
const persistField = usePersistField();
const handleEnter = (newDoubleText: FieldDoubleText) => {
onEnter?.(() => persistField(newDoubleText));
};
const handleEscape = (newDoubleText: FieldDoubleText) => {
onEscape?.(() => persistField(newDoubleText));
};
const handleClickOutside = (
event: MouseEvent | TouchEvent,
newDoubleText: FieldDoubleText,
) => {
onClickOutside?.(() => persistField(newDoubleText));
};
const handleTab = (newDoubleText: FieldDoubleText) => {
onTab?.(() => persistField(newDoubleText));
};
const handleShiftTab = (newDoubleText: FieldDoubleText) => {
onShiftTab?.(() => persistField(newDoubleText));
};
return (
<FieldInputOverlay>
<DoubleTextInput
firstValue={initialFirstValue}
secondValue={initialSecondValue}
firstValuePlaceholder={fieldDefinition.metadata.firstValuePlaceholder}
secondValuePlaceholder={fieldDefinition.metadata.secondValuePlaceholder}
onClickOutside={handleClickOutside}
onEnter={handleEnter}
onEscape={handleEscape}
onShiftTab={handleShiftTab}
onTab={handleTab}
hotkeyScope={hotkeyScope}
/>
</FieldInputOverlay>
);
};

View File

@ -1,191 +0,0 @@
import { useEffect } from 'react';
import { expect, jest } from '@storybook/jest';
import { Decorator, Meta, StoryObj } from '@storybook/react';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
import { useDoubleTextField } from '../../../hooks/useDoubleTextField';
import {
DoubleTextFieldInput,
DoubleTextFieldInputProps,
} from '../DoubleTextFieldInput';
const DoubleTextFieldValueSetterEffect = ({
firstValue,
secondValue,
}: {
firstValue: string;
secondValue: string;
}) => {
const { setFirstValue, setSecondValue } = useDoubleTextField();
useEffect(() => {
setFirstValue(firstValue);
setSecondValue(secondValue);
}, [firstValue, secondValue, setFirstValue, setSecondValue]);
return <></>;
};
type DoubleTextFieldInputWithContextProps = DoubleTextFieldInputProps & {
firstValue: string;
secondValue: string;
entityId?: string;
};
const DoubleTextFieldInputWithContext = ({
entityId,
firstValue,
secondValue,
onClickOutside,
onEnter,
onEscape,
onTab,
onShiftTab,
}: DoubleTextFieldInputWithContextProps) => {
const setHotKeyScope = useSetHotkeyScope();
useEffect(() => {
setHotKeyScope('hotkey-scope');
}, [setHotKeyScope]);
return (
<div>
<FieldContextProvider
fieldDefinition={{
fieldMetadataId: 'double-text',
label: 'Double-Text',
type: 'DOUBLE_TEXT',
metadata: {
firstValueFieldName: 'First-text',
firstValuePlaceholder: 'First-text',
secondValueFieldName: 'Second-text',
secondValuePlaceholder: 'Second-text',
},
}}
entityId={entityId}
>
<DoubleTextFieldValueSetterEffect {...{ firstValue, secondValue }} />
<DoubleTextFieldInput
onEnter={onEnter}
onEscape={onEscape}
onClickOutside={onClickOutside}
onTab={onTab}
onShiftTab={onShiftTab}
/>
</FieldContextProvider>
<div data-testid="data-field-input-click-outside-div" />
</div>
);
};
const enterJestFn = jest.fn();
const escapeJestfn = jest.fn();
const clickOutsideJestFn = jest.fn();
const tabJestFn = jest.fn();
const shiftTabJestFn = jest.fn();
const clearMocksDecorator: Decorator = (Story, context) => {
if (context.parameters.clearMocks) {
enterJestFn.mockClear();
escapeJestfn.mockClear();
clickOutsideJestFn.mockClear();
tabJestFn.mockClear();
shiftTabJestFn.mockClear();
}
return <Story />;
};
const meta: Meta = {
title: 'UI/Data/Field/Input/DoubleTextFieldInput',
component: DoubleTextFieldInputWithContext,
args: {
firstValue: 'first value',
secondValue: 'second value',
onEnter: enterJestFn,
onEscape: escapeJestfn,
onClickOutside: clickOutsideJestFn,
onTab: tabJestFn,
onShiftTab: shiftTabJestFn,
},
argTypes: {
onEnter: { control: false },
onEscape: { control: false },
onClickOutside: { control: false },
onTab: { control: false },
onShiftTab: { control: false },
},
decorators: [clearMocksDecorator],
parameters: {
clearMocks: true,
},
};
export default meta;
type Story = StoryObj<typeof DoubleTextFieldInputWithContext>;
export const Default: Story = {};
export const Enter: Story = {
play: async () => {
expect(enterJestFn).toHaveBeenCalledTimes(0);
await waitFor(() => {
userEvent.keyboard('{enter}');
expect(enterJestFn).toHaveBeenCalledTimes(1);
});
},
};
export const Escape: Story = {
play: async () => {
expect(escapeJestfn).toHaveBeenCalledTimes(0);
await waitFor(() => {
userEvent.keyboard('{esc}');
expect(escapeJestfn).toHaveBeenCalledTimes(1);
});
},
};
export const ClickOutside: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(clickOutsideJestFn).toHaveBeenCalledTimes(0);
const emptyDiv = await canvas.findByTestId(
'data-field-input-click-outside-div',
);
await waitFor(() => {
userEvent.click(emptyDiv);
expect(clickOutsideJestFn).toHaveBeenCalledTimes(1);
});
},
};
export const Tab: Story = {
play: async () => {
expect(tabJestFn).toHaveBeenCalledTimes(0);
await waitFor(() => {
userEvent.keyboard('{tab}');
expect(tabJestFn).toHaveBeenCalledTimes(1);
});
},
};
export const ShiftTab: Story = {
play: async () => {
expect(shiftTabJestFn).toHaveBeenCalledTimes(0);
await waitFor(() => {
userEvent.keyboard('{shift>}{tab}');
expect(shiftTabJestFn).toHaveBeenCalledTimes(1);
});
},
};

View File

@ -1,5 +1,7 @@
import { selectorFamily } from 'recoil';
import { isFieldFullName } from '@/ui/object/field/types/guards/isFieldFullName';
import { isFieldFullNameValue } from '@/ui/object/field/types/guards/isFieldFullNameValue';
import { isFieldUuid } from '@/ui/object/field/types/guards/isFieldUuid';
import { assertNotNull } from '~/utils/assert';
@ -108,6 +110,16 @@ export const isEntityFieldEmptyFamilySelector = selectorFamily({
);
}
if (isFieldFullName(fieldDefinition)) {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
return (
!isFieldFullNameValue(fieldValue) ||
isValueEmpty(fieldValue?.firstName + fieldValue?.lastName)
);
}
if (isFieldLink(fieldDefinition)) {
const fieldName = fieldDefinition.metadata.fieldName;
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];

View File

@ -48,6 +48,10 @@ export type FieldCurrencyMetadata = {
isPositive?: boolean;
};
export type FieldFullnameMetadata = {
fieldName: string;
};
export type FieldEmailMetadata = {
fieldName: string;
placeHolder: string;
@ -119,6 +123,7 @@ export type FieldLinkValue = { url: string; label: string };
export type FieldNumberValue = number | null;
export type FieldMoneyValue = number | null;
export type FieldCurrencyValue = { currencyCode: string; amountMicros: number };
export type FieldFullNameValue = { firstName: string; lastName: string };
export type FieldEmailValue = string;
export type FieldProbabilityValue = number;

View File

@ -15,4 +15,5 @@ export type FieldType =
| 'PROBABILITY'
| 'CURRENCY'
| 'MONEY_AMOUNT'
| 'MONEY';
| 'MONEY'
| 'FULL_NAME';

View File

@ -7,6 +7,7 @@ import {
FieldDoubleTextChipMetadata,
FieldDoubleTextMetadata,
FieldEmailMetadata,
FieldFullnameMetadata,
FieldLinkMetadata,
FieldMetadata,
FieldMoneyMetadata,
@ -28,6 +29,8 @@ type AssertFieldMetadataFunction = <
? FieldChipMetadata
: E extends 'CURRENCY'
? FieldCurrencyMetadata
: E extends 'FULL_NAME'
? FieldFullnameMetadata
: E extends 'DATE'
? FieldDateMetadata
: E extends 'DOUBLE_TEXT'

View File

@ -0,0 +1,7 @@
import { FieldDefinition } from '../FieldDefinition';
import { FieldCurrencyMetadata, FieldMetadata } from '../FieldMetadata';
export const isFieldFullName = (
field: FieldDefinition<FieldMetadata>,
): field is FieldDefinition<FieldCurrencyMetadata> =>
field.type === 'FULL_NAME';

View File

@ -0,0 +1,13 @@
import { z } from 'zod';
import { FieldFullNameValue } from '../FieldMetadata';
const currencySchema = z.object({
firstName: z.string(),
lastName: z.string(),
});
export const isFieldFullNameValue = (
fieldValue: unknown,
): fieldValue is FieldFullNameValue =>
currencySchema.safeParse(fieldValue).success;

View File

@ -6,7 +6,9 @@ import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObje
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
export const useColorScheme = () => {
const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState);
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
currentWorkspaceMemberState,
);
const { updateOneObject: updateOneWorkspaceMember } =
useUpdateOneObjectRecord({
@ -19,6 +21,15 @@ export const useColorScheme = () => {
if (!currentWorkspaceMember) {
return;
}
setCurrentWorkspaceMember((current) => {
if (!current) {
return current;
}
return {
...current,
colorScheme: value,
};
});
await updateOneWorkspaceMember?.({
idToUpdate: currentWorkspaceMember?.id,
input: {
@ -26,7 +37,11 @@ export const useColorScheme = () => {
},
});
},
[currentWorkspaceMember, updateOneWorkspaceMember],
[
currentWorkspaceMember,
setCurrentWorkspaceMember,
updateOneWorkspaceMember,
],
);
return {

View File

@ -33,25 +33,26 @@ export const UserPicker = ({
}, [initialSearchFilter, setRelationPickerSearchFilter]);
const { findManyQuery } = useFindOneObjectMetadataItem({
objectNamePlural: 'workspaceMembersV2',
objectNameSingular: 'workspaceMemberV2',
});
const useFindManyWorkspaceMembers = (options: any) =>
useQuery(findManyQuery, options);
const users = useFilteredSearchEntityQueryV2({
const workspaceMembers = useFilteredSearchEntityQueryV2({
queryHook: useFindManyWorkspaceMembers,
filters: [
{
fieldNames: ['firstName', 'lastName'],
fieldNames: ['name.firstName', 'name.lastName'],
filter: relationPickerSearchFilter,
},
],
orderByField: 'firstName',
orderByField: 'createdAt',
mappingFunction: (workspaceMember) => ({
entityType: Entity.WorkspaceMember,
id: workspaceMember.id,
name: workspaceMember.firstName,
name:
workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName,
avatarType: 'rounded',
avatarUrl: '',
originalEntity: workspaceMember,
@ -68,11 +69,11 @@ export const UserPicker = ({
<SingleEntitySelect
EmptyIcon={IconUserCircle}
emptyLabel="No Owner"
entitiesToSelect={users.entitiesToSelect}
loading={users.loading}
entitiesToSelect={workspaceMembers.entitiesToSelect}
loading={workspaceMembers.loading}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
selectedEntity={users.selectedEntities[0]}
selectedEntity={workspaceMembers.selectedEntities[0]}
width={width}
/>
);

View File

@ -67,7 +67,7 @@ export const ViewBarEffect = () => {
useFindManyObjectRecords({
skip: !currentViewId,
objectNamePlural: 'viewFieldsV2',
filter: { view: { eq: currentViewId } },
filter: { viewId: { eq: currentViewId } },
onCompleted: useRecoilCallback(
({ snapshot, set }) =>
async (data: PaginatedObjectTypeResults<ViewField>) => {

View File

@ -41,7 +41,7 @@ export const useViewFields = (viewScopeId: string) => {
variables: {
input: {
fieldMetadataId: viewField.fieldMetadataId,
view: viewIdToPersist,
viewId: viewIdToPersist,
isVisible: viewField.isVisible,
size: viewField.size,
position: viewField.position,

View File

@ -2,8 +2,10 @@ export type ColorScheme = 'Dark' | 'Light' | 'System';
export type WorkspaceMember = {
id: string;
firstName: string;
lastName: string;
name: {
firstName: string;
lastName: string;
};
avatarUrl: string | null;
locale: string;
colorScheme: ColorScheme;

View File

@ -41,16 +41,18 @@ export const WorkspaceMemberCard = ({
<Avatar
avatarUrl={workspaceMember.avatarUrl}
colorId={workspaceMember.id}
placeholder={workspaceMember.firstName || ''}
placeholder={workspaceMember.name.firstName || ''}
type="squared"
size="xl"
/>
<StyledContent>
<OverflowingTextWithTooltip
text={workspaceMember.firstName + ' ' + workspaceMember.lastName}
text={
workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName
}
/>
<StyledEmailText>
{workspaceMember.firstName + ' ' + workspaceMember.lastName}
{workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName}
</StyledEmailText>
</StyledContent>

View File

@ -73,8 +73,8 @@ export const CreateProfile = () => {
} = useForm<Form>({
mode: 'onChange',
defaultValues: {
firstName: currentWorkspaceMember?.firstName ?? '',
lastName: currentWorkspaceMember?.lastName ?? '',
firstName: currentWorkspaceMember?.name.firstName ?? '',
lastName: currentWorkspaceMember?.name.lastName ?? '',
},
resolver: zodResolver(validationSchema),
});
@ -95,8 +95,10 @@ export const CreateProfile = () => {
await updateOneObject({
idToUpdate: currentWorkspaceMember?.id,
input: {
firstName: data.firstName,
lastName: data.lastName,
name: {
firstName: data.firstName,
lastName: data.lastName,
},
},
});
@ -104,8 +106,10 @@ export const CreateProfile = () => {
(current) =>
({
...current,
firstName: data.firstName,
lastName: data.lastName,
name: {
firstName: data.firstName,
lastName: data.lastName,
},
} as any),
);

View File

@ -3,10 +3,12 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod';
import { useSetRecoilState } from 'recoil';
import { z } from 'zod';
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { H2Title } from '@/ui/display/typography/components/H2Title';
@ -41,6 +43,7 @@ export const CreateWorkspace = () => {
const navigate = useNavigate();
const { enqueueSnackBar } = useSnackBar();
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const [updateWorkspace] = useUpdateWorkspaceMutation();
@ -68,6 +71,10 @@ export const CreateWorkspace = () => {
},
},
});
setCurrentWorkspace({
id: result.data?.updateWorkspace?.id ?? '',
displayName: data.name,
});
if (result.errors || !result.data?.updateWorkspace) {
throw result.errors ?? new Error('Unknown error');
@ -82,7 +89,7 @@ export const CreateWorkspace = () => {
});
}
},
[enqueueSnackBar, navigate, updateWorkspace],
[enqueueSnackBar, navigate, setCurrentWorkspace, updateWorkspace],
);
useScopedHotkeys(

View File

@ -44,7 +44,9 @@ export const SettingsDevelopersApiKeys = () => {
const navigate = useNavigate();
const [apiKeys, setApiKeys] = useState<Array<ApiFieldItem>>([]);
const { registerOptimisticEffect } = useOptimisticEffect('apiKeyV2');
const { registerOptimisticEffect } = useOptimisticEffect({
objectNameSingular: 'apiKeyV2',
});
const { foundObjectMetadataItem } = useFindOneObjectMetadataItem({
objectNameSingular: 'apiKeyV2',
});

View File

@ -22,9 +22,9 @@ export const TasksEffect = () => {
value: currentWorkspaceMember.id,
operand: ViewFilterOperand.Is,
displayValue:
currentWorkspaceMember.firstName +
currentWorkspaceMember.name.firstName +
' ' +
currentWorkspaceMember.lastName,
currentWorkspaceMember.name.lastName,
displayAvatarUrl: currentWorkspaceMember.avatarUrl ?? undefined,
definition: tasksFilterDefinitions[0],
});

View File

@ -17,11 +17,8 @@ type MockedActivity = Pick<
| 'dueAt'
| 'completedAt'
> & {
author: Pick<WorkspaceMember, 'id' | 'firstName' | 'lastName' | 'avatarUrl'>;
assignee: Pick<
WorkspaceMember,
'id' | 'firstName' | 'lastName' | 'avatarUrl'
>;
author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
assignee: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
comments: Comment[];
activityTargets: Array<
Pick<
@ -53,14 +50,18 @@ export const mockedTasks: Array<MockedActivity> = [
completedAt: null,
author: {
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: '',
},
assignee: {
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: '',
},
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
@ -82,14 +83,18 @@ export const mockedActivities: Array<MockedActivity> = [
completedAt: null,
author: {
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: '',
},
assignee: {
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: '',
},
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
@ -149,14 +154,18 @@ export const mockedActivities: Array<MockedActivity> = [
dueAt: '2029-08-26T10:12:42.33625+00:00',
author: {
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: '',
},
assignee: {
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
firstName: 'Charles',
lastName: 'Test',
name: {
firstName: 'Charles',
lastName: 'Test',
},
avatarUrl: '',
},
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',

View File

@ -15,7 +15,7 @@ export enum SeedPersonFieldMetadataIds {
Email = '20202020-8a96-4e4b-86fd-ea126530e0c1',
LinkedinLink = '20202020-dcf6-445a-b543-37e55de43c25',
XUrl = '20202020-a3a7-4f63-9303-10226f6055be',
XLink = '20202020-a3a7-4f63-9303-10226f6055be',
JobTitle = '20202020-3b86-413e-ab56-0ebd1a583ff3',
Phone = '20202020-486f-45f9-bbdf-aac18b1831c0',
City = '20202020-78f8-4b4c-90ff-86adf77590f5',
@ -170,7 +170,7 @@ export const seedPersonFieldMetadata = async (
defaultValue: undefined,
},
{
id: SeedPersonFieldMetadataIds.XUrl,
id: SeedPersonFieldMetadataIds.XLink,
objectMetadataId: SeedObjectMetadataIds.Person,
isCustom: false,
workspaceId: SeedWorkspaceId,

View File

@ -16,6 +16,7 @@ export enum SeedViewFieldFieldMetadataIds {
Size = '20202020-b9a1-4c2e-a5af-3a6b4fef4af6',
Position = '20202020-a4bb-440a-add2-81dbd9a74517',
View = '20202020-8788-4508-b771-719807b60e61',
ViewForeignKey = '20202020-c852-4c28-b13a-07788c845d6a',
}
export const seedViewFieldFieldMetadata = async (
@ -126,13 +127,29 @@ export const seedViewFieldFieldMetadata = async (
type: FieldMetadataType.RELATION,
name: 'view',
label: 'View Id',
targetColumnMap: { value: 'viewId' },
targetColumnMap: {},
description: 'View Field related view',
icon: 'IconLayoutCollage',
isNullable: false,
isNullable: true,
isSystem: false,
defaultValue: undefined,
},
{
id: SeedViewFieldFieldMetadataIds.ViewForeignKey,
objectMetadataId: SeedObjectMetadataIds.ViewField,
isCustom: false,
workspaceId: SeedWorkspaceId,
isActive: true,
type: FieldMetadataType.UUID,
name: 'viewId',
label: 'View ID (foreign key)',
targetColumnMap: {},
description: 'Foreign key for view',
icon: undefined,
isNullable: false,
isSystem: true,
defaultValue: undefined,
},
{
id: SeedViewFieldFieldMetadataIds.IsVisible,
objectMetadataId: SeedObjectMetadataIds.ViewField,

View File

@ -13,6 +13,7 @@ export enum SeedViewFilterFieldMetadataIds {
FieldMetadataId = '20202020-78bb-4f2b-a052-260bc8efd694',
View = '20202020-65e5-4082-829d-8c634c20e7b5',
ViewForeignKey = '20202020-c852-4c28-b13a-07788c845d6b',
Operand = '20202020-1d12-465d-ab2c-8af008182730',
Value = '20202020-8b37-46ae-86b8-14287ec06802',
DisplayValue = '20202020-ed89-4892-83fa-d2b2929c6d52',
@ -126,15 +127,29 @@ export const seedViewFilterFieldMetadata = async (
type: FieldMetadataType.TEXT,
name: 'viewId',
label: 'View Id',
targetColumnMap: {
value: 'viewId',
},
targetColumnMap: {},
description: 'View Filter related view',
icon: 'IconLayoutCollage',
isNullable: false,
isSystem: false,
defaultValue: undefined,
},
{
id: SeedViewFilterFieldMetadataIds.ViewForeignKey,
objectMetadataId: SeedObjectMetadataIds.ViewField,
isCustom: false,
workspaceId: SeedWorkspaceId,
isActive: true,
type: FieldMetadataType.UUID,
name: 'viewId',
label: 'View ID (foreign key)',
targetColumnMap: {},
description: 'Foreign key for view',
icon: undefined,
isNullable: false,
isSystem: true,
defaultValue: undefined,
},
{
id: SeedViewFilterFieldMetadataIds.Operand,
objectMetadataId: SeedObjectMetadataIds.ViewFilter,

View File

@ -13,6 +13,7 @@ export enum SeedViewSortFieldMetadataIds {
FieldMetadataId = '20202020-cb2c-4c8f-a289-c9851b23d064',
View = '20202020-f5d0-467f-a3d8-395ba16b8ebf',
ViewForeignKey = '20202020-c852-4c28-b13a-07788c845d6c',
Direction = '20202020-077e-4451-b1d8-e602c956ebd2',
}
@ -124,15 +125,29 @@ export const seedViewSortFieldMetadata = async (
type: FieldMetadataType.TEXT,
name: 'viewId',
label: 'View Id',
targetColumnMap: {
value: 'viewId',
},
targetColumnMap: {},
description: 'View Sort related view',
icon: 'IconLayoutCollage',
isNullable: false,
isSystem: false,
defaultValue: undefined,
},
{
id: SeedViewSortFieldMetadataIds.ViewForeignKey,
objectMetadataId: SeedObjectMetadataIds.ViewField,
isCustom: false,
workspaceId: SeedWorkspaceId,
isActive: true,
type: FieldMetadataType.UUID,
name: 'viewId',
label: 'View ID (foreign key)',
targetColumnMap: {},
description: 'Foreign key for view',
icon: undefined,
isNullable: false,
isSystem: true,
defaultValue: undefined,
},
{
id: SeedViewSortFieldMetadataIds.Direction,
objectMetadataId: SeedObjectMetadataIds.ViewSort,

View File

@ -3,7 +3,6 @@ import { DataSource } from 'typeorm';
import { SeedObjectMetadataIds } from 'src/database/typeorm-seeds/metadata/object-metadata';
import { SeedWorkspaceId } from 'src/database/seeds/metadata';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { SeedPersonFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/person';
const fieldMetadataTableName = 'fieldMetadata';
@ -12,8 +11,7 @@ export enum SeedWorkspaceMemberFieldMetadataIds {
CreatedAt = '20202020-1cbf-4b32-8c33-fbfedcd9afa8',
UpdatedAt = '20202020-1ba3-4c24-b2cd-b0789633e8d4',
FirstName = '20202020-1fa8-4d38-9fa4-0cf696909298',
LastName = '20202020-8c37-4163-ba06-1dada334ce3e',
Name = '20202020-8c37-4163-ba06-1dada334ce3e',
AvatarUrl = '20202020-7ba6-40d5-934b-17146183a212',
Locale = '20202020-10f6-4df9-8d6f-a760b65bd800',
ColorScheme = '20202020-83f2-4c5f-96b0-0c51ecc160e3',
@ -110,7 +108,7 @@ export const seedWorkspaceMemberFieldMetadata = async (
},
// Scalar fields
{
id: SeedPersonFieldMetadataIds.Name,
id: SeedWorkspaceMemberFieldMetadataIds.Name,
objectMetadataId: SeedObjectMetadataIds.WorkspaceMember,
isCustom: false,
workspaceId: SeedWorkspaceId,

View File

@ -1,6 +1,9 @@
import { DataSource } from 'typeorm';
import { SeedViewIds } from 'src/database/typeorm-seeds/workspace/views';
import { SeedCompanyFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/company';
import { SeedPersonFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/person';
import { SeedOpportunityFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/opportunity';
const tableName = 'viewField';
@ -21,112 +24,112 @@ export const seedViewFields = async (
.orIgnore()
.values([
{
fieldMetadataId: 'name',
fieldMetadataId: SeedCompanyFieldMetadataIds.Name,
viewId: SeedViewIds.Company,
position: 0,
isVisible: true,
size: 180,
},
{
fieldMetadataId: 'domainName',
fieldMetadataId: SeedCompanyFieldMetadataIds.DomainName,
viewId: SeedViewIds.Company,
position: 1,
isVisible: true,
size: 100,
},
{
fieldMetadataId: 'accountOwner',
fieldMetadataId: SeedCompanyFieldMetadataIds.AccountOwner,
viewId: SeedViewIds.Company,
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
fieldMetadataId: SeedCompanyFieldMetadataIds.CreatedAt,
viewId: SeedViewIds.Company,
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'employees',
fieldMetadataId: SeedCompanyFieldMetadataIds.Employees,
viewId: SeedViewIds.Company,
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'linkedin',
fieldMetadataId: SeedCompanyFieldMetadataIds.LinkedinLink,
viewId: SeedViewIds.Company,
position: 5,
isVisible: true,
size: 170,
},
{
fieldMetadataId: 'address',
fieldMetadataId: SeedCompanyFieldMetadataIds.Address,
viewId: SeedViewIds.Company,
position: 6,
isVisible: true,
size: 170,
},
{
fieldMetadataId: 'displayName',
fieldMetadataId: SeedPersonFieldMetadataIds.Name,
viewId: SeedViewIds.Person,
position: 0,
isVisible: true,
size: 210,
},
{
fieldMetadataId: 'email',
fieldMetadataId: SeedPersonFieldMetadataIds.Email,
viewId: SeedViewIds.Person,
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'company',
fieldMetadataId: SeedPersonFieldMetadataIds.Company,
viewId: SeedViewIds.Person,
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'phone',
fieldMetadataId: SeedPersonFieldMetadataIds.Phone,
viewId: SeedViewIds.Person,
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'createdAt',
fieldMetadataId: SeedPersonFieldMetadataIds.CreatedAt,
viewId: SeedViewIds.Person,
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'city',
fieldMetadataId: SeedPersonFieldMetadataIds.City,
viewId: SeedViewIds.Person,
position: 5,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'jobTitle',
fieldMetadataId: SeedPersonFieldMetadataIds.JobTitle,
viewId: SeedViewIds.Person,
position: 6,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'linkedin',
fieldMetadataId: SeedPersonFieldMetadataIds.LinkedinLink,
viewId: SeedViewIds.Person,
position: 7,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'x',
fieldMetadataId: SeedPersonFieldMetadataIds.XLink,
viewId: SeedViewIds.Person,
position: 8,
isVisible: true,
@ -134,28 +137,28 @@ export const seedViewFields = async (
},
{
fieldMetadataId: 'amount',
fieldMetadataId: SeedOpportunityFieldMetadataIds.Amount,
viewId: SeedViewIds.Opportunity,
position: 0,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'closeDate',
fieldMetadataId: SeedOpportunityFieldMetadataIds.CloseDate,
viewId: SeedViewIds.Opportunity,
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'probability',
fieldMetadataId: SeedOpportunityFieldMetadataIds.Probability,
viewId: SeedViewIds.Opportunity,
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId: 'pointOfContact',
fieldMetadataId: SeedOpportunityFieldMetadataIds.PointOfContact,
viewId: SeedViewIds.Opportunity,
position: 3,
isVisible: true,

View File

@ -1,33 +0,0 @@
import { EntityManager } from 'typeorm';
export const opportunityPrefillData = async (
entityManager: EntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.insert()
.into(`${schemaName}.opportunity`, [
'amount',
'closeDate',
'probability',
'pipelineStepId',
'pointOfContactId',
'personId',
'companyId',
])
.orIgnore()
.values([
{
amount: 100000,
closeDate: new Date(),
probability: 0.5,
pipelineStepId: '73ac09c6-2b90-4874-9e5d-553ea76912ee',
pointOfContactId: 'bb757987-ae38-4d16-96ec-b25b595e7bd8',
personId: 'a4a2b8e9-7a2b-4b6a-8c8b-7e9a0a0a0a0a',
companyId: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
},
])
.returning('*')
.execute();
};

View File

@ -5,7 +5,6 @@ import { viewPrefillData } from 'src/workspace/workspace-manager/standard-object
import { companyPrefillData } from 'src/workspace/workspace-manager/standard-objects-prefill-data/company';
import { personPrefillData } from 'src/workspace/workspace-manager/standard-objects-prefill-data/person';
import { pipelineStepPrefillData } from 'src/workspace/workspace-manager/standard-objects-prefill-data/pipeline-step';
import { opportunityPrefillData } from 'src/workspace/workspace-manager/standard-objects-prefill-data/opportunity';
export const standardObjectsPrefillData = async (
workspaceDataSource: DataSource,
@ -28,6 +27,5 @@ export const standardObjectsPrefillData = async (
await personPrefillData(entityManager, schemaName);
await viewPrefillData(entityManager, schemaName, objectMetadataMap);
await pipelineStepPrefillData(entityManager, schemaName);
await opportunityPrefillData(entityManager, schemaName);
});
};

View File

@ -125,12 +125,23 @@ const personMetadata = {
type: FieldMetadataType.RELATION,
name: 'company',
label: 'Company',
targetColumnMap: {
value: 'companyId',
},
targetColumnMap: {},
description: 'Contacts company',
icon: 'IconBuildingSkyscraper',
isNullable: false,
isSystem: false,
},
{
isCustom: false,
isActive: true,
type: FieldMetadataType.UUID,
name: 'companyId',
label: 'Company ID (foreign key)',
targetColumnMap: {},
description: 'Foreign key for company',
icon: undefined,
isNullable: false,
isSystem: true,
},
{
isCustom: false,

View File

@ -73,16 +73,15 @@ const viewFieldMetadata = {
type: FieldMetadataType.RELATION,
name: 'view',
label: 'View',
targetColumnMap: { value: 'viewId' },
targetColumnMap: {},
description: 'View Field related view',
icon: 'IconLayoutCollage',
isNullable: false,
isNullable: true,
},
// Temporary hack?
{
isCustom: false,
isActive: true,
type: FieldMetadataType.TEXT,
type: FieldMetadataType.UUID,
name: 'viewId',
label: 'View Id',
targetColumnMap: {

View File

@ -73,16 +73,15 @@ const viewFilterMetadata = {
type: FieldMetadataType.RELATION,
name: 'view',
label: 'View',
targetColumnMap: { value: 'viewId' },
targetColumnMap: {},
description: 'View Filter related view',
icon: 'IconLayoutCollage',
isNullable: false,
isNullable: true,
},
// Temporary hack?
{
isCustom: false,
isActive: true,
type: FieldMetadataType.TEXT,
type: FieldMetadataType.UUID,
name: 'viewId',
label: 'View Id',
targetColumnMap: {

View File

@ -45,18 +45,15 @@ const viewSortMetadata = {
type: FieldMetadataType.RELATION,
name: 'view',
label: 'View',
targetColumnMap: {
value: 'viewId',
},
targetColumnMap: {},
description: 'View Sort related view',
icon: 'IconLayoutCollage',
isNullable: false,
isNullable: true,
},
// Temporary Hack?
{
isCustom: false,
isActive: true,
type: FieldMetadataType.TEXT,
type: FieldMetadataType.UUID,
name: 'viewId',
label: 'View Id',
targetColumnMap: {

View File

@ -11,19 +11,6 @@ const workspaceMemberMetadata = {
isActive: true,
isSystem: true,
fields: [
{
isCustom: false,
isActive: true,
type: FieldMetadataType.TEXT,
name: 'firstName',
label: 'First name',
targetColumnMap: {
value: 'firstName',
},
description: 'Workspace member first name',
icon: 'IconCircleUser',
isNullable: false,
},
{
isCustom: false,
isActive: true,
@ -93,6 +80,20 @@ const workspaceMemberMetadata = {
isNullable: true,
isSystem: false,
},
{
isCustom: false,
isActive: true,
type: FieldMetadataType.UUID,
name: 'userId',
label: 'User Id',
targetColumnMap: {
value: 'userId',
},
description: 'Associated User Id',
icon: 'IconCircleUsers',
isNullable: false,
isSystem: false,
},
// Relations
{
isCustom: false,

View File

@ -81,23 +81,21 @@ export class WorkspaceManagerService {
workspaceId: string,
): Promise<ObjectMetadataEntity[]> {
const createdObjectMetadata = await this.objectMetadataService.createMany(
Object.values(standardObjectsMetadata).map(
(objectMetadata: ObjectMetadataEntity) => ({
...objectMetadata,
dataSourceId,
workspaceId,
isCustom: false,
isActive: true,
fields: [...basicFieldsMetadata, ...objectMetadata.fields].map(
(field) => ({
...field,
workspaceId,
isCustom: false,
isActive: true,
}),
),
}),
),
Object.values(standardObjectsMetadata).map((objectMetadata: any) => ({
...objectMetadata,
dataSourceId,
workspaceId,
isCustom: false,
isActive: true,
fields: [...basicFieldsMetadata, ...objectMetadata.fields].map(
(field) => ({
...field,
workspaceId,
isCustom: false,
isActive: true,
}),
),
})),
);
await this.relationMetadataService.createMany(