mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-19 02:28:08 -05:00
Add support for dynamic fetching of preview modal content from S3 bucket (#33380)
* Add support for dynamic fetching of preview modal content from S3 bucket
* Update server/channels/api4/cloud.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update webapp/channels/src/components/cloud_preview_modal/cloud_preview_modal_controller.test.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fixes for CI pipelines
* Add definitions for openapi spec
* Use any instead of interface{}
* Update translations
* Add the translations
* Hook should only run fetch when in cloud preview
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
69e483f32b
commit
e402db875c
14 changed files with 421 additions and 20 deletions
|
|
@ -384,4 +384,35 @@
|
|||
$ref: "#/components/responses/Forbidden"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
/api/v4/cloud/preview/modal_data:
|
||||
get:
|
||||
tags:
|
||||
- cloud
|
||||
summary: Get cloud preview modal data
|
||||
description: >
|
||||
Retrieves modal content data from the configured S3 bucket for displaying cloud product preview modals.
|
||||
|
||||
##### Permissions
|
||||
|
||||
Must be authenticated.
|
||||
Must be in a Cloud Preview environment.
|
||||
|
||||
__Minimum server version__: 10.0
|
||||
__Note:__ This is intended for internal use and is subject to change.
|
||||
operationId: GetPreviewModalData
|
||||
responses:
|
||||
"200":
|
||||
description: Preview modal data returned successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/PreviewModalContentData"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalServerError"
|
||||
|
||||
|
|
|
|||
|
|
@ -3956,6 +3956,37 @@ components:
|
|||
state:
|
||||
description: The current state of the installation
|
||||
type: string
|
||||
MessageDescriptor:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: The i18n message ID
|
||||
type: string
|
||||
defaultMessage:
|
||||
description: The default message text
|
||||
type: string
|
||||
values:
|
||||
description: Optional values for message interpolation
|
||||
type: object
|
||||
additionalProperties: true
|
||||
PreviewModalContentData:
|
||||
type: object
|
||||
properties:
|
||||
skuLabel:
|
||||
$ref: "#/components/schemas/MessageDescriptor"
|
||||
title:
|
||||
$ref: "#/components/schemas/MessageDescriptor"
|
||||
subtitle:
|
||||
$ref: "#/components/schemas/MessageDescriptor"
|
||||
videoUrl:
|
||||
description: URL of the video content
|
||||
type: string
|
||||
videoPoster:
|
||||
description: URL of the video poster/thumbnail image
|
||||
type: string
|
||||
useCase:
|
||||
description: The use case category for this content
|
||||
type: string
|
||||
ServerLimits:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ func (api *API) InitCloud() {
|
|||
|
||||
// GET /api/v4/cloud/cws-health-check
|
||||
api.BaseRoutes.Cloud.Handle("/check-cws-connection", api.APIHandler(handleCheckCWSConnection)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/cloud/preview/modal_data
|
||||
api.BaseRoutes.Cloud.Handle("/preview/modal_data", api.APISessionRequired(getPreviewModalData)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func ensureCloudInterface(c *Context, where string) bool {
|
||||
|
|
@ -609,3 +612,21 @@ func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request
|
|||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func getPreviewModalData(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
modalData, err := c.App.GetPreviewModalData()
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
responseData, jsonErr := json.Marshal(modalData)
|
||||
if jsonErr != nil {
|
||||
c.Err = model.NewAppError("Api4.getPreviewModalData", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
if _, writeErr := w.Write(responseData); writeErr != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(writeErr))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
|
|
@ -35,3 +38,33 @@ func (a *App) SendSubscriptionHistoryEvent(userID string) (*model.SubscriptionHi
|
|||
|
||||
return a.Cloud().CreateOrUpdateSubscriptionHistoryEvent(userID, int(userCount))
|
||||
}
|
||||
|
||||
// GetPreviewModalData fetches modal content data from the configured S3 bucket
|
||||
func (a *App) GetPreviewModalData() ([]model.PreviewModalContentData, *model.AppError) {
|
||||
bucketURL := a.Config().CloudSettings.PreviewModalBucketURL
|
||||
if bucketURL == nil || *bucketURL == "" {
|
||||
return nil, model.NewAppError("GetPreviewModalData", "app.cloud.preview_modal_bucket_url_not_configured", nil, "", http.StatusNotFound)
|
||||
}
|
||||
|
||||
// Construct the full URL to the modal_content.json file
|
||||
fileURL := *bucketURL + "/modal_content.json"
|
||||
|
||||
// Make HTTP request to S3
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetPreviewModalData", "app.cloud.preview_modal_data_fetch_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, model.NewAppError("GetPreviewModalData", "app.cloud.preview_modal_data_fetch_error", nil, "", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
var modalData []model.PreviewModalContentData
|
||||
if err := json.NewDecoder(resp.Body).Decode(&modalData); err != nil {
|
||||
return nil, model.NewAppError("GetPreviewModalData", "app.cloud.preview_modal_data_parse_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return modalData, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5002,6 +5002,18 @@
|
|||
"id": "app.channel_member_history.log_leave_event.internal_error",
|
||||
"translation": "Failed to record channel member history. Failed to update existing join record"
|
||||
},
|
||||
{
|
||||
"id": "app.cloud.preview_modal_bucket_url_not_configured",
|
||||
"translation": "Preview bucket URL is not configured"
|
||||
},
|
||||
{
|
||||
"id": "app.cloud.preview_modal_data_fetch_error",
|
||||
"translation": "Failed to fetch preview modal data"
|
||||
},
|
||||
{
|
||||
"id": "app.cloud.preview_modal_data_parse_error",
|
||||
"translation": "Failed to parse preview modal data"
|
||||
},
|
||||
{
|
||||
"id": "app.cloud.trial_plan_bot_message",
|
||||
"translation": "{{.UsersNum}} members of the {{.WorkspaceName}} workspace have requested starting the Enterprise trial for access to: "
|
||||
|
|
|
|||
|
|
@ -344,6 +344,23 @@ type WorkspaceDeletionRequest struct {
|
|||
Feedback *Feedback `json:"delete_feedback"`
|
||||
}
|
||||
|
||||
// MessageDescriptor represents an i18n message descriptor
|
||||
type MessageDescriptor struct {
|
||||
ID string `json:"id"`
|
||||
DefaultMessage string `json:"defaultMessage"`
|
||||
Values map[string]any `json:"values,omitempty"`
|
||||
}
|
||||
|
||||
// PreviewModalContentData represents the structure of modal content data from S3
|
||||
type PreviewModalContentData struct {
|
||||
SKULabel MessageDescriptor `json:"skuLabel"`
|
||||
Title MessageDescriptor `json:"title"`
|
||||
Subtitle MessageDescriptor `json:"subtitle"`
|
||||
VideoURL string `json:"videoUrl"`
|
||||
VideoPoster string `json:"videoPoster,omitempty"`
|
||||
UseCase string `json:"useCase"`
|
||||
}
|
||||
|
||||
func (p *Product) IsYearly() bool {
|
||||
return p.RecurringInterval == RecurringIntervalYearly
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3298,10 +3298,11 @@ func (s *JobSettings) SetDefaults() {
|
|||
}
|
||||
|
||||
type CloudSettings struct {
|
||||
CWSURL *string `access:"write_restrictable"`
|
||||
CWSAPIURL *string `access:"write_restrictable"`
|
||||
CWSMock *bool `access:"write_restrictable"`
|
||||
Disable *bool `access:"write_restrictable,cloud_restrictable"`
|
||||
CWSURL *string `access:"write_restrictable"`
|
||||
CWSAPIURL *string `access:"write_restrictable"`
|
||||
CWSMock *bool `access:"write_restrictable"`
|
||||
Disable *bool `access:"write_restrictable,cloud_restrictable"`
|
||||
PreviewModalBucketURL *string `access:"write_restrictable"`
|
||||
}
|
||||
|
||||
func (s *CloudSettings) SetDefaults() {
|
||||
|
|
@ -3331,6 +3332,10 @@ func (s *CloudSettings) SetDefaults() {
|
|||
if s.Disable == nil {
|
||||
s.Disable = NewPointer(false)
|
||||
}
|
||||
|
||||
if s.PreviewModalBucketURL == nil {
|
||||
s.PreviewModalBucketURL = NewPointer("")
|
||||
}
|
||||
}
|
||||
|
||||
type PluginState struct {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,20 @@ export function getTeamsUsage(): ThunkActionFunc<Promise<boolean | ServerError>>
|
|||
};
|
||||
}
|
||||
|
||||
export function getCloudPreviewModalData(): ThunkActionFunc<Promise<boolean | ServerError>> {
|
||||
return async () => {
|
||||
try {
|
||||
const result = await Client4.getCloudPreviewModalData();
|
||||
if (result) {
|
||||
return {data: result};
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function retryFailedCloudFetches(): ActionFunc<boolean> {
|
||||
return (dispatch, getState) => {
|
||||
const errors = getCloudErrors(getState());
|
||||
|
|
|
|||
|
|
@ -5,12 +5,23 @@ import {fireEvent, screen, waitFor} from '@testing-library/react';
|
|||
import React from 'react';
|
||||
import * as reactRedux from 'react-redux';
|
||||
|
||||
import type {Subscription} from '@mattermost/types/cloud';
|
||||
import type {Subscription, PreviewModalContentData} from '@mattermost/types/cloud';
|
||||
import type {TeamType} from '@mattermost/types/teams';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
|
||||
import CloudPreviewModal from './cloud_preview_modal_controller';
|
||||
import {modalContent} from './preview_modal_content_data';
|
||||
|
||||
// Mock the useGetCloudPreviewModalContent hook
|
||||
const mockUseGetCloudPreviewModalContent = jest.fn();
|
||||
|
||||
jest.mock('hooks/useGetCloudPreviewModalContent', () => ({
|
||||
useGetCloudPreviewModalContent: () => mockUseGetCloudPreviewModalContent(),
|
||||
}));
|
||||
|
||||
// Variable to track contentData passed to PreviewModalController
|
||||
let lastContentData: PreviewModalContentData[] = [];
|
||||
|
||||
// Mock the async_load module to return components synchronously
|
||||
jest.mock('components/async_load', () => ({
|
||||
|
|
@ -19,9 +30,12 @@ jest.mock('components/async_load', () => ({
|
|||
const Component = (props: any) => {
|
||||
// Mock the preview modal controller
|
||||
if (displayName === 'PreviewModalController') {
|
||||
// Capture contentData for testing
|
||||
lastContentData = props.contentData || [];
|
||||
return props.show ? (
|
||||
<div data-testid='preview-modal-controller'>
|
||||
<button onClick={props.onClose}>{'Close'}</button>
|
||||
<div data-testid='content-data-length'>{props.contentData?.length || 0}</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
|
@ -50,6 +64,12 @@ describe('CloudPreviewModal', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
useDispatchMock.mockClear();
|
||||
lastContentData = [];
|
||||
mockUseGetCloudPreviewModalContent.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
});
|
||||
|
||||
const baseSubscription: Subscription = {
|
||||
|
|
@ -308,4 +328,126 @@ describe('CloudPreviewModal', () => {
|
|||
expect(fabButton).toHaveAttribute('aria-label', 'Open cloud preview overview');
|
||||
expect(fabButton).toHaveClass('cloud-preview-modal-fab__button');
|
||||
});
|
||||
|
||||
it('should use dynamic content when available', () => {
|
||||
const dynamicContent: PreviewModalContentData[] = [
|
||||
{
|
||||
skuLabel: {
|
||||
id: 'dynamic.sku.label',
|
||||
defaultMessage: 'Dynamic SKU',
|
||||
},
|
||||
title: {
|
||||
id: 'dynamic.title',
|
||||
defaultMessage: 'Dynamic Title',
|
||||
},
|
||||
subtitle: {
|
||||
id: 'dynamic.subtitle',
|
||||
defaultMessage: 'Dynamic Subtitle',
|
||||
},
|
||||
videoUrl: 'https://example.com/dynamic-video.mp4',
|
||||
videoPoster: 'https://example.com/dynamic-poster.jpg',
|
||||
useCase: 'mission-ops',
|
||||
},
|
||||
];
|
||||
|
||||
mockUseGetCloudPreviewModalContent.mockReturnValue({
|
||||
data: dynamicContent,
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
|
||||
renderWithContext(
|
||||
<CloudPreviewModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('preview-modal-controller')).toBeInTheDocument();
|
||||
expect(lastContentData).toHaveLength(1);
|
||||
expect(lastContentData[0]).toEqual(dynamicContent[0]);
|
||||
expect(lastContentData[0].title.defaultMessage).toBe('Dynamic Title');
|
||||
});
|
||||
|
||||
it('should fallback to hardcoded content when dynamic content is not available', () => {
|
||||
mockUseGetCloudPreviewModalContent.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
|
||||
renderWithContext(
|
||||
<CloudPreviewModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('preview-modal-controller')).toBeInTheDocument();
|
||||
const missionOpsContent = modalContent.filter((content) => content.useCase === 'mission-ops');
|
||||
expect(lastContentData).toHaveLength(missionOpsContent.length);
|
||||
expect(lastContentData[0].title.defaultMessage).toBe('Welcome to your Mattermost preview');
|
||||
});
|
||||
|
||||
it('should fallback to hardcoded content when dynamic content is empty', () => {
|
||||
mockUseGetCloudPreviewModalContent.mockReturnValue({
|
||||
data: [],
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
|
||||
renderWithContext(
|
||||
<CloudPreviewModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('preview-modal-controller')).toBeInTheDocument();
|
||||
const missionOpsContent = modalContent.filter((content) => content.useCase === 'mission-ops');
|
||||
expect(lastContentData).toHaveLength(missionOpsContent.length);
|
||||
expect(lastContentData[0].title.defaultMessage).toBe('Welcome to your Mattermost preview');
|
||||
});
|
||||
|
||||
it('should not show modal when content is loading', () => {
|
||||
mockUseGetCloudPreviewModalContent.mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: false,
|
||||
});
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
|
||||
renderWithContext(
|
||||
<CloudPreviewModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('preview-modal-controller')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should fallback to hardcoded content when there is an error fetching dynamic content', () => {
|
||||
mockUseGetCloudPreviewModalContent.mockReturnValue({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: true,
|
||||
});
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
|
||||
renderWithContext(
|
||||
<CloudPreviewModal/>,
|
||||
initialState,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('preview-modal-controller')).toBeInTheDocument();
|
||||
const missionOpsContent = modalContent.filter((content) => content.useCase === 'mission-ops');
|
||||
expect(lastContentData).toHaveLength(missionOpsContent.length);
|
||||
expect(lastContentData[0].title.defaultMessage).toBe('Welcome to your Mattermost preview');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
|||
import {makeAsyncComponent} from 'components/async_load';
|
||||
import WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import {useGetCloudPreviewModalContent} from 'hooks/useGetCloudPreviewModalContent';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import type {PreviewModalContentData} from './preview_modal_content_data';
|
||||
|
|
@ -46,9 +48,12 @@ const CloudPreviewModal: React.FC = () => {
|
|||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const filteredContentByUseCase = (content: PreviewModalContentData[]) => {
|
||||
// Fetch dynamic content from the backend
|
||||
const {data: dynamicModalContent, loading: contentLoading} = useGetCloudPreviewModalContent();
|
||||
|
||||
const filteredContentByUseCase = React.useCallback((content: PreviewModalContentData[]) => {
|
||||
return content.filter((content) => content.useCase === team?.name.replace('-hq', ''));
|
||||
};
|
||||
}, [team?.name]);
|
||||
|
||||
useEffect(() => {
|
||||
// Show modal only if:
|
||||
|
|
@ -56,13 +61,17 @@ const CloudPreviewModal: React.FC = () => {
|
|||
// 2. Modal hasn't been shown before
|
||||
// 3. We have the necessary data loaded
|
||||
// 4. There's content to display for the current team
|
||||
const filteredContent = team?.name ? filteredContentByUseCase(modalContent) : [];
|
||||
if (isCloud && isCloudPreview && !hasModalBeenShown && currentUserId && team?.name && filteredContent.length > 0) {
|
||||
// 5. Content is not loading
|
||||
|
||||
// Use dynamic content if available and not empty, fallback to hardcoded content
|
||||
const contentToUse = (dynamicModalContent && dynamicModalContent.length > 0) ? dynamicModalContent : modalContent;
|
||||
const filteredContent = team?.name ? filteredContentByUseCase(contentToUse) : [];
|
||||
if (isCloud && isCloudPreview && !hasModalBeenShown && currentUserId && team?.name && filteredContent.length > 0 && !contentLoading) {
|
||||
setShowModal(true);
|
||||
} else if (hasModalBeenShown) {
|
||||
setShowModal(false);
|
||||
}
|
||||
}, [isCloud, isCloudPreview, hasModalBeenShown, currentUserId, team?.name]);
|
||||
}, [isCloud, isCloudPreview, hasModalBeenShown, currentUserId, team?.name, dynamicModalContent, contentLoading, filteredContentByUseCase]);
|
||||
|
||||
const handleClose = () => {
|
||||
setShowModal(false);
|
||||
|
|
@ -103,7 +112,14 @@ const CloudPreviewModal: React.FC = () => {
|
|||
const shouldShowFAB = hasModalBeenShown && !showModal;
|
||||
|
||||
// Only render the controller if we pass the license checks
|
||||
const contentData = team?.name ? filteredContentByUseCase(modalContent) : [];
|
||||
// Use dynamic content if available and not empty, fallback to hardcoded content
|
||||
const contentToUse = (dynamicModalContent && dynamicModalContent.length > 0) ? dynamicModalContent : modalContent;
|
||||
const contentData = team?.name ? filteredContentByUseCase(contentToUse) : [];
|
||||
|
||||
// Show loading state while fetching dynamic content
|
||||
if (contentLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -2,16 +2,10 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessage} from 'react-intl';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
|
||||
export type PreviewModalContentData = {
|
||||
skuLabel: MessageDescriptor;
|
||||
title: MessageDescriptor;
|
||||
subtitle: MessageDescriptor;
|
||||
videoUrl: string;
|
||||
videoPoster?: string;
|
||||
useCase: string;
|
||||
};
|
||||
import type {PreviewModalContentData} from '@mattermost/types/cloud';
|
||||
|
||||
export type {PreviewModalContentData};
|
||||
|
||||
export const modalContent: PreviewModalContentData[] = [
|
||||
{
|
||||
|
|
|
|||
62
webapp/channels/src/hooks/useGetCloudPreviewModalContent.ts
Normal file
62
webapp/channels/src/hooks/useGetCloudPreviewModalContent.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import type {PreviewModalContentData} from '@mattermost/types/cloud';
|
||||
|
||||
import {getCloudSubscription} from 'mattermost-redux/selectors/entities/cloud';
|
||||
import {getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {getCloudPreviewModalData} from 'actions/cloud';
|
||||
|
||||
export type UseGetCloudPreviewModalContentResult = {
|
||||
data: PreviewModalContentData[] | null;
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
};
|
||||
|
||||
export const useGetCloudPreviewModalContent = (): UseGetCloudPreviewModalContentResult => {
|
||||
const dispatch = useDispatch();
|
||||
const subscription = useSelector(getCloudSubscription);
|
||||
const license = useSelector(getLicense);
|
||||
const [data, setData] = useState<PreviewModalContentData[] | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const isCloud = license?.Cloud === 'true';
|
||||
const isCloudPreview = subscription?.is_cloud_preview === true;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
try {
|
||||
const result = await dispatch(getCloudPreviewModalData());
|
||||
if (result && typeof result === 'object' && 'data' in result) {
|
||||
setData(result.data as PreviewModalContentData[]);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Only fetch data if this is a cloud preview workspace
|
||||
if (isCloud && isCloudPreview) {
|
||||
fetchData();
|
||||
} else {
|
||||
// Not a cloud preview workspace, set loading to false and data to null
|
||||
setLoading(false);
|
||||
setData(null);
|
||||
setError(false);
|
||||
}
|
||||
}, [dispatch, isCloud, isCloudPreview]);
|
||||
|
||||
return {data, loading, error};
|
||||
};
|
||||
|
|
@ -39,6 +39,7 @@ import type {
|
|||
ValidBusinessEmail,
|
||||
NewsletterRequestBody,
|
||||
Installation,
|
||||
PreviewModalContentData,
|
||||
} from '@mattermost/types/cloud';
|
||||
import type {Compliance} from '@mattermost/types/compliance';
|
||||
import type {
|
||||
|
|
@ -4139,6 +4140,13 @@ export default class Client4 {
|
|||
);
|
||||
};
|
||||
|
||||
getCloudPreviewModalData = () => {
|
||||
return this.doFetch<PreviewModalContentData[]>(
|
||||
`${this.getCloudRoute()}/preview/modal_data`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getInvoices = () => {
|
||||
return this.doFetch<Invoice[]>(
|
||||
`${this.getCloudRoute()}/subscription/invoices`,
|
||||
|
|
|
|||
|
|
@ -206,3 +206,18 @@ export type Feedback = {
|
|||
reason: string;
|
||||
comments: string;
|
||||
}
|
||||
|
||||
export type MessageDescriptor = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
values?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type PreviewModalContentData = {
|
||||
skuLabel: MessageDescriptor;
|
||||
title: MessageDescriptor;
|
||||
subtitle: MessageDescriptor;
|
||||
videoUrl: string;
|
||||
videoPoster?: string;
|
||||
useCase: string;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue