mirror of
https://github.com/lingble/twenty.git
synced 2025-11-21 16:34:55 +00:00
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:
@@ -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 <></>;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
|
||||
export const activityTitleFamilyState = createFamilyState<
|
||||
string,
|
||||
{ activityId: string }
|
||||
>({
|
||||
key: 'activityTitleFamilyState',
|
||||
defaultValue: '',
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
|
||||
export const targetableObjectsInDrawerState = createState<
|
||||
ActivityTargetableObject[]
|
||||
>({
|
||||
key: 'targetableObjectsInDrawerState',
|
||||
defaultValue: [],
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
),
|
||||
],
|
||||
};
|
||||
@@ -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',
|
||||
});
|
||||
@@ -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',
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const currentUserDueTaskCountState = createState<number>({
|
||||
defaultValue: 0,
|
||||
key: 'currentUserDueTaskCountState',
|
||||
});
|
||||
@@ -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 <></>;
|
||||
};
|
||||
@@ -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';
|
||||
};
|
||||
@@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import { EventData, useEventTracker } from './useEventTracker';
|
||||
|
||||
export const useTrackEvent = (eventType: string, eventData: EventData) => {
|
||||
const eventTracker = useEventTracker();
|
||||
|
||||
return eventTracker(eventType, eventData);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
|
||||
export const favoritesState = createState<Favorite[]>({
|
||||
key: 'favoritesState',
|
||||
defaultValue: [],
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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}`,
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const useIsTasksPage = () => useLocation().pathname === '/tasks';
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export type Position = number | 'first' | 'last';
|
||||
@@ -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}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useCurrencyField } from '../../hooks/useCurrencyField';
|
||||
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
|
||||
export type CurrencyFieldInputProps = {
|
||||
type CurrencyFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useLinkField } from '../../hooks/useLinkField';
|
||||
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
|
||||
export type LinkFieldInputProps = {
|
||||
type LinkFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
IconBookmark,
|
||||
IconBookmarkPlus,
|
||||
|
||||
@@ -23,7 +23,7 @@ const StyledRelationPickerContainer = styled.div`
|
||||
top: -1px;
|
||||
`;
|
||||
|
||||
export type MultiSelectFieldInputProps = {
|
||||
type MultiSelectFieldInputProps = {
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const StyledRelationPickerContainer = styled.div`
|
||||
top: -1px;
|
||||
`;
|
||||
|
||||
export type SelectFieldInputProps = {
|
||||
type SelectFieldInputProps = {
|
||||
onSubmit?: FieldInputEvent;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const recordPositionInternalState = createState<number | null>({
|
||||
key: 'recordPositionInternalState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import { FieldDefinition } from './FieldDefinition';
|
||||
import { FieldMetadata } from './FieldMetadata';
|
||||
|
||||
export type FieldDefinitionSerializable = Omit<
|
||||
FieldDefinition<FieldMetadata>,
|
||||
'Icon'
|
||||
>;
|
||||
@@ -1 +0,0 @@
|
||||
export const CREATE_BUTTON_ID = 'create-button';
|
||||
@@ -1 +0,0 @@
|
||||
export const EMPTY_BUTTON_ID = 'empty-button';
|
||||
@@ -1,10 +0,0 @@
|
||||
export const getPreselectedIdIndex = (
|
||||
selectableOptionIds: string[],
|
||||
preselectedOptionId: string,
|
||||
) => {
|
||||
const preselectedIdIndex = selectableOptionIds.findIndex(
|
||||
(option) => option === preselectedOptionId,
|
||||
);
|
||||
|
||||
return preselectedIdIndex === -1 ? 0 : preselectedIdIndex;
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const mapToRecordId = (objectRecord: ObjectRecord) => {
|
||||
return objectRecord.id;
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const sortByObjectRecordId = (a: ObjectRecord, b: ObjectRecord) => {
|
||||
return a.id.localeCompare(b.id);
|
||||
};
|
||||
@@ -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,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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}`);
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
@@ -1,4 +0,0 @@
|
||||
export enum ViewSortDirection {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
];
|
||||
};
|
||||
@@ -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}`),
|
||||
});
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -24,7 +24,7 @@ export type ParticipantWithId = Participant & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type Attachment = {
|
||||
type Attachment = {
|
||||
id: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export type GmailThread = {
|
||||
id: string;
|
||||
subject: string;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user