Improve model
This commit is contained in:
@@ -356,7 +356,6 @@ export type ApplicationRegistration = {
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isFeatured: Scalars['Boolean'];
|
||||
isListed: Scalars['Boolean'];
|
||||
isProvenanceVerified: Scalars['Boolean'];
|
||||
latestAvailableVersion?: Maybe<Scalars['String']>;
|
||||
logoUrl?: Maybe<Scalars['String']>;
|
||||
@@ -1645,7 +1644,6 @@ export type FeatureFlag = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
|
||||
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED = 'IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED',
|
||||
IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
|
||||
@@ -4856,7 +4854,6 @@ export type UpdateApplicationRegistrationInput = {
|
||||
export type UpdateApplicationRegistrationPayload = {
|
||||
author?: InputMaybe<Scalars['String']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
isListed?: InputMaybe<Scalars['Boolean']>;
|
||||
logoUrl?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
oAuthRedirectUris?: InputMaybe<Array<Scalars['String']>>;
|
||||
@@ -6499,7 +6496,7 @@ export type GetAdminAiModelsQuery = { __typename?: 'Query', getAdminAiModels: {
|
||||
export type FindAllApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
|
||||
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isFeatured: boolean, provenanceRepositoryUrl?: string | null, isProvenanceVerified: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
|
||||
|
||||
export type CreateDatabaseConfigVariableMutationVariables = Exact<{
|
||||
key: Scalars['String'];
|
||||
@@ -6603,7 +6600,7 @@ export type GetSystemHealthStatusQueryVariables = Exact<{ [key: string]: never;
|
||||
|
||||
export type GetSystemHealthStatusQuery = { __typename?: 'Query', getSystemHealthStatus: { __typename?: 'SystemHealth', services: Array<{ __typename?: 'SystemHealthService', id: HealthIndicatorId, label: string, status: AdminPanelHealthServiceStatus }> } };
|
||||
|
||||
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
|
||||
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isFeatured: boolean, provenanceRepositoryUrl?: string | null, isProvenanceVerified: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
|
||||
|
||||
export type DeleteApplicationRegistrationMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -6632,7 +6629,7 @@ export type UpdateApplicationRegistrationMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
|
||||
export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isFeatured: boolean, provenanceRepositoryUrl?: string | null, isProvenanceVerified: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type UpdateApplicationRegistrationVariableMutationVariables = Exact<{
|
||||
input: UpdateApplicationRegistrationVariableInput;
|
||||
@@ -6672,14 +6669,14 @@ export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query',
|
||||
export type FindManyApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
|
||||
export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isFeatured: boolean, provenanceRepositoryUrl?: string | null, isProvenanceVerified: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
|
||||
|
||||
export type FindOneApplicationRegistrationQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
|
||||
export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isFeatured: boolean, provenanceRepositoryUrl?: string | null, isProvenanceVerified: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type UninstallApplicationMutationVariables = Exact<{
|
||||
universalIdentifier: Scalars['String'];
|
||||
@@ -8029,8 +8026,9 @@ export const ApplicationRegistrationFragmentFragmentDoc = gql`
|
||||
latestAvailableVersion
|
||||
websiteUrl
|
||||
termsUrl
|
||||
isListed
|
||||
isFeatured
|
||||
provenanceRepositoryUrl
|
||||
isProvenanceVerified
|
||||
ownerWorkspaceId
|
||||
createdAt
|
||||
updatedAt
|
||||
|
||||
+9
-3
@@ -65,7 +65,7 @@ export const SettingsAdminApps = () => {
|
||||
<TableRow gridTemplateColumns={TABLE_GRID}>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Source`}</TableHeader>
|
||||
<TableHeader>{t`Listed`}</TableHeader>
|
||||
<TableHeader>{t`Type`}</TableHeader>
|
||||
<TableHeader>{t`Featured`}</TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
@@ -86,8 +86,14 @@ export const SettingsAdminApps = () => {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Status
|
||||
color={registration.isListed ? 'green' : 'gray'}
|
||||
text={registration.isListed ? t`Yes` : t`No`}
|
||||
color={
|
||||
registration.sourceType === 'npm'
|
||||
? 'green'
|
||||
: registration.sourceType === 'tarball'
|
||||
? 'blue'
|
||||
: 'gray'
|
||||
}
|
||||
text={registration.sourceType.toUpperCase()}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
||||
+2
-1
@@ -16,8 +16,9 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
|
||||
latestAvailableVersion
|
||||
websiteUrl
|
||||
termsUrl
|
||||
isListed
|
||||
isFeatured
|
||||
provenanceRepositoryUrl
|
||||
isProvenanceVerified
|
||||
ownerWorkspaceId
|
||||
createdAt
|
||||
updatedAt
|
||||
|
||||
+25
-66
@@ -1,11 +1,7 @@
|
||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { UPDATE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/mutations/updateApplicationRegistration';
|
||||
import { FIND_APPLICATION_REGISTRATION_STATS } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationStats';
|
||||
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
||||
import { FIND_ONE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/queries/findOneApplicationRegistration';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
@@ -35,7 +31,6 @@ export const SettingsApplicationRegistrationDistributionTab = ({
|
||||
registration: ApplicationRegistrationData;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const applicationRegistrationId = registration.id;
|
||||
|
||||
@@ -47,32 +42,6 @@ export const SettingsApplicationRegistrationDistributionTab = ({
|
||||
skip: !applicationRegistrationId,
|
||||
});
|
||||
|
||||
const [updateRegistration] = useMutation(UPDATE_APPLICATION_REGISTRATION, {
|
||||
refetchQueries: [
|
||||
FIND_ONE_APPLICATION_REGISTRATION,
|
||||
FIND_MANY_APPLICATION_REGISTRATIONS,
|
||||
],
|
||||
});
|
||||
|
||||
const handleToggleListed = async () => {
|
||||
try {
|
||||
await updateRegistration({
|
||||
variables: {
|
||||
input: {
|
||||
id: applicationRegistrationId,
|
||||
update: {
|
||||
isListed: !registration.isListed,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Error updating marketplace listing`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const marketplacePageUrl = getSettingsPath(
|
||||
SettingsPath.AvailableApplicationDetail,
|
||||
{
|
||||
@@ -111,41 +80,31 @@ export const SettingsApplicationRegistrationDistributionTab = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Marketplace Listing`}
|
||||
description={t`Control visibility on the marketplace. Unlisted apps are still accessible via direct link.`}
|
||||
/>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
title={t`Listed on marketplace`}
|
||||
description={
|
||||
isNpmSource
|
||||
? t`Managed by the marketplace catalog sync for npm packages`
|
||||
: t`When enabled, this app appears in the marketplace browse page`
|
||||
}
|
||||
checked={registration.isListed}
|
||||
onChange={handleToggleListed}
|
||||
disabled={isNpmSource}
|
||||
divider
|
||||
{isNpmSource && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Marketplace Listing`}
|
||||
description={t`This app is listed on the marketplace because it is published to npm.`}
|
||||
/>
|
||||
<SettingsOptionCardContentToggle
|
||||
title={t`Featured`}
|
||||
description={t`Featured apps are curated. Open a PR to request featured status.`}
|
||||
checked={registration.isFeatured}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</Card>
|
||||
<StyledButtonGroup>
|
||||
<Button
|
||||
Icon={IconExternalLink}
|
||||
title={t`View marketplace page`}
|
||||
variant="secondary"
|
||||
to={marketplacePageUrl}
|
||||
/>
|
||||
</StyledButtonGroup>
|
||||
</Section>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
title={t`Featured`}
|
||||
description={t`Featured apps are curated. Open a PR to request featured status.`}
|
||||
checked={registration.isFeatured}
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
/>
|
||||
</Card>
|
||||
<StyledButtonGroup>
|
||||
<Button
|
||||
Icon={IconExternalLink}
|
||||
title={t`View marketplace page`}
|
||||
variant="secondary"
|
||||
to={marketplacePageUrl}
|
||||
/>
|
||||
</StyledButtonGroup>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{hasStats && (
|
||||
<Section>
|
||||
|
||||
+103
-46
@@ -3,9 +3,10 @@ import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-regi
|
||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
@@ -19,32 +20,22 @@ import {
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
type ApplicationRegistrationFragmentFragment,
|
||||
ApplicationRegistrationSourceType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
margin: ${themeCssVariables.spacing[2]} 0;
|
||||
`;
|
||||
|
||||
const StyledPublishMethodLabel = styled.div`
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledEmptyStateContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
type ApplicationRegistration = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
export const SettingsApplicationsDeveloperTab = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { t } = useLingui();
|
||||
@@ -55,9 +46,24 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
|
||||
const { data, loading } = useQuery(FIND_MANY_APPLICATION_REGISTRATIONS);
|
||||
|
||||
const registrations: ApplicationRegistration[] =
|
||||
const registrations: ApplicationRegistrationFragmentFragment[] =
|
||||
data?.findManyApplicationRegistrations ?? [];
|
||||
|
||||
const developmentApps = registrations.filter(
|
||||
(registration) =>
|
||||
registration.sourceType === ApplicationRegistrationSourceType.LOCAL,
|
||||
);
|
||||
|
||||
const publishedApps = registrations.filter(
|
||||
(registration) =>
|
||||
registration.sourceType === ApplicationRegistrationSourceType.NPM,
|
||||
);
|
||||
|
||||
const internalApps = registrations.filter(
|
||||
(registration) =>
|
||||
registration.sourceType === ApplicationRegistrationSourceType.TARBALL,
|
||||
);
|
||||
|
||||
const createCommands = [
|
||||
// oxlint-disable-next-line lingui/no-unlocalized-strings
|
||||
'npx create-twenty-app@latest my-twenty-app',
|
||||
@@ -116,6 +122,56 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const navigateToRegistration = (
|
||||
registration: ApplicationRegistrationFragmentFragment,
|
||||
) => {
|
||||
navigate(
|
||||
getSettingsPath(SettingsPath.ApplicationRegistrationDetail, {
|
||||
applicationRegistrationId: registration.id,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const renderAppList = (
|
||||
apps: ApplicationRegistrationFragmentFragment[],
|
||||
emptyState: React.ReactNode,
|
||||
) => {
|
||||
if (apps.length > 0) {
|
||||
return (
|
||||
<SettingsListCard
|
||||
items={apps}
|
||||
getItemLabel={(registration) => registration.name}
|
||||
isLoading={loading}
|
||||
RowIcon={IconApps}
|
||||
onRowClick={navigateToRegistration}
|
||||
RowRightComponent={() => (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return emptyState;
|
||||
};
|
||||
|
||||
const devCommands = [
|
||||
// oxlint-disable-next-line lingui/no-unlocalized-strings
|
||||
'npx twenty app:dev',
|
||||
];
|
||||
|
||||
const devCopyButton = (
|
||||
<Button
|
||||
onClick={() => {
|
||||
copyToClipboard(devCommands.join('\n'), t`Command copied to clipboard`);
|
||||
}}
|
||||
ariaLabel={t`Copy command`}
|
||||
Icon={IconCopy}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
@@ -140,48 +196,49 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`My Apps`}
|
||||
description={t`Apps you've created, registered, or published`}
|
||||
title={t`Your Development Apps`}
|
||||
description={t`Apps running in local development mode`}
|
||||
/>
|
||||
{registrations.length > 0 ? (
|
||||
<SettingsListCard
|
||||
items={registrations}
|
||||
getItemLabel={(registration) => registration.name}
|
||||
isLoading={loading}
|
||||
RowIcon={IconApps}
|
||||
onRowClick={(registration) => {
|
||||
navigate(
|
||||
getSettingsPath(SettingsPath.ApplicationRegistrationDetail, {
|
||||
applicationRegistrationId: registration.id,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
RowRightComponent={() => (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
{renderAppList(
|
||||
developmentApps,
|
||||
<StyledEmptyStateContainer>
|
||||
<CommandBlock commands={devCommands} button={devCopyButton} />
|
||||
</StyledEmptyStateContainer>,
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Your Published Apps`}
|
||||
description={t`Apps published to npm and available to all servers`}
|
||||
/>
|
||||
{renderAppList(
|
||||
publishedApps,
|
||||
<StyledEmptyStateContainer>
|
||||
<StyledPublishMethodLabel>
|
||||
{t`Publish to npm`}
|
||||
</StyledPublishMethodLabel>
|
||||
<CommandBlock
|
||||
commands={publishNpmCommands}
|
||||
button={publishNpmCopyButton}
|
||||
/>
|
||||
<StyledPublishMethodLabel>
|
||||
{t`Or push directly to your server`}
|
||||
</StyledPublishMethodLabel>
|
||||
</StyledEmptyStateContainer>,
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Your Internal Apps`}
|
||||
description={t`Apps deployed to this server via tarball. Install or upgrade from any workspace.`}
|
||||
/>
|
||||
{renderAppList(
|
||||
internalApps,
|
||||
<StyledEmptyStateContainer>
|
||||
<CommandBlock
|
||||
commands={publishServerCommands}
|
||||
button={publishServerCopyButton}
|
||||
/>
|
||||
</StyledEmptyStateContainer>
|
||||
</StyledEmptyStateContainer>,
|
||||
)}
|
||||
</Section>
|
||||
</>
|
||||
|
||||
-1
@@ -7,7 +7,6 @@ export type ApplicationRegistrationData = {
|
||||
universalIdentifier: string;
|
||||
sourceType: ApplicationRegistrationSourceType;
|
||||
sourcePackage?: string | null;
|
||||
isListed: boolean;
|
||||
isFeatured: boolean;
|
||||
oAuthClientId: string;
|
||||
oAuthScopes?: string[] | null;
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveApplicationRegistrationIsListed1772960000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveApplicationRegistrationIsListed1772960000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" DROP COLUMN IF EXISTS "isListed"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."applicationRegistration" ADD COLUMN "isListed" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE "core"."applicationRegistration" SET "isListed" = true WHERE "sourceType" = 'npm'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
-22
@@ -11,10 +11,6 @@ import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
|
||||
import { RunWorkspaceMigrationInput } from 'src/engine/core-modules/application/application-manifest/dtos/run-workspace-migration.input';
|
||||
@@ -29,7 +25,6 @@ import {
|
||||
} from 'src/engine/guards/feature-flag.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service';
|
||||
|
||||
@@ -43,7 +38,6 @@ export class ApplicationManifestResolver {
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly applicationSyncService: ApplicationSyncService,
|
||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@@ -53,22 +47,6 @@ export class ApplicationManifestResolver {
|
||||
@Args() { workspaceMigration: { actions } }: RunWorkspaceMigrationInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
const { featureFlagsMap } = await this.workspaceCacheService.getOrRecompute(
|
||||
workspaceId,
|
||||
['featureFlagsMap'],
|
||||
);
|
||||
|
||||
if (
|
||||
featureFlagsMap[
|
||||
FeatureFlagKey.IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED
|
||||
] !== true
|
||||
) {
|
||||
throw new ApplicationException(
|
||||
'Application installation from tarball is not enabled',
|
||||
ApplicationExceptionCode.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
|
||||
-2
@@ -38,7 +38,6 @@ export class MarketplaceCatalogSyncService {
|
||||
websiteUrl: entry.websiteUrl ?? null,
|
||||
termsUrl: entry.termsUrl ?? null,
|
||||
latestAvailableVersion: entry.richDisplayData.version ?? null,
|
||||
isListed: true,
|
||||
isFeatured: entry.isFeatured,
|
||||
marketplaceDisplayData: entry.richDisplayData,
|
||||
ownerWorkspaceId: null,
|
||||
@@ -93,7 +92,6 @@ export class MarketplaceCatalogSyncService {
|
||||
websiteUrl: app.websiteUrl ?? null,
|
||||
termsUrl: null,
|
||||
latestAvailableVersion: app.version ?? null,
|
||||
isListed: true,
|
||||
isFeatured: false,
|
||||
marketplaceDisplayData: null,
|
||||
ownerWorkspaceId: null,
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export class MarketplaceQueryService {
|
||||
}
|
||||
|
||||
const registrations =
|
||||
await this.applicationRegistrationService.findManyListed();
|
||||
await this.applicationRegistrationService.findManyNpm();
|
||||
|
||||
if (registrations.length === 0) {
|
||||
if (!this.hasSyncBeenEnqueued) {
|
||||
|
||||
-4
@@ -136,10 +136,6 @@ export class ApplicationRegistrationEntity {
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
termsUrl: string | null;
|
||||
|
||||
@Field(() => Boolean)
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isListed: boolean;
|
||||
|
||||
@Field(() => Boolean)
|
||||
@Column({ name: 'isFeatured', type: 'boolean', default: false })
|
||||
isFeatured: boolean;
|
||||
|
||||
+2
-7
@@ -220,8 +220,6 @@ export class ApplicationRegistrationService {
|
||||
updateData.oAuthScopes = update.oAuthScopes;
|
||||
if (isDefined(update.websiteUrl)) updateData.websiteUrl = update.websiteUrl;
|
||||
if (isDefined(update.termsUrl)) updateData.termsUrl = update.termsUrl;
|
||||
if (isDefined(update.isListed)) updateData.isListed = update.isListed;
|
||||
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await this.applicationRegistrationRepository.update(id, updateData);
|
||||
}
|
||||
@@ -276,7 +274,6 @@ export class ApplicationRegistrationService {
|
||||
| 'websiteUrl'
|
||||
| 'termsUrl'
|
||||
| 'latestAvailableVersion'
|
||||
| 'isListed'
|
||||
| 'isFeatured'
|
||||
| 'marketplaceDisplayData'
|
||||
| 'ownerWorkspaceId'
|
||||
@@ -328,7 +325,6 @@ export class ApplicationRegistrationService {
|
||||
websiteUrl: params.websiteUrl,
|
||||
termsUrl: params.termsUrl,
|
||||
latestAvailableVersion: params.latestAvailableVersion,
|
||||
isListed: params.isListed,
|
||||
isFeatured: params.isFeatured,
|
||||
marketplaceDisplayData: params.marketplaceDisplayData,
|
||||
oAuthClientId: v4(),
|
||||
@@ -389,7 +385,6 @@ export class ApplicationRegistrationService {
|
||||
sourceType: ApplicationRegistrationSourceType.NPM,
|
||||
sourcePackage: params.packageName,
|
||||
latestAvailableVersion: params.latestAvailableVersion,
|
||||
isListed: true,
|
||||
oAuthClientId: v4(),
|
||||
oAuthRedirectUris: [],
|
||||
oAuthScopes: [],
|
||||
@@ -411,9 +406,9 @@ export class ApplicationRegistrationService {
|
||||
});
|
||||
}
|
||||
|
||||
async findManyListed(): Promise<ApplicationRegistrationEntity[]> {
|
||||
async findManyNpm(): Promise<ApplicationRegistrationEntity[]> {
|
||||
return this.applicationRegistrationRepository.find({
|
||||
where: { isListed: true },
|
||||
where: { sourceType: ApplicationRegistrationSourceType.NPM },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -63,6 +63,10 @@ export class ApplicationTarballService {
|
||||
};
|
||||
}>(contentDir, 'manifest.json');
|
||||
|
||||
const packageJson = await readJsonFile<{
|
||||
version?: string;
|
||||
}>(contentDir, 'package.json');
|
||||
|
||||
if (manifest === null) {
|
||||
throw new ApplicationRegistrationException(
|
||||
'manifest.json not found or invalid in tarball',
|
||||
@@ -137,6 +141,9 @@ export class ApplicationTarballService {
|
||||
await this.appRegistrationRepository.update(appRegistration.id, {
|
||||
sourceType: ApplicationRegistrationSourceType.TARBALL,
|
||||
tarballFileId: savedFile.id,
|
||||
...(isDefined(packageJson?.version) && {
|
||||
latestAvailableVersion: packageJson.version,
|
||||
}),
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
|
||||
-6
@@ -4,7 +4,6 @@ import { Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@@ -66,11 +65,6 @@ export class UpdateApplicationRegistrationPayload {
|
||||
@MaxLength(2048)
|
||||
@IsOptional()
|
||||
termsUrl?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isListed?: boolean;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
||||
+2
-3
@@ -100,11 +100,10 @@ export class ApplicationUpgradeService {
|
||||
});
|
||||
|
||||
if (
|
||||
appRegistration.sourceType === ApplicationRegistrationSourceType.LOCAL ||
|
||||
appRegistration.sourceType === ApplicationRegistrationSourceType.TARBALL
|
||||
appRegistration.sourceType === ApplicationRegistrationSourceType.LOCAL
|
||||
) {
|
||||
throw new ApplicationException(
|
||||
'Cannot upgrade an app installed from a tarball or local source',
|
||||
'Cannot upgrade an app running in local development mode',
|
||||
ApplicationExceptionCode.UPGRADE_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
-1
@@ -241,7 +241,6 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: false,
|
||||
IS_NAVIGATION_MENU_ITEM_ENABLED: false,
|
||||
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED: false,
|
||||
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED: false,
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false,
|
||||
IS_MARKETPLACE_ENABLED: false,
|
||||
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED: false,
|
||||
|
||||
-20
@@ -4,23 +4,12 @@ import { installApplication } from 'test/integration/metadata/suites/application
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
const INVALID_UUID_APP_ID = uuidv4();
|
||||
const INVALID_UUID_ROLE_ID = uuidv4();
|
||||
|
||||
describe('Install application should fail when entity does not exist', () => {
|
||||
beforeAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag:
|
||||
FeatureFlagKey.IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED,
|
||||
value: true,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupApplicationForSync({
|
||||
applicationUniversalIdentifier: INVALID_UUID_APP_ID,
|
||||
@@ -30,15 +19,6 @@ describe('Install application should fail when entity does not exist', () => {
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterAll(async () => {
|
||||
await updateFeatureFlag({
|
||||
featureFlag:
|
||||
FeatureFlagKey.IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED,
|
||||
value: false,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await uninstallApplication({
|
||||
|
||||
@@ -3,7 +3,6 @@ export enum FeatureFlagKey {
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
|
||||
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED = 'IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED',
|
||||
IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED',
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
|
||||
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
|
||||
|
||||
Reference in New Issue
Block a user