Remove some dead code (#6611)

We could remove a lot more than this, this is just a start.

There are various tools to help with this, knip is a good one
This commit is contained in:
Félix Malfait 2024-08-11 20:43:18 +02:00 committed by GitHub
parent 39512a779e
commit d5350e11a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 23 additions and 1044 deletions

View File

@ -1,28 +0,0 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { activityBodyFamilyState } from '@/activities/states/activityBodyFamilyState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isDefined } from '~/utils/isDefined';
export const ActivityBodyEffect = ({ activityId }: { activityId: string }) => {
const [activityFromStore] = useRecoilState(
recordStoreFamilyState(activityId),
);
const [activityBody, setActivityBody] = useRecoilState(
activityBodyFamilyState({ activityId }),
);
useEffect(() => {
if (
activityBody === '' &&
isDefined(activityFromStore) &&
activityBody !== activityFromStore.body
) {
setActivityBody(activityFromStore.body);
}
}, [activityFromStore, activityBody, setActivityBody]);
return <></>;
};

View File

@ -1,9 +0,0 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
export const activityTitleFamilyState = createFamilyState<
string,
{ activityId: string }
>({
key: 'activityTitleFamilyState',
defaultValue: '',
});

View File

@ -1,10 +0,0 @@
import { createState } from 'twenty-ui';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
export const targetableObjectsInDrawerState = createState<
ActivityTargetableObject[]
>({
key: 'targetableObjectsInDrawerState',
defaultValue: [],
});

View File

@ -1,18 +0,0 @@
import styled from '@emotion/styled';
import { CommentChip, CommentChipProps } from './CommentChip';
type CellCommentChipProps = CommentChipProps;
// TODO: tie those fixed values to the other components in the cell
const StyledCellWrapper = styled.div``;
export const CellCommentChip = ({ count, onClick }: CellCommentChipProps) => {
if (count === 0) return null;
return (
<StyledCellWrapper>
<CommentChip count={count} onClick={onClick} />
</StyledCellWrapper>
);
};

View File

@ -1,60 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComment } from 'twenty-ui';
export type CommentChipProps = {
count: number;
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
};
const StyledChip = styled.div`
align-items: center;
backdrop-filter: ${({ theme }) => theme.blur.medium};
background: ${({ theme }) => theme.background.transparent.primary};
border-radius: ${({ theme }) => theme.border.radius.md};
color: ${({ theme }) => theme.font.color.light};
cursor: pointer;
display: flex;
flex-direction: row;
gap: 4px;
height: 26px;
justify-content: center;
max-width: 42px;
padding-left: 4px;
padding-right: 4px;
&:hover {
background: ${({ theme }) => theme.background.tertiary};
color: ${({ theme }) => theme.font.color.tertiary};
}
user-select: none;
`;
const StyledCount = styled.div`
align-items: center;
display: flex;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
justify-content: center;
`;
export const CommentChip = ({ count, onClick }: CommentChipProps) => {
const theme = useTheme();
if (count === 0) return null;
const formattedCount = count > 99 ? '99+' : count;
return (
<StyledChip data-testid="comment-chip" onClick={onClick}>
<StyledCount>{formattedCount}</StyledCount>
<IconComment size={theme.icon.size.md} />
</StyledChip>
);
};

View File

@ -1,73 +0,0 @@
import styled from '@emotion/styled';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from 'twenty-ui';
import { CommentChip } from '../CommentChip';
const meta: Meta<typeof CommentChip> = {
title: 'Modules/Comments/CommentChip',
component: CommentChip,
decorators: [ComponentDecorator],
args: { count: 1 },
};
export default meta;
type Story = StoryObj<typeof CommentChip>;
const StyledTestCellContainer = styled.div`
align-items: center;
background: ${({ theme }) => theme.background.primary};
display: flex;
height: fit-content;
justify-content: space-between;
max-width: 250px;
min-width: 250px;
overflow: hidden;
text-wrap: nowrap;
`;
const StyledFakeCellText = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const OneComment: Story = {};
export const TenComments: Story = {
args: { count: 10 },
};
export const TooManyComments: Story = {
args: { count: 1000 },
};
export const InCellDefault: Story = {
args: { count: 12 },
decorators: [
(Story) => (
<StyledTestCellContainer>
<StyledFakeCellText>Fake short text</StyledFakeCellText>
<Story />
</StyledTestCellContainer>
),
],
};
export const InCellOverlappingBlur: Story = {
...InCellDefault,
decorators: [
(Story) => (
<StyledTestCellContainer>
<StyledFakeCellText>
Fake long text to demonstrate ellipsis
</StyledFakeCellText>
<Story />
</StyledTestCellContainer>
),
],
};

View File

@ -1,9 +0,0 @@
import { atom } from 'recoil';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
export const currentCompletedTaskQueryVariablesState =
atom<RecordGqlOperationVariables | null>({
default: null,
key: 'currentCompletedTaskQueryVariablesState',
});

View File

@ -1,9 +0,0 @@
import { atom } from 'recoil';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
export const currentIncompleteTaskQueryVariablesState =
atom<RecordGqlOperationVariables | null>({
default: null,
key: 'currentIncompleteTaskQueryVariablesState',
});

View File

@ -1,6 +0,0 @@
import { createState } from 'twenty-ui';
export const currentUserDueTaskCountState = createState<number>({
defaultValue: 0,
key: 'currentUserDueTaskCountState',
});

View File

@ -1,22 +0,0 @@
import { useActivities } from '@/activities/hooks/useActivities';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timelineActivities/constants/FindManyTimelineActivitiesOrderBy';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isDefined } from '~/utils/isDefined';
export const TimelineActivitiesQueryEffect = ({
targetableObject,
}: {
targetableObject: ActivityTargetableObject;
}) => {
useActivities({
objectNameSingular:
targetableObject.targetObjectNameSingular as CoreObjectNameSingular,
targetableObjects: [targetableObject],
activitiesFilters: {},
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
skip: !isDefined(targetableObject),
});
return <></>;
};

View File

@ -1,11 +0,0 @@
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
export type Comment = {
id: string;
createdAt: string;
body: string;
updatedAt: string;
activityId: string;
author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
__typename: 'Comment';
};

View File

@ -1,25 +0,0 @@
import { expect } from '@storybook/test';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { useTrackEvent } from '../useTrackEvent';
const mockTrackMutation = jest.fn();
jest.mock('~/generated/graphql', () => ({
useTrackMutation: () => [mockTrackMutation],
}));
describe('useTrackEvent', () => {
it('should call useEventTracker with the correct arguments', async () => {
const eventType = 'exampleType';
const eventData = { location: { pathname: '/examplePath' } };
renderHook(() => useTrackEvent(eventType, eventData), {
wrapper: RecoilRoot,
});
expect(mockTrackMutation).toHaveBeenCalledTimes(1);
expect(mockTrackMutation).toHaveBeenCalledWith({
variables: { type: eventType, data: eventData },
});
});
});

View File

@ -1,7 +0,0 @@
import { EventData, useEventTracker } from './useEventTracker';
export const useTrackEvent = (eventType: string, eventData: EventData) => {
const eventTracker = useEventTracker();
return eventTracker(eventType, eventData);
};

View File

@ -1,8 +0,0 @@
import { createState } from 'twenty-ui';
import { Favorite } from '@/favorites/types/Favorite';
export const favoritesState = createState<Favorite[]>({
key: 'favoritesState',
defaultValue: [],
});

View File

@ -1,50 +0,0 @@
import { mapFavorites } from '../mapFavorites';
describe('mapFavorites', () => {
it('should return the correct value', () => {
const favorites = [
{
id: '1',
person: {
id: '2',
name: {
firstName: 'John',
lastName: 'Doe',
},
avatarUrl: 'https://example.com/avatar.png',
},
},
{
id: '3',
company: {
id: '4',
name: 'My Company',
domainName: 'example.com',
},
position: 1,
},
];
const res = mapFavorites(favorites);
expect(res).toHaveLength(2);
// Person
expect(res[0].id).toBe('1');
expect(res[0].labelIdentifier).toBe('John Doe');
expect(res[0].avatarUrl).toBe('https://example.com/avatar.png');
expect(res[0].avatarType).toBe('rounded');
expect(res[0].link).toBe('/object/person/2');
expect(res[0].recordId).toBe('2');
expect(res[0].position).toBeUndefined();
// Company
expect(res[1].id).toBe('3');
expect(res[1].labelIdentifier).toBe('My Company');
expect(res[1].avatarUrl).toBe('https://favicon.twenty.com/example.com');
expect(res[1].avatarType).toBe('squared');
expect(res[1].link).toBe('/object/company/4');
expect(res[1].recordId).toBe('4');
expect(res[1].position).toBe(1);
});
});

View File

@ -1,40 +0,0 @@
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
import { getLogoUrlFromDomainName } from '~/utils';
import { isDefined } from '~/utils/isDefined';
export const mapFavorites = (favorites: any) => {
return favorites
.map((favorite: any) => {
const recordInformation = isDefined(favorite?.person)
? {
id: favorite.person.id,
labelIdentifier:
favorite.person.name.firstName +
' ' +
favorite.person.name.lastName,
avatarUrl: favorite.person.avatarUrl,
avatarType: 'rounded',
link: `/object/person/${favorite.person.id}`,
}
: isDefined(favorite?.company)
? {
id: favorite.company.id,
labelIdentifier: favorite.company.name,
avatarUrl: getLogoUrlFromDomainName(
getCompanyDomainName(favorite.company),
),
avatarType: 'squared',
link: `/object/company/${favorite.company.id}`,
}
: undefined;
return {
...recordInformation,
recordId: recordInformation?.id,
id: favorite?.id,
position: favorite?.position,
};
})
.filter(isDefined)
.sort((a: any, b: any) => a.position - b.position);
};

View File

@ -1,15 +0,0 @@
import { formatInTimeZone } from 'date-fns-tz';
import { parseDate } from '~/utils/date-utils';
export const formatDatetime = (
date: Date | string,
timeZone: string,
dateFormat: string,
timeFormat: string,
) => {
return formatInTimeZone(
parseDate(date).toJSDate(),
timeZone,
`${dateFormat} ${timeFormat}`,
);
};

View File

@ -1,31 +0,0 @@
import { MemoryRouter } from 'react-router-dom';
import { renderHook } from '@testing-library/react';
import { useIsTasksPage } from '../useIsTasksPage';
const getWrapper =
(initialIndex: 0 | 1) =>
({ children }: { children: React.ReactNode }) => (
<MemoryRouter
initialEntries={['/settings/', '/tasks']}
initialIndex={initialIndex}
>
{children}
</MemoryRouter>
);
describe('useIsSettingsPage', () => {
it('should return true for pages which has /tasks in pathname', () => {
const { result } = renderHook(() => useIsTasksPage(), {
wrapper: getWrapper(1),
});
expect(result.current).toBe(true);
});
it('should return false for other pages which does not have /tasks in pathname', () => {
const { result } = renderHook(() => useIsTasksPage(), {
wrapper: getWrapper(0),
});
expect(result.current).toBe(false);
});
});

View File

@ -1,3 +0,0 @@
import { useLocation } from 'react-router-dom';
export const useIsTasksPage = () => useLocation().pathname === '/tasks';

View File

@ -1,38 +0,0 @@
import { renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { useFilterOutUnexistingObjectMetadataItems } from '../useFilterOutUnexistingObjectMetadataItems';
const mockObjectMetadataItems = getObjectMetadataItemsMock();
describe('useFilterOutUnexistingObjectMetadataItems', () => {
it('should work as expected', async () => {
const { result } = renderHook(
() => {
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(mockObjectMetadataItems.slice(1));
return useFilterOutUnexistingObjectMetadataItems();
},
{
wrapper: RecoilRoot,
},
);
const objectExists = result.current.filterOutUnexistingObjectMetadataItems(
mockObjectMetadataItems[0],
);
expect(objectExists).toBe(false);
const secondObjectExists =
result.current.filterOutUnexistingObjectMetadataItems(
mockObjectMetadataItems[1],
);
expect(secondObjectExists).toBe(true);
});
});

View File

@ -1,22 +0,0 @@
import { useRecoilValue } from 'recoil';
import { objectMetadataItemsByNameSingularMapSelector } from '@/object-metadata/states/objectMetadataItemsByNameSingularMapSelector';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isDefined } from '~/utils/isDefined';
export const useFilterOutUnexistingObjectMetadataItems = () => {
const objectMetadataItemsByNameSingularMap = useRecoilValue(
objectMetadataItemsByNameSingularMapSelector,
);
const filterOutUnexistingObjectMetadataItems = (
objectMetadatItem: ObjectMetadataItem,
) =>
isDefined(
objectMetadataItemsByNameSingularMap.get(objectMetadatItem.nameSingular),
);
return {
filterOutUnexistingObjectMetadataItems,
};
};

View File

@ -1 +0,0 @@
export type Position = number | 'first' | 'last';

View File

@ -1,38 +0,0 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { RelationDirections } from '@/object-record/record-field/types/FieldDefinition';
import {
FieldMetadataType,
RelationDefinitionType,
} from '~/generated-metadata/graphql';
export const getFieldRelationDirections = (
field: Pick<FieldMetadataItem, 'type' | 'relationDefinition'> | undefined,
): RelationDirections => {
if (!field || field.type !== FieldMetadataType.Relation) {
throw new Error(`Field is not a relation field.`);
}
switch (field.relationDefinition?.direction) {
case RelationDefinitionType.ManyToMany:
throw new Error(`Many to many relations are not supported.`);
case RelationDefinitionType.OneToMany:
return {
from: 'FROM_ONE_OBJECT',
to: 'TO_MANY_OBJECTS',
};
case RelationDefinitionType.ManyToOne:
return {
from: 'FROM_MANY_OBJECTS',
to: 'TO_ONE_OBJECT',
};
case RelationDefinitionType.OneToOne:
return {
from: 'FROM_ONE_OBJECT',
to: 'TO_ONE_OBJECT',
};
default:
throw new Error(
`Invalid relation definition type direction : ${field.relationDefinition?.direction}`,
);
}
};

View File

@ -4,7 +4,7 @@ import { AddressInput } from '@/ui/field/input/components/AddressInput';
import { usePersistField } from '../../../hooks/usePersistField';
import { FieldInputEvent } from './DateFieldInput';
import { FieldInputEvent } from './DateTimeFieldInput';
export type AddressFieldInputProps = {
onClickOutside?: FieldInputEvent;

View File

@ -9,7 +9,7 @@ import { useCurrencyField } from '../../hooks/useCurrencyField';
import { FieldInputEvent } from './DateTimeFieldInput';
export type CurrencyFieldInputProps = {
type CurrencyFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;

View File

@ -6,9 +6,9 @@ import { isDefined } from '~/utils/isDefined';
import { usePersistField } from '../../../hooks/usePersistField';
export type FieldInputEvent = (persist: () => void) => void;
type FieldInputEvent = (persist: () => void) => void;
export type DateFieldInputProps = {
type DateFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;

View File

@ -13,7 +13,7 @@ const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
const LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
'Last name';
export type FullNameFieldInputProps = {
type FullNameFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;

View File

@ -5,7 +5,7 @@ import { useLinkField } from '../../hooks/useLinkField';
import { FieldInputEvent } from './DateTimeFieldInput';
export type LinkFieldInputProps = {
type LinkFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;

View File

@ -1,5 +1,5 @@
import { useMemo, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { useMemo, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { IconCheck, IconPlus } from 'twenty-ui';
@ -24,7 +24,7 @@ const StyledDropdownMenu = styled(DropdownMenu)`
top: -1px;
`;
export type LinksFieldInputProps = {
type LinksFieldInputProps = {
onCancel?: () => void;
};

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
import {
IconBookmark,
IconBookmarkPlus,

View File

@ -23,7 +23,7 @@ const StyledRelationPickerContainer = styled.div`
top: -1px;
`;
export type MultiSelectFieldInputProps = {
type MultiSelectFieldInputProps = {
onCancel?: () => void;
};

View File

@ -3,9 +3,9 @@ import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
import { useJsonField } from '../../hooks/useJsonField';
import { FieldInputEvent } from './DateFieldInput';
import { FieldInputEvent } from './DateTimeFieldInput';
export type RawJsonFieldInputProps = {
type RawJsonFieldInputProps = {
onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent;
onEscape?: FieldInputEvent;

View File

@ -12,7 +12,7 @@ import { MultiRecordSelect } from '@/object-record/relation-picker/components/Mu
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
export type RelationFromManyFieldInputProps = {
type RelationFromManyFieldInputProps = {
onSubmit?: FieldInputEvent;
};

View File

@ -25,7 +25,7 @@ const StyledRelationPickerContainer = styled.div`
top: -1px;
`;
export type SelectFieldInputProps = {
type SelectFieldInputProps = {
onSubmit?: FieldInputEvent;
onCancel?: () => void;
};

View File

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

View File

@ -1,7 +0,0 @@
import { FieldDefinition } from './FieldDefinition';
import { FieldMetadata } from './FieldMetadata';
export type FieldDefinitionSerializable = Omit<
FieldDefinition<FieldMetadata>,
'Icon'
>;

View File

@ -1 +0,0 @@
export const CREATE_BUTTON_ID = 'create-button';

View File

@ -1 +0,0 @@
export const EMPTY_BUTTON_ID = 'empty-button';

View File

@ -1,10 +0,0 @@
export const getPreselectedIdIndex = (
selectableOptionIds: string[],
preselectedOptionId: string,
) => {
const preselectedIdIndex = selectableOptionIds.findIndex(
(option) => option === preselectedOptionId,
);
return preselectedIdIndex === -1 ? 0 : preselectedIdIndex;
};

View File

@ -1,5 +0,0 @@
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
export const mapToRecordId = (objectRecord: ObjectRecord) => {
return objectRecord.id;
};

View File

@ -1,5 +0,0 @@
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
export const sortByObjectRecordId = (a: ObjectRecord, b: ObjectRecord) => {
return a.id.localeCompare(b.id);
};

View File

@ -1,85 +0,0 @@
import { OrderBy } from '@/object-metadata/types/OrderBy';
import { sortObjectRecordByDateField } from './sortObjectRecordByDateField';
describe('sortByObjectRecordByCreatedAt', () => {
const recordOldest = {
id: '',
createdAt: '2022-01-01T00:00:00.000Z',
__typename: 'RecordType',
};
const recordNewest = {
id: '',
createdAt: '2022-01-02T00:00:00.000Z',
__typename: 'RecordType',
};
const recordNull1 = { id: '', createdAt: null, __typename: 'RecordType' };
const recordNull2 = { id: '', createdAt: null, __typename: 'RecordType' };
it('should sort in ascending order with null values first', () => {
const sortDirection = 'AscNullsFirst' satisfies OrderBy;
const sortedArray = [
recordNull2,
recordNewest,
recordNull1,
recordOldest,
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
expect(sortedArray).toEqual([
recordNull1,
recordNull2,
recordOldest,
recordNewest,
]);
});
it('should sort in descending order with null values first', () => {
const sortDirection = 'DescNullsFirst' satisfies OrderBy;
const sortedArray = [
recordNull2,
recordOldest,
recordNewest,
recordNull1,
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
expect(sortedArray).toEqual([
recordNull2,
recordNull1,
recordNewest,
recordOldest,
]);
});
it('should sort in ascending order with null values last', () => {
const sortDirection = 'AscNullsLast' satisfies OrderBy;
const sortedArray = [
recordOldest,
recordNull2,
recordNewest,
recordNull1,
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
expect(sortedArray).toEqual([
recordOldest,
recordNewest,
recordNull1,
recordNull2,
]);
});
it('should sort in descending order with null values last', () => {
const sortDirection = 'DescNullsLast' satisfies OrderBy;
const sortedArray = [
recordNull1,
recordOldest,
recordNewest,
recordNull2,
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
expect(sortedArray).toEqual([
recordNewest,
recordOldest,
recordNull1,
recordNull2,
]);
});
});

View File

@ -1,68 +0,0 @@
import { DateTime } from 'luxon';
import { OrderBy } from '@/object-metadata/types/OrderBy';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from '~/utils/isDefined';
const SORT_BEFORE = -1;
const SORT_AFTER = 1;
const SORT_EQUAL = 0;
export const sortObjectRecordByDateField =
<T extends ObjectRecord>(dateField: keyof T, sortDirection: OrderBy) =>
(a: T, b: T) => {
const aDate = a[dateField];
const bDate = b[dateField];
if (!isDefined(aDate) && !isDefined(bDate)) {
return SORT_EQUAL;
}
if (!isDefined(aDate)) {
if (sortDirection === 'AscNullsFirst') {
return SORT_BEFORE;
} else if (sortDirection === 'DescNullsFirst') {
return SORT_BEFORE;
} else if (sortDirection === 'AscNullsLast') {
return SORT_AFTER;
} else if (sortDirection === 'DescNullsLast') {
return SORT_AFTER;
}
throw new Error(`Invalid sortDirection: ${sortDirection}`);
}
if (!isDefined(bDate)) {
if (sortDirection === 'AscNullsFirst') {
return SORT_AFTER;
} else if (sortDirection === 'DescNullsFirst') {
return SORT_AFTER;
} else if (sortDirection === 'AscNullsLast') {
return SORT_BEFORE;
} else if (sortDirection === 'DescNullsLast') {
return SORT_BEFORE;
}
throw new Error(`Invalid sortDirection: ${sortDirection}`);
}
const differenceInMs = DateTime.fromISO(aDate)
.diff(DateTime.fromISO(bDate))
.as('milliseconds');
if (differenceInMs === 0) {
return SORT_EQUAL;
} else if (
sortDirection === 'AscNullsFirst' ||
sortDirection === 'AscNullsLast'
) {
return differenceInMs > 0 ? SORT_AFTER : SORT_BEFORE;
} else if (
sortDirection === 'DescNullsFirst' ||
sortDirection === 'DescNullsLast'
) {
return differenceInMs > 0 ? SORT_BEFORE : SORT_AFTER;
}
throw new Error(`Invalid sortDirection: ${sortDirection}`);
};

View File

@ -1,25 +0,0 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useGetSyncStatusOptions = () => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.MessageChannel,
});
if (isUndefinedOrNull(objectMetadataItem)) {
throw new Error('ObjectMetadataItem not found for MessageChannel');
}
const syncStatusMetadata = objectMetadataItem.fields.find(
(field) => field.name === 'syncStatus',
);
const syncStatusOptions = syncStatusMetadata?.options;
if (isUndefinedOrNull(syncStatusMetadata)) {
throw new Error('syncStatusMetaData not found for MessageChannel');
}
return syncStatusOptions;
};

View File

@ -14,7 +14,7 @@ type StyledDropdownButtonProps = {
isUnfolded: boolean;
};
export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
align-items: center;
color: ${({ color }) => color ?? 'none'};
cursor: pointer;

View File

@ -21,7 +21,7 @@ import { UserContext } from '@/users/contexts/UserContext';
import { useContext } from 'react';
import 'react-datepicker/dist/react-datepicker.css';
export const months = [
const months = [
{ label: 'January', value: 0 },
{ label: 'February', value: 1 },
{ label: 'March', value: 2 },
@ -36,7 +36,7 @@ export const months = [
{ label: 'December', value: 11 },
];
export const years = Array.from(
const years = Array.from(
{ length: 200 },
(_, i) => new Date().getFullYear() + 5 - i,
).map((year) => ({ label: year.toString(), value: year }));
@ -299,7 +299,7 @@ const StyledCustomDatePickerHeader = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
export type InternalDatePickerProps = {
type InternalDatePickerProps = {
date: Date | null;
onMouseSelect?: (date: Date | null) => void;
onChange?: (date: Date | null) => void;

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
import { IconChevronDown, IconWorld } from 'twenty-ui';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
@ -19,7 +19,7 @@ type StyledDropdownButtonProps = {
isUnfolded: boolean;
};
export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
align-items: center;
background: none;
border-radius: ${({ theme }) => theme.border.radius.xs} 0 0

View File

@ -1,16 +0,0 @@
import { AvatarChip } from 'twenty-ui';
export type UserChipProps = {
id: string;
name: string;
avatarUrl?: string;
};
export const UserChip = ({ id, name, avatarUrl }: UserChipProps) => (
<AvatarChip
placeholderColorSeed={id}
name={name}
avatarType="rounded"
avatarUrl={avatarUrl}
/>
);

View File

@ -1,4 +0,0 @@
export enum ViewSortDirection {
Asc = 'asc',
Desc = 'desc',
}

View File

@ -17,7 +17,7 @@ import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-field-metadata/index-field-metadata.entity';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';

View File

@ -1,11 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-field-metadata/index-field-metadata.entity';
@Module({
imports: [TypeOrmModule.forFeature([IndexFieldMetadataEntity], 'metadata')],
providers: [],
exports: [],
})
export class IndexFieldMetadataModule {}

View File

@ -10,7 +10,7 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-field-metadata/index-field-metadata.entity';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Entity('indexMetadata')

View File

@ -1,8 +0,0 @@
export const splitClassesAndStrings = <T>(
classesAndStrings: (string | T)[],
): [T[], string[]] => {
return [
classesAndStrings.filter((cls): cls is T => typeof cls !== 'string'),
classesAndStrings.filter((str): str is string => typeof str === 'string'),
];
};

View File

@ -1,15 +0,0 @@
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { camelCase } from 'src/utils/camel-case';
// Compute composite field metadata by combining the composite field metadata with the field metadata
export const computeCompositeFieldMetadata = (
compositeFieldMetadata: FieldMetadataInterface,
fieldMetadata: FieldMetadataEntity,
): FieldMetadataEntity => ({
...fieldMetadata,
...compositeFieldMetadata,
objectMetadataId: fieldMetadata.objectMetadataId,
name: camelCase(`${fieldMetadata.name}-${compositeFieldMetadata.name}`),
});

View File

@ -1,19 +0,0 @@
import { WorkspaceHealthIssueType } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-issue.interface';
export const isWorkspaceHealthNullableIssue = (
type: WorkspaceHealthIssueType,
): type is WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT => {
return type === WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT;
};
export const isWorkspaceHealthTypeIssue = (
type: WorkspaceHealthIssueType,
): type is WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT => {
return type === WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT;
};
export const isWorkspaceHealthDefaultValueIssue = (
type: WorkspaceHealthIssueType,
): type is WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_CONFLICT => {
return type === WorkspaceHealthIssueType.COLUMN_DEFAULT_VALUE_CONFLICT;
};

View File

@ -1,183 +0,0 @@
import { Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { Command, CommandRunner, Option } from 'nest-commander';
import { DataSource } from 'typeorm';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { StandardFieldFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory';
import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { computeStandardFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-fields.util';
interface RunCommandOptions {
workspaceId?: string;
}
@Command({
name: 'workspace:add-standard-id',
description: 'Add standard id to all metadata objects and fields',
})
export class AddStandardIdCommand extends CommandRunner {
private readonly logger = new Logger(AddStandardIdCommand.name);
constructor(
@InjectDataSource('metadata')
private readonly metadataDataSource: DataSource,
private readonly standardObjectFactory: StandardObjectFactory,
private readonly standardFieldFactory: StandardFieldFactory,
) {
super();
}
async run(_passedParam: string[], options: RunCommandOptions): Promise<void> {
const queryRunner = this.metadataDataSource.createQueryRunner();
const workspaceId = options.workspaceId;
await queryRunner.connect();
await queryRunner.startTransaction();
const manager = queryRunner.manager;
this.logger.log('Adding standardId to metadata objects and fields');
try {
const standardObjectMetadataCollection =
this.standardObjectFactory.create(
standardObjectMetadataDefinitions,
{
// We don't need to provide the workspace id and data source id as we're only adding standardId
workspaceId: '',
dataSourceId: '',
},
{
IS_BLOCKLIST_ENABLED: true,
IS_EVENT_OBJECT_ENABLED: true,
IS_AIRTABLE_INTEGRATION_ENABLED: true,
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
IS_STRIPE_INTEGRATION_ENABLED: false,
IS_COPILOT_ENABLED: false,
IS_MESSAGING_ALIAS_FETCHING_ENABLED: true,
IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true,
IS_FREE_ACCESS_ENABLED: false,
IS_FUNCTION_SETTINGS_ENABLED: false,
IS_WORKFLOW_ENABLED: false,
IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED: false,
},
);
const standardFieldMetadataCollection = this.standardFieldFactory.create(
CustomWorkspaceEntity,
{
workspaceId: '',
dataSourceId: '',
},
{
IS_BLOCKLIST_ENABLED: true,
IS_EVENT_OBJECT_ENABLED: true,
IS_AIRTABLE_INTEGRATION_ENABLED: true,
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
IS_STRIPE_INTEGRATION_ENABLED: false,
IS_COPILOT_ENABLED: false,
IS_MESSAGING_ALIAS_FETCHING_ENABLED: true,
IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true,
IS_FREE_ACCESS_ENABLED: false,
IS_FUNCTION_SETTINGS_ENABLED: false,
IS_WORKFLOW_ENABLED: false,
IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED: false,
},
);
const objectMetadataRepository =
manager.getRepository(ObjectMetadataEntity);
const fieldMetadataRepository =
manager.getRepository(FieldMetadataEntity);
/**
* Update all object metadata with standard id
*/
const updateObjectMetadataCollection: Partial<ObjectMetadataEntity>[] =
[];
const updateFieldMetadataCollection: Partial<FieldMetadataEntity>[] = [];
const originalObjectMetadataCollection =
await objectMetadataRepository.find({
where: {
fields: { isCustom: false },
workspaceId: workspaceId,
},
relations: ['fields'],
});
const customObjectMetadataCollection =
originalObjectMetadataCollection.filter(
(metadata) => metadata.isCustom,
);
const standardObjectMetadataMap = new Map(
standardObjectMetadataCollection.map((metadata) => [
metadata.nameSingular,
metadata,
]),
);
for (const originalObjectMetadata of originalObjectMetadataCollection) {
const standardObjectMetadata = standardObjectMetadataMap.get(
originalObjectMetadata.nameSingular,
);
if (!standardObjectMetadata && !originalObjectMetadata.isCustom) {
continue;
}
const computedStandardFieldMetadataCollection = computeStandardFields(
standardFieldMetadataCollection,
originalObjectMetadata,
customObjectMetadataCollection,
);
if (!originalObjectMetadata.isCustom) {
updateObjectMetadataCollection.push({
id: originalObjectMetadata.id,
standardId: originalObjectMetadata.standardId,
});
}
for (const fieldMetadata of originalObjectMetadata.fields) {
const standardFieldMetadata =
computedStandardFieldMetadataCollection.find(
(field) => field.name === fieldMetadata.name && !field.isCustom,
);
if (!standardFieldMetadata) {
continue;
}
updateFieldMetadataCollection.push({
id: fieldMetadata.id,
standardId: standardFieldMetadata.standardId,
});
}
}
await objectMetadataRepository.save(updateObjectMetadataCollection);
await fieldMetadataRepository.save(updateFieldMetadataCollection);
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error('Error adding standard id to metadata', error);
} finally {
await queryRunner.release();
}
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description: 'workspace id',
required: false,
})
parseWorkspaceId(value: string): string {
return value;
}
}

View File

@ -7,7 +7,6 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module';
import { AddStandardIdCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command';
import { ConvertRecordPositionsToIntegers } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/convert-record-positions-to-integers.command';
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
@ -27,7 +26,6 @@ import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.ser
],
providers: [
SyncWorkspaceMetadataCommand,
AddStandardIdCommand,
ConvertRecordPositionsToIntegers,
SyncWorkspaceLoggerService,
],

View File

@ -24,7 +24,7 @@ export type ParticipantWithId = Participant & {
id: string;
};
export type Attachment = {
type Attachment = {
id: string;
filename: string;
size: number;

View File

@ -1,4 +0,0 @@
export type GmailThread = {
id: string;
subject: string;
};

View File

@ -1,13 +0,0 @@
import { isDefined } from 'class-validator';
// temporary, to remove once domainName has been fully migrated to Links type
export const getCompanyDomainName = (company: any) => {
if (!isDefined(company.domainName)) {
return company.domainName;
}
if (typeof company.domainName === 'string') {
return company.domainName;
} else {
return company.domainName.primaryLinkUrl;
}
};