Refactor application module architecture for clarity and explicitness (#18432)
## Summary - **Module reorganization**: Moved `ApplicationUpgradeService` and cron jobs to `application-upgrade/`, `ApplicationSyncService` to `application-manifest/`, and `runWorkspaceMigration`/`uninstallApplication` mutations to the manifest resolver — each module now has a single clear responsibility. - **Explicit install flow**: Removed implicit `ApplicationEntity` creation from `ApplicationSyncService`. The install service and dev resolver now explicitly create the `ApplicationEntity` before syncing. npm packages are resolved at registration time to extract manifest metadata (universalIdentifier, name, description, etc.), eliminating the `reconcileUniversalIdentifier` hack. - **Better error handling**: Frontend hooks now surface actual server error messages in snackbars instead of swallowing them. Replaced the ugly `ConfirmationModal` for transfer ownership with a proper form modal. Fixed `SettingsAdminTableCard` row height overflow and corrected the `yarn-engine` asset path. ## Test plan - [ ] Register an npm package — verify manifest metadata (name, description, universalIdentifier) is extracted correctly - [ ] Install a registered npm app on a workspace — verify ApplicationEntity is created and sync succeeds - [ ] Test `app:dev` CLI flow — verify local app registration and sync work - [ ] Upload a tarball — verify registration and install flow - [ ] Transfer ownership — verify the new modal UX works - [ ] Verify error messages appear correctly in snackbars when operations fail Made with [Cursor](https://cursor.com)
This commit is contained in:
@@ -7,6 +7,7 @@ export const FIND_MANY_APPLICATIONS = gql`
|
||||
name
|
||||
description
|
||||
version
|
||||
universalIdentifier
|
||||
applicationRegistrationId
|
||||
applicationRegistration {
|
||||
id
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const FIND_ONE_APPLICATION_BY_UNIVERSAL_IDENTIFIER = gql`
|
||||
query FindOneApplicationByUniversalIdentifier($universalIdentifier: UUID!) {
|
||||
findOneApplication(universalIdentifier: $universalIdentifier) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
+1
@@ -50,6 +50,7 @@ export const MARKETPLACE_APP_FRAGMENT = gql`
|
||||
description
|
||||
}
|
||||
sourcePackage
|
||||
isFeatured
|
||||
defaultRole {
|
||||
id
|
||||
label
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const INSTALL_APPLICATION = gql`
|
||||
mutation InstallApplication($appRegistrationId: String!, $version: String) {
|
||||
installApplication(appRegistrationId: $appRegistrationId, version: $version)
|
||||
}
|
||||
`;
|
||||
@@ -1,7 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const INSTALL_NPM_APP = gql`
|
||||
mutation InstallNpmApp($packageName: String!, $version: String) {
|
||||
installNpmApp(packageName: $packageName, version: $version)
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const REGISTER_NPM_PACKAGE = gql`
|
||||
mutation RegisterNpmPackage($packageName: String!) {
|
||||
registerNpmPackage(packageName: $packageName) {
|
||||
id
|
||||
universalIdentifier
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { MARKETPLACE_APP_FRAGMENT } from '@/marketplace/graphql/fragments/marketplaceAppFragment';
|
||||
|
||||
export const FIND_ONE_MARKETPLACE_APP = gql`
|
||||
${MARKETPLACE_APP_FRAGMENT}
|
||||
query FindOneMarketplaceApp($universalIdentifier: String!) {
|
||||
findOneMarketplaceApp(universalIdentifier: $universalIdentifier) {
|
||||
...MarketplaceAppFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -26,9 +26,11 @@ export const useInstallApp = <TVariables extends Record<string, unknown>>(
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to install the application.`,
|
||||
message: graphqlMessage ?? t`Failed to install the application.`,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import { useInstallApp } from '~/modules/marketplace/hooks/useInstallApp';
|
||||
import { INSTALL_NPM_APP } from '~/modules/marketplace/graphql/mutations/installNpmApp';
|
||||
|
||||
export const useInstallNpmApp = () => {
|
||||
const [installNpmAppMutation] = useMutation(INSTALL_NPM_APP);
|
||||
|
||||
return useInstallApp<{
|
||||
packageName: string;
|
||||
version?: string;
|
||||
}>(installNpmAppMutation);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { REGISTER_NPM_PACKAGE } from '~/modules/marketplace/graphql/mutations/registerNpmPackage';
|
||||
|
||||
export const useRegisterNpmPackage = () => {
|
||||
const [registerNpmPackageMutation] = useMutation(REGISTER_NPM_PACKAGE);
|
||||
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||
const [isRegistering, setIsRegistering] = useState(false);
|
||||
|
||||
const register = async (params: {
|
||||
packageName: string;
|
||||
}): Promise<boolean> => {
|
||||
setIsRegistering(true);
|
||||
|
||||
try {
|
||||
const result = await registerNpmPackageMutation({
|
||||
variables: { packageName: params.packageName },
|
||||
});
|
||||
|
||||
const registration = result.data?.registerNpmPackage;
|
||||
|
||||
if (!isDefined(registration)) {
|
||||
enqueueErrorSnackBar({ message: t`Registration failed.` });
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Package registered successfully.`,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: graphqlMessage ?? t`Failed to register npm package.`,
|
||||
});
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
setIsRegistering(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { register, isRegistering };
|
||||
};
|
||||
@@ -29,9 +29,11 @@ export const useUpgradeApplication = () => {
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to upgrade the application.`,
|
||||
message: graphqlMessage ?? t`Failed to upgrade the application.`,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { UPLOAD_APP_TARBALL } from '~/modules/marketplace/graphql/mutations/uplo
|
||||
type UploadResult =
|
||||
| {
|
||||
success: true;
|
||||
registrationId: string;
|
||||
universalIdentifier: string;
|
||||
}
|
||||
| {
|
||||
@@ -29,7 +30,10 @@ export const useUploadAppTarball = () => {
|
||||
|
||||
const registration = result.data?.uploadAppTarball;
|
||||
|
||||
if (!isDefined(registration?.universalIdentifier)) {
|
||||
if (
|
||||
!isDefined(registration?.id) ||
|
||||
!isDefined(registration?.universalIdentifier)
|
||||
) {
|
||||
enqueueErrorSnackBar({ message: t`Upload failed.` });
|
||||
|
||||
return { success: false };
|
||||
@@ -37,11 +41,14 @@ export const useUploadAppTarball = () => {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
registrationId: registration.id,
|
||||
universalIdentifier: registration.universalIdentifier,
|
||||
};
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to upload tarball.`,
|
||||
message: graphqlMessage ?? t`Failed to upload tarball.`,
|
||||
});
|
||||
|
||||
return { success: false };
|
||||
|
||||
+36
-69
@@ -1,27 +1,17 @@
|
||||
import { useState } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
|
||||
import { GET_ADMIN_AI_MODELS } from '@/settings/admin-panel/ai/graphql/queries/getAdminAiModels';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
H2Title,
|
||||
IconArchive,
|
||||
IconFilter,
|
||||
IconPlug,
|
||||
IconRobot,
|
||||
IconSearch,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { H2Title, IconArchive, IconPlug, IconRobot } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import {
|
||||
useCreateDatabaseConfigVariableMutation,
|
||||
useGetAdminAiModelsQuery,
|
||||
@@ -30,17 +20,6 @@ import {
|
||||
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
|
||||
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const SettingsAdminAI = () => {
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -162,53 +141,41 @@ export const SettingsAdminAI = () => {
|
||||
description={t`Toggle model availability across all workspaces`}
|
||||
/>
|
||||
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="admin-model-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
<SearchInput
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
filterDropdown={(filterButton) => (
|
||||
<Dropdown
|
||||
dropdownId="admin-ai-models-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={filterButton}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconPlug}
|
||||
onToggleChange={() =>
|
||||
setShowUnconfigured(!showUnconfigured)
|
||||
}
|
||||
toggled={showUnconfigured}
|
||||
text={t`Unconfigured models`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowDeprecated(!showDeprecated)}
|
||||
toggled={showDeprecated}
|
||||
text={t`Deprecated models`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Dropdown
|
||||
dropdownId="admin-ai-models-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter`}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconPlug}
|
||||
onToggleChange={() =>
|
||||
setShowUnconfigured(!showUnconfigured)
|
||||
}
|
||||
toggled={showUnconfigured}
|
||||
text={t`Unconfigured models`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowDeprecated(!showDeprecated)}
|
||||
toggled={showDeprecated}
|
||||
text={t`Deprecated models`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Card rounded>
|
||||
{filteredModels.map((model, index) => (
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import { FIND_ALL_APPLICATION_REGISTRATIONS } from '@/settings/admin-panel/apps/graphql/queries/findAllApplicationRegistrations';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
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 { useQuery } from '@apollo/client';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { H2Title, Status } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type ApplicationRegistrationFragmentFragment } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledTableHeaderRowContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const TABLE_GRID = '1fr 1fr 100px 80px';
|
||||
|
||||
export const SettingsAdminApps = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const { data } = useQuery(FIND_ALL_APPLICATION_REGISTRATIONS);
|
||||
|
||||
const registrations: ApplicationRegistrationFragmentFragment[] =
|
||||
data?.findAllApplicationRegistrations ?? [];
|
||||
|
||||
const filtered =
|
||||
searchQuery.trim().length === 0
|
||||
? registrations
|
||||
: registrations.filter((registration) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
|
||||
return (
|
||||
registration.name.toLowerCase().includes(query) ||
|
||||
(registration.sourcePackage ?? '').toLowerCase().includes(query) ||
|
||||
registration.universalIdentifier.toLowerCase().includes(query)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`All App Registrations`}
|
||||
description={t`All application registrations across the platform, including orphaned marketplace apps`}
|
||||
/>
|
||||
<SearchInput
|
||||
placeholder={t`Search registrations...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
<StyledTableContainer>
|
||||
<Table>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow gridTemplateColumns={TABLE_GRID}>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Source`}</TableHeader>
|
||||
<TableHeader>{t`Listed`}</TableHeader>
|
||||
<TableHeader>{t`Featured`}</TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
<TableBody>
|
||||
{filtered.map((registration) => (
|
||||
<UndecoratedLink
|
||||
key={registration.id}
|
||||
to={getSettingsPath(
|
||||
SettingsPath.ApplicationRegistrationDetail,
|
||||
{ applicationRegistrationId: registration.id },
|
||||
)}
|
||||
fullWidth
|
||||
>
|
||||
<TableRow gridTemplateColumns={TABLE_GRID} isClickable>
|
||||
<TableCell>{registration.name}</TableCell>
|
||||
<TableCell>
|
||||
{registration.sourcePackage ?? registration.sourceType}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Status
|
||||
color={registration.isListed ? 'green' : 'gray'}
|
||||
text={registration.isListed ? t`Yes` : t`No`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Status
|
||||
color={registration.isFeatured ? 'yellow' : 'gray'}
|
||||
text={registration.isFeatured ? t`Yes` : t`No`}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</UndecoratedLink>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</StyledTableContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { APPLICATION_REGISTRATION_FRAGMENT } from '@/settings/application-registrations/graphql/fragments/applicationRegistrationFragment';
|
||||
|
||||
export const FIND_ALL_APPLICATION_REGISTRATIONS = gql`
|
||||
query FindAllApplicationRegistrations {
|
||||
findAllApplicationRegistrations {
|
||||
...ApplicationRegistrationFragment
|
||||
}
|
||||
}
|
||||
${APPLICATION_REGISTRATION_FRAGMENT}
|
||||
`;
|
||||
+7
@@ -5,6 +5,7 @@ import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/Setting
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
IconApps,
|
||||
IconHeart,
|
||||
IconSettings2,
|
||||
IconSparkles,
|
||||
@@ -24,6 +25,12 @@ export const SettingsAdminContent = () => {
|
||||
Icon: IconSettings2,
|
||||
disabled: !canAccessFullAdminPanel && !canImpersonate,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.APPS,
|
||||
title: t`Apps`,
|
||||
Icon: IconApps,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.AI,
|
||||
title: t`AI`,
|
||||
|
||||
+3
@@ -1,4 +1,5 @@
|
||||
import { SettingsAdminAI } from '@/settings/admin-panel/ai/components/SettingsAdminAI';
|
||||
import { SettingsAdminApps } from '@/settings/admin-panel/apps/components/SettingsAdminApps';
|
||||
import { SettingsAdminGeneral } from '@/settings/admin-panel/components/SettingsAdminGeneral';
|
||||
import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables';
|
||||
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
||||
@@ -16,6 +17,8 @@ export const SettingsAdminTabContent = () => {
|
||||
switch (activeTabId) {
|
||||
case SETTINGS_ADMIN_TABS.GENERAL:
|
||||
return <SettingsAdminGeneral />;
|
||||
case SETTINGS_ADMIN_TABS.APPS:
|
||||
return <SettingsAdminApps />;
|
||||
case SETTINGS_ADMIN_TABS.AI:
|
||||
return <SettingsAdminAI />;
|
||||
case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES:
|
||||
|
||||
+4
-3
@@ -45,13 +45,13 @@ export const SettingsAdminTableCard = ({
|
||||
<TableRow
|
||||
key={index + item.label}
|
||||
gridAutoColumns={gridAutoColumns}
|
||||
height={themeCssVariables.spacing[6]}
|
||||
>
|
||||
<TableCell
|
||||
align={labelAlign}
|
||||
color={themeCssVariables.font.color.tertiary}
|
||||
height={themeCssVariables.spacing[6]}
|
||||
height="auto"
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
padding={`${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
{item.Icon && <item.Icon size={theme.icon.size.md} />}
|
||||
<span>{item.label}</span>
|
||||
@@ -59,9 +59,10 @@ export const SettingsAdminTableCard = ({
|
||||
<TableCell
|
||||
align={valueAlign}
|
||||
color={themeCssVariables.font.color.primary}
|
||||
height={themeCssVariables.spacing[6]}
|
||||
height="auto"
|
||||
onClick={item.onClick}
|
||||
clickable={isDefined(item.onClick)}
|
||||
padding={`${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
{item.value}
|
||||
</TableCell>
|
||||
|
||||
+6
-17
@@ -1,11 +1,5 @@
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconSearch } from 'twenty-ui/display';
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
|
||||
type ConfigVariableSearchInputProps = {
|
||||
value: string;
|
||||
@@ -17,15 +11,10 @@ export const ConfigVariableSearchInput = ({
|
||||
onChange,
|
||||
}: ConfigVariableSearchInputProps) => {
|
||||
return (
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="config-variable-search"
|
||||
placeholder={t`Search config variables`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
autoFocus={false}
|
||||
LeftIcon={IconSearch}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search config variables`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const SETTINGS_ADMIN_TABS = {
|
||||
GENERAL: 'general',
|
||||
APPS: 'apps',
|
||||
AI: 'ai',
|
||||
CONFIG_VARIABLES: 'config-variables',
|
||||
HEALTH_STATUS: 'health-status',
|
||||
|
||||
+3
@@ -16,6 +16,9 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
|
||||
latestAvailableVersion
|
||||
websiteUrl
|
||||
termsUrl
|
||||
isListed
|
||||
isFeatured
|
||||
ownerWorkspaceId
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const TRANSFER_APPLICATION_REGISTRATION_OWNERSHIP = gql`
|
||||
mutation TransferApplicationRegistrationOwnership(
|
||||
$applicationRegistrationId: String!
|
||||
$targetWorkspaceSubdomain: String!
|
||||
) {
|
||||
transferApplicationRegistrationOwnership(
|
||||
applicationRegistrationId: $applicationRegistrationId
|
||||
targetWorkspaceSubdomain: $targetWorkspaceSubdomain
|
||||
) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const APPLICATION_REGISTRATION_TARBALL_URL = gql`
|
||||
query ApplicationRegistrationTarballUrl($id: String!) {
|
||||
applicationRegistrationTarballUrl(id: $id)
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user