Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42be30b681 |
+2
-10
@@ -6,13 +6,11 @@ import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelate
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
|
||||
import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useApolloClient } from '@apollo/client/react';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
FindAllViewsDocument,
|
||||
FindManyCommandMenuItemsDocument,
|
||||
FindAllRecordPageLayoutsDocument,
|
||||
@@ -57,9 +55,6 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
const client = useApolloClient();
|
||||
const store = useStore();
|
||||
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
const loadStaleMetadataEntities = useCallback(
|
||||
async (staleEntityKeys: MetadataEntityKey[]) => {
|
||||
@@ -199,10 +194,7 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
staleEntityKeys.includes('commandMenuItems') &&
|
||||
isCommandMenuItemEnabled
|
||||
) {
|
||||
if (staleEntityKeys.includes('commandMenuItems')) {
|
||||
fetchPromises.push(
|
||||
client
|
||||
.query({
|
||||
@@ -222,7 +214,7 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
await Promise.all(fetchPromises);
|
||||
applyChanges();
|
||||
},
|
||||
[client, store, replaceDraft, applyChanges, isCommandMenuItemEnabled],
|
||||
[client, store, replaceDraft, applyChanges],
|
||||
);
|
||||
|
||||
return { loadStaleMetadataEntities };
|
||||
|
||||
+23
-3
@@ -1,5 +1,6 @@
|
||||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel';
|
||||
import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList';
|
||||
import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect';
|
||||
@@ -10,6 +11,8 @@ import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutIn
|
||||
import { useReorderRecordPageLayoutTabs } from '@/page-layout/hooks/useReorderRecordPageLayoutTabs';
|
||||
import { PageLayoutMainContent } from '@/page-layout/PageLayoutMainContent';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { filterTabsByDeactivatedWidgetTypes } from '@/page-layout/utils/filterTabsByDeactivatedWidgetTypes';
|
||||
import { getDeactivatedActivityWidgetTypes } from '@/page-layout/utils/getDeactivatedActivityWidgetTypes';
|
||||
import { getScrollWrapperInstanceIdFromPageLayoutId } from '@/page-layout/utils/getScrollWrapperInstanceIdFromPageLayoutId';
|
||||
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
|
||||
import { getTabsByDisplayMode } from '@/page-layout/utils/getTabsByDisplayMode';
|
||||
@@ -30,7 +33,7 @@ import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey, WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div<{ hasPinnedTab: boolean }>`
|
||||
display: grid;
|
||||
@@ -90,6 +93,8 @@ export const PageLayoutTabsRenderer = () => {
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED,
|
||||
);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const metadataStore = useAtomFamilyStateValue(
|
||||
metadataStoreState,
|
||||
'objectMetadataItems',
|
||||
@@ -131,13 +136,28 @@ export const PageLayoutTabsRenderer = () => {
|
||||
isEditMode: isPageLayoutInEditMode,
|
||||
});
|
||||
|
||||
const deactivatedWidgetTypes =
|
||||
!isPageLayoutInEditMode &&
|
||||
isDefined(targetRecordIdentifier?.targetObjectNameSingular)
|
||||
? getDeactivatedActivityWidgetTypes({
|
||||
targetObjectNameSingular:
|
||||
targetRecordIdentifier.targetObjectNameSingular,
|
||||
objectMetadataItems,
|
||||
})
|
||||
: new Set<WidgetType>();
|
||||
|
||||
const tabsWithActiveWidgets = filterTabsByDeactivatedWidgetTypes({
|
||||
tabs: tabsWithVisibleWidgets,
|
||||
deactivatedWidgetTypes,
|
||||
});
|
||||
|
||||
const SYSTEM_OBJECT_TABS = ['Home', 'Timeline', 'Overview', 'Flow'];
|
||||
|
||||
const tabsForCurrentObject = isSystemObject
|
||||
? tabsWithVisibleWidgets.filter((tab) =>
|
||||
? tabsWithActiveWidgets.filter((tab) =>
|
||||
SYSTEM_OBJECT_TABS.includes(tab.title),
|
||||
)
|
||||
: tabsWithVisibleWidgets;
|
||||
: tabsWithActiveWidgets;
|
||||
|
||||
const { tabsToRenderInTabList, pinnedLeftTab } = getTabsByDisplayMode({
|
||||
tabs: tabsForCurrentObject,
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { type WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const filterTabsByDeactivatedWidgetTypes = ({
|
||||
tabs,
|
||||
deactivatedWidgetTypes,
|
||||
}: {
|
||||
tabs: PageLayoutTab[];
|
||||
deactivatedWidgetTypes: Set<WidgetType>;
|
||||
}): PageLayoutTab[] => {
|
||||
if (deactivatedWidgetTypes.size === 0) {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
return tabs
|
||||
.map((tab) => ({
|
||||
...tab,
|
||||
widgets: tab.widgets.filter(
|
||||
(widget) => !deactivatedWidgetTypes.has(widget.type),
|
||||
),
|
||||
}))
|
||||
.filter((tab) => tab.widgets.length > 0);
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
const ACTIVITY_WIDGET_TO_TARGET_OBJECT: Partial<
|
||||
Record<WidgetType, CoreObjectNameSingular>
|
||||
> = {
|
||||
[WidgetType.NOTES]: CoreObjectNameSingular.NoteTarget,
|
||||
[WidgetType.TASKS]: CoreObjectNameSingular.TaskTarget,
|
||||
};
|
||||
|
||||
export const getDeactivatedActivityWidgetTypes = ({
|
||||
targetObjectNameSingular,
|
||||
objectMetadataItems,
|
||||
}: {
|
||||
targetObjectNameSingular: string;
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
}): Set<WidgetType> => {
|
||||
const deactivatedWidgetTypes = new Set<WidgetType>();
|
||||
|
||||
const targetObjectMetadata = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectNameSingular,
|
||||
);
|
||||
|
||||
if (!isDefined(targetObjectMetadata)) {
|
||||
return deactivatedWidgetTypes;
|
||||
}
|
||||
|
||||
for (const [widgetType, activityTargetObjectName] of Object.entries(
|
||||
ACTIVITY_WIDGET_TO_TARGET_OBJECT,
|
||||
)) {
|
||||
const activityTargetObjectMetadata = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === activityTargetObjectName,
|
||||
);
|
||||
|
||||
if (!isDefined(activityTargetObjectMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for a relation field on the activity target object (e.g. NoteTarget)
|
||||
// that links to the current target object (e.g. Person).
|
||||
// In the legacy model, this is a RELATION field with targetObjectMetadata matching.
|
||||
// In the morph model, this is a field named after the target object (e.g. personId).
|
||||
const relationFieldToTarget = activityTargetObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.relation?.targetObjectMetadata.id === targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (isDefined(relationFieldToTarget) && !relationFieldToTarget.isActive) {
|
||||
deactivatedWidgetTypes.add(widgetType as WidgetType);
|
||||
}
|
||||
}
|
||||
|
||||
return deactivatedWidgetTypes;
|
||||
};
|
||||
+5
-46
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { createWriteStream } from 'fs';
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
SdkClientException,
|
||||
SdkClientExceptionCode,
|
||||
} from 'src/engine/core-modules/sdk-client/exceptions/sdk-client.exception';
|
||||
import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
@@ -30,15 +29,11 @@ const SDK_CLIENT_ARCHIVE_NAME = 'twenty-client-sdk.zip';
|
||||
|
||||
@Injectable()
|
||||
export class SdkClientArchiveService {
|
||||
private readonly logger = new Logger(SdkClientArchiveService.name);
|
||||
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
@Inject(forwardRef(() => SdkClientGenerationService))
|
||||
private readonly sdkClientGenerationService: SdkClientGenerationService,
|
||||
) {}
|
||||
|
||||
async downloadAndExtractToPackage({
|
||||
@@ -159,49 +154,13 @@ export class SdkClientArchiveService {
|
||||
error instanceof FileStorageException &&
|
||||
error.code === FileStorageExceptionCode.FILE_NOT_FOUND
|
||||
) {
|
||||
return this.generateAndRetryReadArchiveStream({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
throw new SdkClientException(
|
||||
`SDK client archive "${SDK_CLIENT_ARCHIVE_NAME}" not found for application "${applicationUniversalIdentifier}" in workspace "${workspaceId}".`,
|
||||
SdkClientExceptionCode.ARCHIVE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async generateAndRetryReadArchiveStream({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<Readable> {
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { workspaceId, universalIdentifier: applicationUniversalIdentifier },
|
||||
});
|
||||
|
||||
if (!application) {
|
||||
throw new SdkClientException(
|
||||
`SDK client archive "${SDK_CLIENT_ARCHIVE_NAME}" not found for application "${applicationUniversalIdentifier}" in workspace "${workspaceId}".`,
|
||||
SdkClientExceptionCode.ARCHIVE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`SDK client archive missing for application "${applicationUniversalIdentifier}", generating on-the-fly`,
|
||||
);
|
||||
|
||||
await this.sdkClientGenerationService.generateSdkClientForApplication({
|
||||
workspaceId,
|
||||
applicationId: application.id,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
return this.fileStorageService.readFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.GeneratedSdkClient,
|
||||
resourcePath: SDK_CLIENT_ARCHIVE_NAME,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+1027
-1027
File diff suppressed because it is too large
Load Diff
+1
@@ -4,4 +4,5 @@ export const DEFAULT_FEATURE_FLAGS = [
|
||||
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
|
||||
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
|
||||
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
] as const satisfies FeatureFlagKey[];
|
||||
|
||||
Reference in New Issue
Block a user