Add npm packages app settings list (#19324)
solves https://discord.com/channels/1130383047699738754/1488499318762635344
This commit is contained in:
@@ -1724,6 +1724,7 @@ enum FeatureFlagKey {
|
||||
IS_AI_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
IS_MARKETPLACE_ENABLED
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
|
||||
IS_PUBLIC_DOMAIN_ENABLED
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
|
||||
@@ -1424,7 +1424,7 @@ export interface PublicFeatureFlag {
|
||||
__typename: 'PublicFeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
|
||||
export interface ClientConfigMaintenanceMode {
|
||||
startAt: Scalars['DateTime']
|
||||
@@ -9092,6 +9092,7 @@ export const enumFeatureFlagKey = {
|
||||
IS_AI_ENABLED: 'IS_AI_ENABLED' as const,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
|
||||
IS_MARKETPLACE_ENABLED: 'IS_MARKETPLACE_ENABLED' as const,
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
|
||||
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
|
||||
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
|
||||
|
||||
@@ -1729,6 +1729,7 @@ export enum FeatureFlagKey {
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED',
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE',
|
||||
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconApps, IconCode, IconDownload } from 'twenty-ui/display';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
type FeatureFlagKey,
|
||||
FeatureFlagKey,
|
||||
PermissionFlagType,
|
||||
FindManyApplicationsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@@ -33,6 +33,10 @@ export const SettingsApplications = () => {
|
||||
'IS_MARKETPLACE_ENABLED' as FeatureFlagKey,
|
||||
);
|
||||
|
||||
const isMarketplaceSettingTabVisible = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
|
||||
);
|
||||
|
||||
const activeTabId = useAtomComponentStateValue(
|
||||
activeTabIdComponentState,
|
||||
APPLICATIONS_TAB_LIST_ID,
|
||||
@@ -65,7 +69,9 @@ export const SettingsApplications = () => {
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: 'marketplace', title: t`Marketplace`, Icon: IconDownload },
|
||||
...(isMarketplaceSettingTabVisible
|
||||
? [{ id: 'marketplace', title: t`Marketplace`, Icon: IconDownload }]
|
||||
: []),
|
||||
{ id: 'installed', title: t`Installed`, Icon: IconApps },
|
||||
...(hasDeveloperAccess
|
||||
? [{ id: 'developer', title: t`Developer`, Icon: IconCode }]
|
||||
@@ -81,7 +87,11 @@ export const SettingsApplications = () => {
|
||||
case 'developer':
|
||||
return <SettingsApplicationsDeveloperTab />;
|
||||
default:
|
||||
return <SettingsApplicationsAvailableTab />;
|
||||
return isMarketplaceSettingTabVisible ? (
|
||||
<SettingsApplicationsAvailableTab />
|
||||
) : (
|
||||
<SettingsApplicationsInstalledTab />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,11 +107,7 @@ export const SettingsApplications = () => {
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
componentInstanceId={APPLICATIONS_TAB_LIST_ID}
|
||||
behaveAsLinks={false}
|
||||
/>
|
||||
<TabList tabs={tabs} componentInstanceId={APPLICATIONS_TAB_LIST_ID} />
|
||||
{renderActiveTabContent()}
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
|
||||
+41
-40
@@ -22,8 +22,9 @@ const StyledTableContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledTableHeaderRowContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
const StyledTableRowsContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
padding: ${themeCssVariables.spacing[2]} 0;
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
@@ -64,47 +65,47 @@ export const SettingsApplicationsTable = ({
|
||||
</StyledSearchInputContainer>
|
||||
<StyledTableContainer>
|
||||
<Table>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow
|
||||
gridTemplateColumns={APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader> {t`Name`}</TableHeader>
|
||||
<TableHeader> {t`Description`}</TableHeader>
|
||||
<TableHeader> {''}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
{filteredApplications.map((application) => {
|
||||
const isNpmApp =
|
||||
application.applicationRegistration?.sourceType ===
|
||||
ApplicationRegistrationSourceType.NPM;
|
||||
<TableRow
|
||||
gridTemplateColumns={APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader> {t`Name`}</TableHeader>
|
||||
<TableHeader> {t`Description`}</TableHeader>
|
||||
<TableHeader> {''}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
<StyledTableRowsContainer>
|
||||
{filteredApplications.map((application) => {
|
||||
const isNpmApp =
|
||||
application.applicationRegistration?.sourceType ===
|
||||
ApplicationRegistrationSourceType.NPM;
|
||||
|
||||
const latestVersion =
|
||||
application.applicationRegistration?.latestAvailableVersion;
|
||||
const latestVersion =
|
||||
application.applicationRegistration?.latestAvailableVersion;
|
||||
|
||||
const hasUpdate =
|
||||
isNpmApp &&
|
||||
isDefined(latestVersion) &&
|
||||
isDefined(application.version) &&
|
||||
isNewerSemver(latestVersion, application.version);
|
||||
const hasUpdate =
|
||||
isNpmApp &&
|
||||
isDefined(latestVersion) &&
|
||||
isDefined(application.version) &&
|
||||
isNewerSemver(latestVersion, application.version);
|
||||
|
||||
return (
|
||||
<SettingsApplicationTableRow
|
||||
key={application.id}
|
||||
application={application}
|
||||
hasUpdate={hasUpdate}
|
||||
action={
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
link={getSettingsPath(SettingsPath.ApplicationDetail, {
|
||||
applicationId: application.id,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<SettingsApplicationTableRow
|
||||
key={application.id}
|
||||
application={application}
|
||||
hasUpdate={hasUpdate}
|
||||
action={
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
link={getSettingsPath(SettingsPath.ApplicationDetail, {
|
||||
applicationId: application.id,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledTableRowsContainer>
|
||||
</Table>
|
||||
</StyledTableContainer>
|
||||
</Section>
|
||||
|
||||
+129
-5
@@ -1,34 +1,68 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
||||
import {
|
||||
StyledActionTableCell,
|
||||
StyledNameTableCell,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import {
|
||||
Avatar,
|
||||
CommandBlock,
|
||||
H2Title,
|
||||
IconApps,
|
||||
IconChevronRight,
|
||||
IconCopy,
|
||||
IconFileInfo,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Button, SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import {
|
||||
type ApplicationRegistrationFragmentFragment,
|
||||
ApplicationRegistrationSourceType,
|
||||
FeatureFlagKey,
|
||||
FindManyApplicationRegistrationsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: ${themeCssVariables.spacing[2]} 0;
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledEmptyState = styled.div`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
padding: ${themeCssVariables.spacing[8]};
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledTableRowsContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
padding: ${themeCssVariables.spacing[2]} 0;
|
||||
`;
|
||||
|
||||
const NPM_PACKAGES_GRID_COLUMNS = '200px 1fr 36px';
|
||||
|
||||
const SOURCE_TYPE_BADGE_CONFIG: Record<
|
||||
ApplicationRegistrationSourceType,
|
||||
{ label: string; color: 'gray' | 'blue' | 'green' }
|
||||
@@ -53,12 +87,34 @@ const SOURCE_TYPE_BADGE_CONFIG: Record<
|
||||
|
||||
export const SettingsApplicationsDeveloperTab = () => {
|
||||
const { t } = useLingui();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
|
||||
const { data, loading } = useQuery(FindManyApplicationRegistrationsDocument);
|
||||
|
||||
const isMarketplaceSettingTabVisible = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
|
||||
);
|
||||
|
||||
const [npmSearchTerm, setNpmSearchTerm] = useState('');
|
||||
const { data: marketplaceApps } = useMarketplaceApps();
|
||||
|
||||
const filteredMarketplaceApps = useMemo(() => {
|
||||
if (!npmSearchTerm) {
|
||||
return marketplaceApps;
|
||||
}
|
||||
|
||||
const lowerSearch = npmSearchTerm.toLowerCase();
|
||||
|
||||
return marketplaceApps.filter(
|
||||
(application) =>
|
||||
application.name.toLowerCase().includes(lowerSearch) ||
|
||||
application.description.toLowerCase().includes(lowerSearch),
|
||||
);
|
||||
}, [marketplaceApps, npmSearchTerm]);
|
||||
|
||||
const registrations: ApplicationRegistrationFragmentFragment[] =
|
||||
data?.findManyApplicationRegistrations ?? [];
|
||||
|
||||
@@ -123,8 +179,8 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Create & Develop`}
|
||||
description={t`Scaffold a new app, then use the CLI to develop, publish, and distribute`}
|
||||
title={t`Create an application`}
|
||||
description={t`You can either create a private app or share it to others`}
|
||||
/>
|
||||
<CommandBlock commands={createCommands} button={createCopyButton} />
|
||||
<StyledButtonContainer>
|
||||
@@ -146,7 +202,7 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Your Apps`}
|
||||
title={t`Your apps`}
|
||||
description={t`All applications registered on this workspace`}
|
||||
/>
|
||||
{registrations.length > 0 ? (
|
||||
@@ -164,6 +220,74 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
)
|
||||
)}
|
||||
</Section>
|
||||
|
||||
{!isMarketplaceSettingTabVisible && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`NPM packages`}
|
||||
description={t`Apps made by other developers published on npm`}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search an application`}
|
||||
value={npmSearchTerm}
|
||||
onChange={setNpmSearchTerm}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
{filteredMarketplaceApps.length === 0 ? (
|
||||
<StyledEmptyState>{t`No application found`}</StyledEmptyState>
|
||||
) : (
|
||||
<Table>
|
||||
<TableRow gridAutoColumns={NPM_PACKAGES_GRID_COLUMNS}>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Description`}</TableHeader>
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
<StyledTableRowsContainer>
|
||||
{filteredMarketplaceApps.map((application) => (
|
||||
<TableRow
|
||||
key={application.id}
|
||||
gridAutoColumns={NPM_PACKAGES_GRID_COLUMNS}
|
||||
to={getSettingsPath(
|
||||
SettingsPath.AvailableApplicationDetail,
|
||||
{
|
||||
availableApplicationId: application.id,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<StyledNameTableCell>
|
||||
<Avatar
|
||||
avatarUrl={application.logo || null}
|
||||
placeholder={application.name}
|
||||
placeholderColorSeed={application.name}
|
||||
size="md"
|
||||
type="squared"
|
||||
/>
|
||||
<OverflowingTextWithTooltip text={application.name} />
|
||||
</StyledNameTableCell>
|
||||
<TableCell
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
<OverflowingTextWithTooltip
|
||||
text={application.description}
|
||||
/>
|
||||
</TableCell>
|
||||
<StyledActionTableCell>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</StyledActionTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</StyledTableRowsContainer>
|
||||
</Table>
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
@@ -234,6 +234,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_JSON_FILTER_ENABLED: false,
|
||||
IS_AI_ENABLED: false,
|
||||
IS_MARKETPLACE_ENABLED: false,
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE: false,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false,
|
||||
IS_PUBLIC_DOMAIN_ENABLED: false,
|
||||
IS_EMAILING_DOMAIN_ENABLED: false,
|
||||
|
||||
+5
@@ -50,6 +50,11 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
@@ -4,6 +4,7 @@ export enum FeatureFlagKey {
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED',
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE',
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
|
||||
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
|
||||
@@ -5,7 +5,7 @@ import { themeCssVariables } from '@ui/theme-constants';
|
||||
const StyledContainer = styled.div`
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
background: ${themeCssVariables.background.transparent.secondary};
|
||||
background: ${themeCssVariables.background.secondary};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: ${themeCssVariables.spacing[3]};
|
||||
|
||||
Reference in New Issue
Block a user