Display featured applications in marketplace (#22635)
## After <img width="1060" height="589" alt="image" src="https://github.com/user-attachments/assets/74dfadcf-8698-4404-81c6-b309cc4cbf79" /> <img width="732" alt="image" src="https://github.com/user-attachments/assets/0e1a3644-04bc-4208-aa77-3842d9db9cc8" /> <img width="797" alt="image" src="https://github.com/user-attachments/assets/0456ecce-607a-4705-8a89-c77029bfb6ac" /> - Remove IS_MARKETPLACE_SETTING_TAB_VISIBLE feature flag - add vetted toggle in admin app tab - added people data labs, last contact and call recorder to default vetted applications <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22635?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@twentyhq/call-recorder",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
|
||||
@@ -43,6 +43,7 @@ export default defineApplication({
|
||||
displayName: APP_DISPLAY_NAME,
|
||||
description: APP_DESCRIPTION,
|
||||
logoUrl: 'public/logo.svg',
|
||||
category: 'Productivity',
|
||||
author: 'Twenty',
|
||||
screenshots: ['public/gallery/call-recorder-cover.png'],
|
||||
applicationVariables: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@twentyhq/people-data-labs",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"description": "People Data Labs enrichment app for Twenty",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -7,6 +7,7 @@ export default defineApplication({
|
||||
displayName: 'People Data Labs',
|
||||
description: 'Enrich People and Companies with People Data Labs data.',
|
||||
logoUrl: 'public/people-data-labs-icon.png',
|
||||
category: 'Enrichment',
|
||||
author: 'Twenty',
|
||||
screenshots: ['public/gallery/cover.png'],
|
||||
serverVariables: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@twentyhq/last-contact",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
|
||||
@@ -10,6 +10,7 @@ export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
author: 'Twenty',
|
||||
category: 'Productivity',
|
||||
screenshots: ['public/gallery/cover.png'],
|
||||
displayName: APP_DISPLAY_NAME,
|
||||
description: APP_DESCRIPTION,
|
||||
|
||||
@@ -1752,7 +1752,6 @@ type FeatureFlag {
|
||||
enum FeatureFlagKey {
|
||||
IS_UNIQUE_INDEXES_ENABLED
|
||||
IS_JSON_FILTER_ENABLED
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE
|
||||
IS_EMAIL_GROUP_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT
|
||||
@@ -4326,6 +4325,7 @@ input UpdateApplicationRegistrationPayload {
|
||||
oAuthScopes: [String!]
|
||||
isListed: Boolean
|
||||
isPreInstalled: Boolean
|
||||
isFeatured: Boolean
|
||||
}
|
||||
|
||||
input CreateApplicationRegistrationVariableInput {
|
||||
|
||||
@@ -1396,7 +1396,7 @@ export interface FeatureFlag {
|
||||
__typename: 'FeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' | 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED' | 'IS_SETTINGS_DISCOVERY_HERO_ENABLED' | 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' | 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED' | 'IS_SETTINGS_DISCOVERY_HERO_ENABLED' | 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED'
|
||||
|
||||
export interface WorkspaceUrls {
|
||||
customUrl?: Scalars['String']
|
||||
@@ -6575,7 +6575,7 @@ export interface CreateApplicationRegistrationInput {name: Scalars['String'],uni
|
||||
|
||||
export interface UpdateApplicationRegistrationInput {id: Scalars['String'],update: UpdateApplicationRegistrationPayload}
|
||||
|
||||
export interface UpdateApplicationRegistrationPayload {name?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),isListed?: (Scalars['Boolean'] | null),isPreInstalled?: (Scalars['Boolean'] | null)}
|
||||
export interface UpdateApplicationRegistrationPayload {name?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),isListed?: (Scalars['Boolean'] | null),isPreInstalled?: (Scalars['Boolean'] | null),isFeatured?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface CreateApplicationRegistrationVariableInput {applicationRegistrationId: Scalars['String'],key: Scalars['String'],value: Scalars['String'],description?: (Scalars['String'] | null),isSecret?: (Scalars['Boolean'] | null)}
|
||||
|
||||
@@ -9238,7 +9238,6 @@ export const enumLogicFunctionExecutionStatus = {
|
||||
export const enumFeatureFlagKey = {
|
||||
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
|
||||
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const,
|
||||
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT: 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' as const,
|
||||
|
||||
@@ -11096,6 +11096,9 @@ export default {
|
||||
"isPreInstalled": [
|
||||
6
|
||||
],
|
||||
"isFeatured": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
|
||||
@@ -317,7 +317,6 @@ export enum FeatureFlagKey {
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED = 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED',
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE',
|
||||
IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED = 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED',
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT',
|
||||
IS_SETTINGS_DISCOVERY_HERO_ENABLED = 'IS_SETTINGS_DISCOVERY_HERO_ENABLED',
|
||||
@@ -834,6 +833,7 @@ export type UpdateApplicationRegistrationInput = {
|
||||
};
|
||||
|
||||
export type UpdateApplicationRegistrationPayload = {
|
||||
isFeatured?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isListed?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isPreInstalled?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
|
||||
@@ -1756,7 +1756,6 @@ export enum FeatureFlagKey {
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED = 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED',
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE',
|
||||
IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED = 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED',
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT',
|
||||
IS_SETTINGS_DISCOVERY_HERO_ENABLED = 'IS_SETTINGS_DISCOVERY_HERO_ENABLED',
|
||||
@@ -5347,6 +5346,7 @@ export type UpdateApplicationRegistrationInput = {
|
||||
};
|
||||
|
||||
export type UpdateApplicationRegistrationPayload = {
|
||||
isFeatured?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isListed?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
isPreInstalled?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
|
||||
+24
-1
@@ -1,7 +1,12 @@
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { AppTooltip, Card, TooltipDelay } from 'twenty-ui/surfaces';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { IconArrowBarToDown, IconPinned, IconReload } from 'twenty-ui/icon';
|
||||
import {
|
||||
IconArrowBarToDown,
|
||||
IconPinned,
|
||||
IconReload,
|
||||
IconShield,
|
||||
} from 'twenty-ui/icon';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { H2Title } from 'twenty-ui/typography';
|
||||
import { type ApplicationRegistration } from '~/generated-metadata/graphql';
|
||||
@@ -104,6 +109,24 @@ export const SettingsAdminApplicationRegistrationGeneralToggles = ({
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<Card rounded fullWidth>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconShield}
|
||||
title={t`Vetted`}
|
||||
description={t`Mark this app as reviewed and approved`}
|
||||
checked={registration.isFeatured}
|
||||
onChange={(checked) =>
|
||||
updateRegistration({
|
||||
variables: {
|
||||
input: {
|
||||
id: registration.id,
|
||||
update: { isFeatured: checked },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<Card rounded fullWidth>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconPinned}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
|
||||
import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
@@ -12,10 +11,7 @@ import { IconApps, IconCode, IconDownload, IconPlug } from 'twenty-ui/icon';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import coverDark from '~/pages/settings/applications/assets/cover-dark.png';
|
||||
import coverLight from '~/pages/settings/applications/assets/cover-light.png';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
PermissionFlagType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { PermissionFlagType } from '~/generated-metadata/graphql';
|
||||
import { SettingsApplicationsAvailableTab } from '~/pages/settings/applications/tabs/SettingsApplicationsAvailableTab';
|
||||
import { SettingsApplicationsDeveloperTab } from '~/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab';
|
||||
import { SettingsApplicationsInstalledTab } from '~/pages/settings/applications/tabs/SettingsApplicationsInstalledTab';
|
||||
@@ -30,14 +26,8 @@ export const SettingsApplications = () => {
|
||||
PermissionFlagType.API_KEYS_AND_WEBHOOKS,
|
||||
);
|
||||
|
||||
const isMarketplaceSettingTabVisible = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
|
||||
);
|
||||
|
||||
const tabs = [
|
||||
...(isMarketplaceSettingTabVisible
|
||||
? [{ id: 'marketplace', title: t`Marketplace`, Icon: IconDownload }]
|
||||
: []),
|
||||
{ id: 'marketplace', title: t`Marketplace`, Icon: IconDownload },
|
||||
{ id: 'installed', title: t`Installed`, Icon: IconApps },
|
||||
...(hasDeveloperAccess
|
||||
? [{ id: 'developer', title: t`Developer`, Icon: IconCode }]
|
||||
@@ -58,11 +48,7 @@ export const SettingsApplications = () => {
|
||||
case 'developer':
|
||||
return <SettingsApplicationsDeveloperTab />;
|
||||
default:
|
||||
return isMarketplaceSettingTabVisible ? (
|
||||
<SettingsApplicationsAvailableTab />
|
||||
) : (
|
||||
<SettingsApplicationsInstalledTab />
|
||||
);
|
||||
return <SettingsApplicationsAvailableTab />;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+54
-18
@@ -5,6 +5,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { InlineBanner } from 'twenty-ui/feedback';
|
||||
import { IconSparkles } from 'twenty-ui/icon';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
@@ -17,6 +18,16 @@ const StyledSearchInputContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledNotFeaturedContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledCardsGrid = styled.div`
|
||||
display: grid;
|
||||
gap: ${themeCssVariables.spacing[3]};
|
||||
@@ -56,14 +67,13 @@ export const SettingsApplicationsAvailableTab = () => {
|
||||
})
|
||||
: marketplaceApps;
|
||||
|
||||
const filteredApplications = showFeaturedOnly
|
||||
? textFilteredApplications.filter((application) => application.isFeatured)
|
||||
: textFilteredApplications;
|
||||
const featuredApplications = textFilteredApplications.filter(
|
||||
(application) => application.isFeatured,
|
||||
);
|
||||
|
||||
const nonFeaturedCount = showFeaturedOnly
|
||||
? textFilteredApplications.filter((application) => !application.isFeatured)
|
||||
.length
|
||||
: 0;
|
||||
const nonFeaturedApplications = textFilteredApplications.filter(
|
||||
(application) => !application.isFeatured,
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -74,7 +84,11 @@ export const SettingsApplicationsAvailableTab = () => {
|
||||
}
|
||||
|
||||
const showNonFeaturedHint =
|
||||
filteredApplications.length === 0 && nonFeaturedCount > 0;
|
||||
showFeaturedOnly &&
|
||||
featuredApplications.length === 0 &&
|
||||
nonFeaturedApplications.length > 0;
|
||||
|
||||
const hasNoApplications = textFilteredApplications.length === 0;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
@@ -109,10 +123,11 @@ export const SettingsApplicationsAvailableTab = () => {
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
|
||||
{filteredApplications.length === 0 ? (
|
||||
{hasNoApplications ||
|
||||
(showFeaturedOnly && featuredApplications.length === 0) ? (
|
||||
<SettingsEmptyPlaceholder padding="4">
|
||||
{showNonFeaturedHint
|
||||
? t`No featured applications found. ${nonFeaturedCount} non-featured result(s) available — `
|
||||
? t`No featured applications found. ${nonFeaturedApplications.length} non-featured result(s) available — `
|
||||
: t`No applications available`}
|
||||
{showNonFeaturedHint && (
|
||||
<StyledHintLink onClick={() => setShowFeaturedOnly(false)}>
|
||||
@@ -121,14 +136,35 @@ export const SettingsApplicationsAvailableTab = () => {
|
||||
)}
|
||||
</SettingsEmptyPlaceholder>
|
||||
) : (
|
||||
<StyledCardsGrid>
|
||||
{filteredApplications.map((application) => (
|
||||
<SettingsAvailableApplicationCard
|
||||
key={application.id}
|
||||
application={application}
|
||||
/>
|
||||
))}
|
||||
</StyledCardsGrid>
|
||||
<StyledContentContainer>
|
||||
{featuredApplications.length > 0 && (
|
||||
<StyledCardsGrid>
|
||||
{featuredApplications.map((application) => (
|
||||
<SettingsAvailableApplicationCard
|
||||
key={application.id}
|
||||
application={application}
|
||||
/>
|
||||
))}
|
||||
</StyledCardsGrid>
|
||||
)}
|
||||
|
||||
{!showFeaturedOnly && nonFeaturedApplications.length > 0 && (
|
||||
<StyledNotFeaturedContainer>
|
||||
<InlineBanner
|
||||
color={'danger'}
|
||||
message={t`Applications below are not vetted. Use at your own risk.`}
|
||||
/>
|
||||
<StyledCardsGrid>
|
||||
{nonFeaturedApplications.map((application) => (
|
||||
<SettingsAvailableApplicationCard
|
||||
key={application.id}
|
||||
application={application}
|
||||
/>
|
||||
))}
|
||||
</StyledCardsGrid>
|
||||
</StyledNotFeaturedContainer>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
|
||||
+2
-116
@@ -1,26 +1,18 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { SettingsEmptyPlaceholder } from '@/settings/components/SettingsEmptyPlaceholder';
|
||||
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 { useContext, useState } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { CommandBlock } from 'twenty-ui/data-display';
|
||||
import { InlineBanner } from 'twenty-ui/feedback';
|
||||
import { IconArrowUpRight, IconChevronRight, IconCopy } from 'twenty-ui/icon';
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/surfaces';
|
||||
import { H2Title } from 'twenty-ui/typography';
|
||||
import { Button, SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
@@ -29,14 +21,10 @@ import {
|
||||
FindManyApplicationRegistrationsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
|
||||
import {
|
||||
APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||
SettingsApplicationTableRow,
|
||||
} from '~/pages/settings/applications/components/SettingsApplicationTableRow';
|
||||
import { getApplicationDescriptionSummary } from '~/pages/settings/applications/utils/getApplicationDescriptionSummary';
|
||||
import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -53,43 +41,17 @@ const StyledTableRowsContainer = styled.div`
|
||||
padding: ${themeCssVariables.spacing[2]} 0;
|
||||
`;
|
||||
|
||||
const NPM_PACKAGES_GRID_COLUMNS = '200px 1fr 36px';
|
||||
|
||||
export const SettingsApplicationsDeveloperTab = () => {
|
||||
const { t } = useLingui();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
|
||||
const [displayNotVettedApps, setDisplayNotVettedApps] = useState(false);
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
|
||||
const { data } = useQuery(FindManyApplicationRegistrationsDocument);
|
||||
|
||||
const isMarketplaceSettingTabVisible = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
|
||||
);
|
||||
|
||||
const [marketplaceAppSearchTerm, setMarketplaceAppSearchTerm] = useState('');
|
||||
|
||||
const [myAppsSearchTerm, setMyAppsSearchTerm] = useState('');
|
||||
|
||||
const { data: marketplaceApps } = useMarketplaceApps();
|
||||
|
||||
const filteredMarketplaceApps = useMemo(() => {
|
||||
if (!marketplaceAppSearchTerm) {
|
||||
return marketplaceApps;
|
||||
}
|
||||
|
||||
const lowerSearch = marketplaceAppSearchTerm.toLowerCase();
|
||||
|
||||
return marketplaceApps.filter(
|
||||
(application) =>
|
||||
application.name.toLowerCase().includes(lowerSearch) ||
|
||||
application.description.toLowerCase().includes(lowerSearch),
|
||||
);
|
||||
}, [marketplaceApps, marketplaceAppSearchTerm]);
|
||||
|
||||
const registrations: ApplicationRegistrationListItemFragment[] =
|
||||
data?.findManyApplicationRegistrations ?? [];
|
||||
|
||||
@@ -191,82 +153,6 @@ export const SettingsApplicationsDeveloperTab = () => {
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{!isMarketplaceSettingTabVisible && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`NPM packages`}
|
||||
description={t`Apps made by other developers published on npm`}
|
||||
/>
|
||||
<InlineBanner
|
||||
color={'danger'}
|
||||
message={t`These apps are not vetted. Use at your own risk.`}
|
||||
button={{
|
||||
title: t`Access`,
|
||||
hidden: displayNotVettedApps,
|
||||
onClick: () => setDisplayNotVettedApps(true),
|
||||
}}
|
||||
/>
|
||||
{displayNotVettedApps && (
|
||||
<>
|
||||
<StyledSearchInputContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search an application`}
|
||||
value={marketplaceAppSearchTerm}
|
||||
onChange={setMarketplaceAppSearchTerm}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
{filteredMarketplaceApps.length === 0 ? (
|
||||
<SettingsEmptyPlaceholder>{t`No application found`}</SettingsEmptyPlaceholder>
|
||||
) : (
|
||||
<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>
|
||||
<ApplicationDisplay application={application} />
|
||||
</StyledNameTableCell>
|
||||
<TableCell
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
<OverflowingTextWithTooltip
|
||||
text={getApplicationDescriptionSummary(
|
||||
application.description,
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<StyledActionTableCell>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.light}
|
||||
/>
|
||||
</StyledActionTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</StyledTableRowsContainer>
|
||||
</Table>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
|
||||
|
||||
const FEATURED_UNIVERSAL_IDENTIFIERS = [
|
||||
'8da4b8b5-5edf-4880-b51f-ab6e679ec617',
|
||||
'4a1178c1-3535-4a47-b592-231d3216b36f',
|
||||
'66a504cc-0a75-410e-a43f-cdeae1db1522'
|
||||
];
|
||||
|
||||
@RegisteredInstanceCommand('2.19.0', 1783120000000, { type: 'slow' })
|
||||
export class BackfillIsFeaturedOnApplicationRegistrationSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
async runDataMigration(dataSource: DataSource): Promise<void> {
|
||||
await dataSource.query(
|
||||
`UPDATE "core"."applicationRegistration"
|
||||
SET "isFeatured" = true
|
||||
WHERE "universalIdentifier" = ANY($1::uuid[])`,
|
||||
[FEATURED_UNIVERSAL_IDENTIFIERS],
|
||||
);
|
||||
}
|
||||
|
||||
public async up(_queryRunner: QueryRunner): Promise<void> {}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`UPDATE "core"."applicationRegistration"
|
||||
SET "isFeatured" = false
|
||||
WHERE "universalIdentifier" = ANY($1::uuid[])`,
|
||||
[FEATURED_UNIVERSAL_IDENTIFIERS],
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -98,6 +98,7 @@ import { AddLogoToApplicationRegistrationFastInstanceCommand } from './2-19/2-19
|
||||
import { BackfillLogoOnApplicationRegistrationSlowInstanceCommand } from './2-19/2-19-instance-command-slow-1783069673191-backfill-logo-on-application-registration';
|
||||
import { AddDisplayFieldsToApplicationRegistrationFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783073776590-add-display-fields-to-application-registration';
|
||||
import { BackfillDisplayFieldsOnApplicationRegistrationSlowInstanceCommand } from './2-19/2-19-instance-command-slow-1783073776591-backfill-display-fields-on-application-registration';
|
||||
import { BackfillIsFeaturedOnApplicationRegistrationSlowInstanceCommand } from './2-19/2-19-instance-command-slow-1783120000000-backfill-is-featured-on-application-registration';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -198,4 +199,5 @@ export const INSTANCE_COMMANDS = [
|
||||
BackfillDisplayFieldsOnApplicationRegistrationSlowInstanceCommand,
|
||||
AddStatusToFileFastInstanceCommand,
|
||||
AddPendingMimeCheckToFileFastInstanceCommand,
|
||||
BackfillIsFeaturedOnApplicationRegistrationSlowInstanceCommand,
|
||||
];
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
export const MARKETPLACE_CURATED_APPLICATIONS: {
|
||||
universalIdentifier: string;
|
||||
position?: number;
|
||||
}[] = [];
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
export const MARKETPLACE_FEATURED_APPLICATIONS: {
|
||||
universalIdentifier: string;
|
||||
position?: number;
|
||||
}[] = [
|
||||
{
|
||||
universalIdentifier: '8da4b8b5-5edf-4880-b51f-ab6e679ec617',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
universalIdentifier: '4a1178c1-3535-4a47-b592-231d3216b36f',
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
universalIdentifier: '66a504cc-0a75-410e-a43f-cdeae1db1522',
|
||||
position: 3,
|
||||
},
|
||||
];
|
||||
+5
-5
@@ -32,7 +32,7 @@ import { ApplicationEntity } from 'src/engine/core-modules/application/applicati
|
||||
import { validateRedirectUri } from 'src/engine/core-modules/auth/utils/validate-redirect-uri.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
|
||||
import { MARKETPLACE_CURATED_APPLICATIONS } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-curated-applications.constant';
|
||||
import { MARKETPLACE_FEATURED_APPLICATIONS } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-featured-applications.constant';
|
||||
|
||||
const BCRYPT_SALT_ROUNDS = 10;
|
||||
|
||||
@@ -281,6 +281,7 @@ export class ApplicationRegistrationService {
|
||||
if (isDefined(update.isListed)) updateData.isListed = update.isListed;
|
||||
if (isDefined(update.isPreInstalled))
|
||||
updateData.isPreInstalled = update.isPreInstalled;
|
||||
if (isDefined(update.isFeatured)) updateData.isFeatured = update.isFeatured;
|
||||
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await this.applicationRegistrationRepository.update(id, updateData);
|
||||
@@ -383,13 +384,13 @@ export class ApplicationRegistrationService {
|
||||
params.universalIdentifier,
|
||||
);
|
||||
|
||||
const curatedIdentifiers = new Set(
|
||||
MARKETPLACE_CURATED_APPLICATIONS.map(
|
||||
const featuredIdentifiers = new Set(
|
||||
MARKETPLACE_FEATURED_APPLICATIONS.map(
|
||||
(entry) => entry.universalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const isFeatured = curatedIdentifiers.has(params.universalIdentifier);
|
||||
const isFeatured = featuredIdentifiers.has(params.universalIdentifier);
|
||||
|
||||
if (isDefined(existing)) {
|
||||
await this.applicationRegistrationRepository.save({
|
||||
@@ -400,7 +401,6 @@ export class ApplicationRegistrationService {
|
||||
latestAvailableVersion: params.latestAvailableVersion,
|
||||
manifest: params.manifest,
|
||||
...fromManifestApplicationToDisplayFields(params.manifest?.application),
|
||||
isFeatured,
|
||||
});
|
||||
} else {
|
||||
const registration = this.applicationRegistrationRepository.create({
|
||||
|
||||
+5
@@ -46,6 +46,11 @@ export class UpdateApplicationRegistrationPayload {
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isPreInstalled?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isFeatured?: boolean;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
||||
-1
@@ -236,7 +236,6 @@ describe('WorkspaceEntityManager', () => {
|
||||
featureFlagsMap: {
|
||||
IS_UNIQUE_INDEXES_ENABLED: false,
|
||||
IS_JSON_FILTER_ENABLED: false,
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE: false,
|
||||
IS_EMAIL_GROUP_ENABLED: false,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: false,
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT: false,
|
||||
|
||||
-5
@@ -35,11 +35,6 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_SETTINGS_DISCOVERY_HERO_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export enum FeatureFlagKey {
|
||||
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE',
|
||||
IS_EMAIL_GROUP_ENABLED = 'IS_EMAIL_GROUP_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT',
|
||||
|
||||
Reference in New Issue
Block a user