Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TEST] License Add-ons #33585

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dcd7b3c
addon chip
tiagoevanp Oct 7, 2024
877da52
modals
tiagoevanp Oct 7, 2024
5ec82d7
fix modal link
tiagoevanp Oct 7, 2024
d446ab0
Function to determine if the app requires add-on
d-gubert Oct 8, 2024
35b1d91
fix lint
tiagoevanp Oct 8, 2024
4d8bd7b
remove TODO in useAppMenu
tiagoevanp Oct 8, 2024
acbc40b
Merge branch 'develop' into feat/apps-add-on
tiagoevanp Oct 9, 2024
80ec181
fix cloud url link
tiagoevanp Oct 9, 2024
25d297d
Merge branch 'feat/apps-add-on' of github.com:RocketChat/Rocket.Chat …
tiagoevanp Oct 9, 2024
615f9d5
introduce apps info callout for add-ons
tiagoevanp Oct 9, 2024
5137299
layout adjustment
tiagoevanp Oct 9, 2024
6bab4da
Merge branch 'feat/apps-add-on-2' of github.com:RocketChat/Rocket.Cha…
tiagoevanp Oct 10, 2024
7428884
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
tiagoevanp Oct 10, 2024
650b73f
addon conference [WIP]
tiagoevanp Oct 10, 2024
b587df4
wip+
d-gubert Oct 10, 2024
25cbcb5
Create tidy-suns-move.md
tiagoevanp Oct 15, 2024
30ad2e3
Apply suggestions from code review
tiagoevanp Oct 15, 2024
83157a6
change addon check conferences for modal
tiagoevanp Oct 15, 2024
33364d7
Merge branch 'feat/apps-add-on' of github.com:RocketChat/Rocket.Chat …
tiagoevanp Oct 15, 2024
4707eca
fix categories undefined
tiagoevanp Oct 16, 2024
886b1bf
fix add-on modal loading unnecessary
tiagoevanp Oct 16, 2024
f462ad4
fix reviews
tiagoevanp Oct 16, 2024
c4861c2
TODO useAppMenu
tiagoevanp Oct 16, 2024
bd80b7f
fix inverse logic
tiagoevanp Oct 16, 2024
8f4be9f
qa review
tiagoevanp Oct 17, 2024
f10bfe6
console log
tiagoevanp Oct 17, 2024
1add488
Update apps/meteor/client/views/marketplace/AppsList/AddonChip.tsx
tiagoevanp Oct 18, 2024
a465b29
admin check
tiagoevanp Oct 18, 2024
6b09d44
Merge branch 'feat/apps-add-on' of github.com:RocketChat/Rocket.Chat …
tiagoevanp Oct 18, 2024
686494b
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
tiagoevanp Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/tidy-suns-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": feat
"@rocket.chat/i18n": feat
---

Introduces new visual components into marketplace pages to inform an add-on necessity into the workspace.
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const CONTACT_SALES_LINK = 'https://go.rocket.chat/i/contact-sales-produc
export const PRICING_LINK = 'https://go.rocket.chat/i/pricing-product';
export const DOWNGRADE_LINK = 'https://go.rocket.chat/i/docs-downgrade';
export const TRIAL_LINK = 'https://go.rocket.chat/i/docs-trial';
export const GET_ADDONS_LINK = 'https://go.rocket.chat/i/get-addons';
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Box, Callout, Chip, Margins } from '@rocket.chat/fuselage';
import type { App } from '@rocket.chat/core-typings';
import { Box, Button, Callout, Chip, Margins } from '@rocket.chat/fuselage';
import { ExternalLink } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import DOMPurify from 'dompurify';
import React from 'react';
import { useTranslation } from 'react-i18next';

import { useExternalLink } from '../../../../../hooks/useExternalLink';
import { useHasLicenseModule } from '../../../../../hooks/useHasLicenseModule';
import { GET_ADDONS_LINK } from '../../../../admin/subscription/utils/links';
import ScreenshotCarouselAnchor from '../../../components/ScreenshotCarouselAnchor';
import type { AppInfo } from '../../../definitions/AppInfo';
import { purifyOptions } from '../../../lib/purifyOptions';
Expand Down Expand Up @@ -37,18 +41,37 @@ const AppDetails = ({ app }: AppDetailsProps) => {
const normalizedSupportUrl = support ? normalizeUrl(support) : undefined;
const normalizedDocumentationUrl = documentation ? normalizeUrl(documentation) : undefined;

const appAddon = (app as App).addon;
const workspaceHasAddon = useHasLicenseModule(appAddon);

const openExternalLink = useExternalLink();

return (
<Box maxWidth='x640' w='full' marginInline='auto' color='default'>
<Box mbs='36px' maxWidth='x640' w='full' marginInline='auto' color='default'>
{appAddon && !workspaceHasAddon && (
<Callout
mb={16}
title={t('Subscription_add-on_required')}
type='info'
actions={
<Button small onClick={() => openExternalLink(GET_ADDONS_LINK)}>
{t('Contact_sales')}
</Button>
}
>
{t('App_cannot_be_enabled_without_add-on')}
</Callout>
)}
{app.licenseValidation && (
<>
{Object.entries(app.licenseValidation.warnings).map(([key]) => (
<Callout key={key} type='warning'>
<Callout key={key} type='warning' mb={16}>
{t(`Apps_License_Message_${key}` as TranslationKey)}
</Callout>
))}

{Object.entries(app.licenseValidation.errors).map(([key]) => (
<Callout key={key} type='danger'>
<Callout key={key} type='danger' mb={16}>
{t(`Apps_License_Message_${key}` as TranslationKey)}
</Callout>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import type { ReactElement } from 'react';
import React, { useCallback, useState, memo } from 'react';
import semver from 'semver';

import { useHasLicenseModule } from '../../../../../hooks/useHasLicenseModule';
import { useIsEnterprise } from '../../../../../hooks/useIsEnterprise';
import AddonRequiredModal from '../../../AppsList/AddonRequiredModal';
import type { appStatusSpanResponseProps } from '../../../helpers';
import { appButtonProps, appMultiStatusProps } from '../../../helpers';
import type { AppInstallationHandlerParams } from '../../../hooks/useAppInstallationHandler';
Expand Down Expand Up @@ -41,6 +43,9 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed, ...pro
const { data } = useIsEnterprise();
const isEnterprise = data?.isEnterprise ?? false;

const appAddon = (app as App).addon;
const workspaceHasAddon = useHasLicenseModule(appAddon);

const statuses = appMultiStatusProps(app, isAppDetailsPage, context || '', isEnterprise);

const totalSeenRequests = app?.appRequestStats?.totalSeen;
Expand Down Expand Up @@ -81,8 +86,13 @@ const AppStatus = ({ app, showStatus = true, isAppDetailsPage, installed, ...pro

const handleAcquireApp = useCallback(() => {
setLoading(true);

if (isAdminUser && appAddon && !workspaceHasAddon) {
return setModal(<AddonRequiredModal actionType='install' onDismiss={cancelAction} onInstallAnyway={appInstallationHandler} />);
}

appInstallationHandler();
}, [appInstallationHandler, setLoading]);
}, [appAddon, appInstallationHandler, cancelAction, isAdminUser, setLoading, setModal, workspaceHasAddon]);

// @TODO we should refactor this to not use the label to determine the variant
const getStatusVariant = (status: appStatusSpanResponseProps) => {
Expand Down
24 changes: 24 additions & 0 deletions apps/meteor/client/views/marketplace/AppsList/AddonChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { App } from '@rocket.chat/core-typings';
import { Tag } from '@rocket.chat/fuselage';
import React from 'react';
import { useTranslation } from 'react-i18next';

type AddonChipProps = {
app: App;
};

const AddonChip = ({ app }: AddonChipProps) => {
const { t } = useTranslation();

if (!app.addon) {
return null;
}

return (
<Tag variant='secondary' title={t('Requires_subscription_add-on')}>
{t('Add-on')}
</Tag>
);
};

export default AddonChip;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Button, Modal } from '@rocket.chat/fuselage';
import React from 'react';
import { useTranslation } from 'react-i18next';

import { useExternalLink } from '../../../hooks/useExternalLink';
import { GET_ADDONS_LINK } from '../../admin/subscription/utils/links';

export type AddonActionType = 'install' | 'enable';

type AddonRequiredModalProps = {
actionType: AddonActionType;
onDismiss: () => void;
onInstallAnyway: () => void;
};

const AddonRequiredModal = ({ actionType, onDismiss, onInstallAnyway }: AddonRequiredModalProps) => {
const { t } = useTranslation();

const handleOpenLink = useExternalLink();

return (
<Modal>
<Modal.Header>
<Modal.HeaderText>
<Modal.Title>{t('Add-on_required')}</Modal.Title>
</Modal.HeaderText>
<Modal.Close onClick={onDismiss} />
</Modal.Header>
<Modal.Content>{t('Add-on_required_modal_enable_content')}</Modal.Content>
<Modal.Footer>
<Modal.FooterControllers>
{actionType === 'install' && <Button onClick={onInstallAnyway}>{t('Install_anyway')}</Button>}
<Button primary onClick={() => handleOpenLink(GET_ADDONS_LINK)}>
{t('Contact_sales')}
</Button>
</Modal.FooterControllers>
</Modal.Footer>
</Modal>
);
};

export default AddonRequiredModal;
2 changes: 2 additions & 0 deletions apps/meteor/client/views/marketplace/AppsList/AppRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import semver from 'semver';
import AppStatus from '../AppDetailsPage/tabs/AppStatus/AppStatus';
import AppMenu from '../AppMenu';
import BundleChips from '../BundleChips';
import AddonChip from './AddonChip';

// TODO: org props
const AppRow = ({ className, ...props }: App & { className?: string }): ReactElement => {
Expand Down Expand Up @@ -68,6 +69,7 @@ const AppRow = ({ className, ...props }: App & { className?: string }): ReactEle
{name}
</CardTitle>
{Boolean(bundledIn?.length) && <BundleChips bundledIn={bundledIn} />}
<AddonChip app={props} />
</CardHeader>
{shortDescription && <CardBody id={`${id}-description`}>{shortDescription}</CardBody>}
</CardCol>
Expand Down
48 changes: 38 additions & 10 deletions apps/meteor/client/views/marketplace/hooks/useAppMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import React, { useMemo, useCallback, useState } from 'react';
import semver from 'semver';

import WarningModal from '../../../components/WarningModal';
import { useHasLicenseModule } from '../../../hooks/useHasLicenseModule';
import { useIsEnterprise } from '../../../hooks/useIsEnterprise';
import type { AddonActionType } from '../AppsList/AddonRequiredModal';
import AddonRequiredModal from '../AppsList/AddonRequiredModal';
import IframeModal from '../IframeModal';
import UninstallGrandfatheredAppModal from '../components/UninstallGrandfatheredAppModal/UninstallGrandfatheredAppModal';
import type { Actions } from '../helpers';
Expand Down Expand Up @@ -56,6 +59,9 @@ export const useAppMenu = (app: App, isAppDetailsPage: boolean) => {
const { data } = useIsEnterprise();
const isEnterpriseLicense = !!data?.isEnterprise;

const appAddon = (app as App).addon;
const workspaceHasAddon = useHasLicenseModule(appAddon);

const [isLoading, setLoading] = useState(false);
const [requestedEndUser, setRequestedEndUser] = useState(app.requestedEndUser);
const [isAppPurchased, setPurchased] = useState(app?.isPurchased);
Expand Down Expand Up @@ -118,10 +124,30 @@ export const useAppMenu = (app: App, isAppDetailsPage: boolean) => {
setIsPurchased: setPurchased,
});

// TODO: There is no necessity of all these callbacks being out of the above useMemo.
// My propose here is to refactor the hook to make it clearer and with less unnecessary caching.
const missingAddonHandler = useCallback(
(actionType: AddonActionType) => {
setModal(<AddonRequiredModal actionType={actionType} onDismiss={closeModal} onInstallAnyway={appInstallationHandler} />);
},
[appInstallationHandler, closeModal, setModal],
);

const handleAddon = useCallback(
(actionType: AddonActionType, callback: () => void) => {
if (isAdminUser && appAddon && !workspaceHasAddon) {
return missingAddonHandler(actionType);
}

callback();
},
[appAddon, isAdminUser, missingAddonHandler, workspaceHasAddon],
);

const handleAcquireApp = useCallback(() => {
setLoading(true);
appInstallationHandler();
}, [appInstallationHandler, setLoading]);
handleAddon('install', appInstallationHandler);
}, [appInstallationHandler, handleAddon]);

const handleSubscription = useCallback(async () => {
if (app?.versionIncompatible && !isSubscribed) {
Expand Down Expand Up @@ -181,14 +207,16 @@ export const useAppMenu = (app: App, isAppDetailsPage: boolean) => {
);
}, [app.name, closeModal, setAppStatus, setModal, t]);

const handleEnable = useCallback(async () => {
try {
const { status } = await setAppStatus({ status: AppStatus.MANUALLY_ENABLED });
warnEnableDisableApp(app.name, status, 'enable');
} catch (error) {
handleAPIError(error);
}
}, [app.name, setAppStatus]);
const handleEnable = useCallback(() => {
handleAddon('enable', async () => {
try {
const { status } = await setAppStatus({ status: AppStatus.MANUALLY_ENABLED });
warnEnableDisableApp(app.name, status, 'enable');
} catch (error) {
handleAPIError(error);
}
});
}, [app.name, handleAddon, setAppStatus]);

const handleUninstall = useCallback(() => {
const uninstall = async () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,9 @@
"admin-video-conf-provider-not-configured": "**Conference call not enabled**: Configure conference calls in order to make it available on this workspace.",
"admin-no-videoconf-provider-app": "**Conference call not enabled**: Conference call apps are available in the Rocket.Chat marketplace.",
"Administration": "Administration",
"Add-on": "Add-on",
"Add-on_required": "Add-on required",
"Add-on_required_modal_enable_content": "App cannot be enabled without the required subscription add-on. Contact sales to get the add-on for this app.",
"Address": "Address",
"Adjustable_font_size": "Adjustable font size",
"Adjustable_font_size_description": "Designed for those who prefer larger or smaller text for improved readability. This flexibility promotes inclusivity by empowering users to tailor the software interface to their specific needs.",
Expand Down Expand Up @@ -4579,6 +4582,7 @@
"Require_any_token": "Require any token",
"Require_password_change": "Require password change",
"Require_Two_Factor_Authentication": "Require Two Factor Authentication",
"Requires_subscription_add-on": "Requires subscription add-on",
"Resend_verification_email": "Resend verification email",
"Resend_welcome_email": "Resend welcome email",
"Reset": "Reset",
Expand Down Expand Up @@ -6352,6 +6356,8 @@
"onboarding.form.standaloneServerForm.servicesUnavailable": "Some of the services will be unavailable or will require manual setup",
"onboarding.form.standaloneServerForm.publishOwnApp": "In order to send push notitications you need to compile and publish your own app to Google Play and App Store",
"onboarding.form.standaloneServerForm.manuallyIntegrate": "Need to manually integrate with external services",
"Subscription_add-on_required": "Subscription add-on required",
"App_cannot_be_enabled_without_add-on": "App cannot be enabled without add-on.",
"subscription.callout.servicesDisruptionsMayOccur": "Services disruptions may occur",
"subscription.callout.servicesDisruptionsOccurring": "Services disruptions occurring",
"subscription.callout.capabilitiesDisabled": "Capabilities disabled",
Expand Down
Loading