# Introduction In this PR we've migrated `twenty-shared` from a `vite` app [libary-mode](https://vite.dev/guide/build#library-mode) to a [preconstruct](https://preconstruct.tools/) "atomic" application ( in the future would like to introduce preconstruct to handle of all our atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be integrated at the monorepo's root directly, would be to invasive in the first, starting incremental via `twenty-shared`) For more information regarding the motivations please refer to nor: - https://github.com/twentyhq/core-team-issues/issues/587 - https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682 close https://github.com/twentyhq/core-team-issues/issues/589 close https://github.com/twentyhq/core-team-issues/issues/590 ## How to test In order to ease the review this PR will ship all the codegen at the very end, the actual meaning full diff is `+2,411 −114` In order to migrate existing dependent packages to `twenty-shared` multi barrel new arch you need to run in local: ```sh yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \ npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier ``` Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm included in the PR but should not be merged. ( such as codegen will be added before merging this script will be removed ) ## Misc - related opened issue preconstruct https://github.com/preconstruct/preconstruct/issues/617 ## Closed related PR - https://github.com/twentyhq/twenty/pull/11028 - https://github.com/twentyhq/twenty/pull/10993 - https://github.com/twentyhq/twenty/pull/10960 ## Upcoming enhancement: ( in others dedicated PRs ) - 1/ refactor generate barrel to export atomic module instead of `*` - 2/ generate barrel own package with several files and tests - 3/ Migration twenty-ui the same way - 4/ Use `preconstruct` at monorepo global level ## Conclusion As always any suggestions are welcomed !
228 lines
8.5 KiB
TypeScript
228 lines
8.5 KiB
TypeScript
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
|
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
|
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
|
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
|
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
|
|
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
|
|
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
|
|
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
|
|
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
|
|
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
|
import { GraphQLView } from '@/views/types/GraphQLView';
|
|
import { View } from '@/views/types/View';
|
|
import { ViewGroup } from '@/views/types/ViewGroup';
|
|
import { ViewSort } from '@/views/types/ViewSort';
|
|
import { ViewType } from '@/views/types/ViewType';
|
|
import { duplicateViewFiltersAndViewFilterGroups } from '@/views/utils/duplicateViewFiltersAndViewFilterGroups';
|
|
import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup';
|
|
import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter';
|
|
import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort';
|
|
import { useRecoilCallback } from 'recoil';
|
|
import { v4 } from 'uuid';
|
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
|
const currentViewIdCallbackState = useRecoilComponentCallbackStateV2(
|
|
contextStoreCurrentViewIdComponentState,
|
|
viewBarComponentId,
|
|
);
|
|
|
|
const { createOneRecord } = useCreateOneRecord<View>({
|
|
objectNameSingular: CoreObjectNameSingular.View,
|
|
});
|
|
|
|
const { createViewFieldRecords } = usePersistViewFieldRecords();
|
|
|
|
const { createViewSortRecords } = usePersistViewSortRecords();
|
|
|
|
const { createViewGroupRecords } = usePersistViewGroupRecords();
|
|
|
|
const { createViewFilterRecords } = usePersistViewFilterRecords();
|
|
|
|
const { createViewFilterGroupRecords } = usePersistViewFilterGroupRecords();
|
|
|
|
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
|
|
|
const { findManyRecords } = useLazyFindManyRecords({
|
|
objectNameSingular: CoreObjectNameSingular.View,
|
|
fetchPolicy: 'network-only',
|
|
});
|
|
|
|
const currentRecordFilterGroups = useRecoilComponentValueV2(
|
|
currentRecordFilterGroupsComponentState,
|
|
);
|
|
|
|
const currentRecordSorts = useRecoilComponentValueV2(
|
|
currentRecordSortsComponentState,
|
|
);
|
|
|
|
const currentRecordFilters = useRecoilComponentValueV2(
|
|
currentRecordFiltersComponentState,
|
|
);
|
|
|
|
const createViewFromCurrentView = useRecoilCallback(
|
|
({ snapshot, set }) =>
|
|
async (
|
|
{
|
|
id,
|
|
name,
|
|
icon,
|
|
kanbanFieldMetadataId,
|
|
type,
|
|
}: Partial<
|
|
Pick<
|
|
GraphQLView,
|
|
'id' | 'name' | 'icon' | 'kanbanFieldMetadataId' | 'type'
|
|
>
|
|
>,
|
|
shouldCopyFiltersAndSortsAndAggregate?: boolean,
|
|
) => {
|
|
const currentViewId = getSnapshotValue(
|
|
snapshot,
|
|
currentViewIdCallbackState,
|
|
);
|
|
|
|
if (!isDefined(currentViewId)) {
|
|
return;
|
|
}
|
|
|
|
const sourceView = snapshot
|
|
.getLoadable(
|
|
prefetchViewFromViewIdFamilySelector({
|
|
viewId: currentViewId,
|
|
}),
|
|
)
|
|
.getValue();
|
|
|
|
if (!isDefined(sourceView)) {
|
|
return;
|
|
}
|
|
|
|
set(isPersistingViewFieldsState, true);
|
|
|
|
const newView = await createOneRecord({
|
|
id: id ?? v4(),
|
|
name: name ?? sourceView.name,
|
|
icon: icon ?? sourceView.icon,
|
|
key: null,
|
|
kanbanFieldMetadataId:
|
|
kanbanFieldMetadataId ?? sourceView.kanbanFieldMetadataId,
|
|
kanbanAggregateOperation: shouldCopyFiltersAndSortsAndAggregate
|
|
? sourceView.kanbanAggregateOperation
|
|
: undefined,
|
|
kanbanAggregateOperationFieldMetadataId:
|
|
shouldCopyFiltersAndSortsAndAggregate
|
|
? sourceView.kanbanAggregateOperationFieldMetadataId
|
|
: undefined,
|
|
type: type ?? sourceView.type,
|
|
objectMetadataId: sourceView.objectMetadataId,
|
|
});
|
|
|
|
if (isUndefinedOrNull(newView)) {
|
|
throw new Error('Failed to create view');
|
|
}
|
|
|
|
await createViewFieldRecords(sourceView.viewFields, newView);
|
|
|
|
if (type === ViewType.Kanban) {
|
|
if (!isDefined(kanbanFieldMetadataId)) {
|
|
throw new Error('Kanban view must have a kanban field');
|
|
}
|
|
|
|
const viewGroupsToCreate =
|
|
objectMetadataItem.fields
|
|
?.find((field) => field.id === kanbanFieldMetadataId)
|
|
?.options?.map(
|
|
(option, index) =>
|
|
({
|
|
id: v4(),
|
|
__typename: 'ViewGroup',
|
|
fieldMetadataId: kanbanFieldMetadataId,
|
|
fieldValue: option.value,
|
|
isVisible: true,
|
|
position: index,
|
|
}) satisfies ViewGroup,
|
|
) ?? [];
|
|
|
|
viewGroupsToCreate.push({
|
|
__typename: 'ViewGroup',
|
|
id: v4(),
|
|
fieldValue: '',
|
|
position: viewGroupsToCreate.length,
|
|
isVisible: true,
|
|
fieldMetadataId: kanbanFieldMetadataId,
|
|
} satisfies ViewGroup);
|
|
|
|
await createViewGroupRecords({
|
|
viewGroupsToCreate,
|
|
viewId: newView.id,
|
|
});
|
|
}
|
|
|
|
if (shouldCopyFiltersAndSortsAndAggregate === true) {
|
|
const viewFilterGroupsToCopy = currentRecordFilterGroups.map(
|
|
(recordFilterGroup) =>
|
|
mapRecordFilterGroupToViewFilterGroup({
|
|
recordFilterGroup,
|
|
view: newView,
|
|
}),
|
|
);
|
|
|
|
const viewFiltersToCopy = currentRecordFilters.map(
|
|
mapRecordFilterToViewFilter,
|
|
);
|
|
|
|
const {
|
|
duplicatedViewFilterGroups: viewFilterGroupsToCreate,
|
|
duplicatedViewFilters: viewFiltersToCreate,
|
|
} = duplicateViewFiltersAndViewFilterGroups({
|
|
viewFilterGroupsToDuplicate: viewFilterGroupsToCopy,
|
|
viewFiltersToDuplicate: viewFiltersToCopy,
|
|
});
|
|
|
|
const viewSortsToCreate = currentRecordSorts
|
|
.map(mapRecordSortToViewSort)
|
|
.map(
|
|
(viewSort) =>
|
|
({
|
|
...viewSort,
|
|
id: v4(),
|
|
}) satisfies ViewSort,
|
|
);
|
|
|
|
await createViewFilterGroupRecords(viewFilterGroupsToCreate, newView);
|
|
await createViewFilterRecords(viewFiltersToCreate, newView);
|
|
await createViewSortRecords(viewSortsToCreate, newView);
|
|
}
|
|
|
|
await findManyRecords();
|
|
set(isPersistingViewFieldsState, false);
|
|
},
|
|
[
|
|
currentViewIdCallbackState,
|
|
createOneRecord,
|
|
createViewFieldRecords,
|
|
findManyRecords,
|
|
objectMetadataItem.fields,
|
|
createViewGroupRecords,
|
|
createViewSortRecords,
|
|
createViewFilterRecords,
|
|
createViewFilterGroupRecords,
|
|
currentRecordFilters,
|
|
currentRecordSorts,
|
|
currentRecordFilterGroups,
|
|
],
|
|
);
|
|
|
|
return { createViewFromCurrentView };
|
|
};
|