Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
272 lines
10 KiB
TypeScript
272 lines
10 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import type {IntlShape} from 'react-intl';
|
|
import type {AnyAction} from 'redux';
|
|
|
|
import type {AppCallResponse, AppForm, AppCallRequest, AppContext, AppBinding} from '@mattermost/types/apps';
|
|
import type {CommandArgs} from '@mattermost/types/integrations';
|
|
import type {Post} from '@mattermost/types/posts';
|
|
|
|
import {Client4} from 'mattermost-redux/client';
|
|
import {AppCallResponseTypes} from 'mattermost-redux/constants/apps';
|
|
import {cleanForm} from 'mattermost-redux/utils/apps';
|
|
|
|
import {openModal} from 'actions/views/modals';
|
|
|
|
import AppsForm from 'components/apps_form';
|
|
|
|
import {createCallRequest, makeCallErrorResponse} from 'utils/apps';
|
|
import {getHistory} from 'utils/browser_history';
|
|
import {ModalIdentifiers} from 'utils/constants';
|
|
import {getSiteURL, shouldOpenInNewTab} from 'utils/url';
|
|
|
|
import type {DoAppCallResult} from 'types/apps';
|
|
import type {ActionFuncAsync, ThunkActionFunc} from 'types/store';
|
|
|
|
import {sendEphemeralPost} from './global_actions';
|
|
|
|
export function handleBindingClick<Res=unknown>(binding: AppBinding, context: AppContext, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
|
|
return async (dispatch) => {
|
|
// Fetch form
|
|
let form = binding.form;
|
|
if (form?.source) {
|
|
const callRequest = createCallRequest(form.source, context);
|
|
const res = await dispatch(doAppFetchForm<Res>(callRequest, intl));
|
|
if (res.error) {
|
|
return res;
|
|
}
|
|
form = res.data!.form;
|
|
}
|
|
|
|
// Open form
|
|
if (form) {
|
|
// This should come properly formed, but using preventive checks
|
|
if (!form?.submit) {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.malformed_binding',
|
|
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
|
|
const res: AppCallResponse<Res> = {
|
|
type: AppCallResponseTypes.FORM,
|
|
form,
|
|
};
|
|
return {data: res};
|
|
}
|
|
|
|
// Submit binding
|
|
// This should come properly formed, but using preventive checks
|
|
if (!binding.submit) {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.malformed_binding',
|
|
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
|
|
const callRequest = createCallRequest(
|
|
binding.submit,
|
|
context,
|
|
);
|
|
|
|
const res = await dispatch(doAppSubmit<Res>(callRequest, intl));
|
|
return res;
|
|
};
|
|
}
|
|
|
|
export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: IntlShape): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
|
|
return async () => {
|
|
try {
|
|
const call: AppCallRequest = {
|
|
...inCall,
|
|
context: {
|
|
...inCall.context,
|
|
track_as_submit: true,
|
|
},
|
|
};
|
|
const res = await Client4.executeAppCall(call, true) as AppCallResponse<Res>;
|
|
const responseType = res.type || AppCallResponseTypes.OK;
|
|
|
|
switch (responseType) {
|
|
case AppCallResponseTypes.OK:
|
|
return {data: res};
|
|
case AppCallResponseTypes.ERROR:
|
|
return {error: res};
|
|
case AppCallResponseTypes.FORM:
|
|
if (!res.form?.submit) {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.responses.form.no_form',
|
|
defaultMessage: 'Response type is `form`, but no form was included in response.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
|
|
cleanForm(res.form);
|
|
return {data: res};
|
|
|
|
case AppCallResponseTypes.NAVIGATE: {
|
|
if (!res.navigate_to_url) {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.responses.navigate.no_url',
|
|
defaultMessage: 'Response type is `navigate`, but no url was included in response.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
if (shouldOpenInNewTab(res.navigate_to_url, getSiteURL())) {
|
|
window.open(res.navigate_to_url);
|
|
return {data: res};
|
|
}
|
|
const navigateURL = res.navigate_to_url.startsWith(getSiteURL()) ? res.navigate_to_url.slice(getSiteURL().length) : res.navigate_to_url;
|
|
getHistory().push(navigateURL);
|
|
return {data: res};
|
|
}
|
|
default: {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.responses.unknown_type',
|
|
defaultMessage: 'App response type not supported. Response type: {type}.',
|
|
}, {type: responseType});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
const errMsg = error.message || intl.formatMessage({
|
|
id: 'apps.error.responses.unexpected_error',
|
|
defaultMessage: 'Received an unexpected error.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
};
|
|
}
|
|
|
|
export function doAppFetchForm<Res=unknown>(call: AppCallRequest, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
|
|
return async () => {
|
|
try {
|
|
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
|
|
const responseType = res.type || AppCallResponseTypes.OK;
|
|
|
|
switch (responseType) {
|
|
case AppCallResponseTypes.ERROR:
|
|
return {error: res};
|
|
case AppCallResponseTypes.FORM:
|
|
if (!res.form?.submit) {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.responses.form.no_form',
|
|
defaultMessage: 'Response type is `form`, but no form was included in response.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
cleanForm(res.form);
|
|
return {data: res};
|
|
default: {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.responses.unknown_type',
|
|
defaultMessage: 'App response type not supported. Response type: {type}.',
|
|
}, {type: responseType});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
const errMsg = error.message || intl.formatMessage({
|
|
id: 'apps.error.responses.unexpected_error',
|
|
defaultMessage: 'Received an unexpected error.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
};
|
|
}
|
|
|
|
export function doAppLookup<Res=unknown>(call: AppCallRequest, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
|
|
return async () => {
|
|
try {
|
|
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
|
|
const responseType = res.type || AppCallResponseTypes.OK;
|
|
|
|
switch (responseType) {
|
|
case AppCallResponseTypes.OK:
|
|
return {data: res};
|
|
case AppCallResponseTypes.ERROR:
|
|
return {error: res};
|
|
|
|
default: {
|
|
const errMsg = intl.formatMessage({
|
|
id: 'apps.error.responses.unknown_type',
|
|
defaultMessage: 'App response type not supported. Response type: {type}.',
|
|
}, {type: responseType});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
const errMsg = error.message || intl.formatMessage({
|
|
id: 'apps.error.responses.unexpected_error',
|
|
defaultMessage: 'Received an unexpected error.',
|
|
});
|
|
return {error: makeCallErrorResponse(errMsg)};
|
|
}
|
|
};
|
|
}
|
|
|
|
export function makeFetchBindings(location: string): (channelId: string, teamId: string) => ActionFuncAsync<AppBinding[]> {
|
|
return (channelId: string, teamId: string): ActionFuncAsync<AppBinding[]> => {
|
|
return async () => {
|
|
try {
|
|
const allBindings = await Client4.getAppsBindings(channelId, teamId);
|
|
const headerBindings = allBindings.filter((b) => b.location === location);
|
|
const bindings = headerBindings.reduce((accum: AppBinding[], current: AppBinding) => accum.concat(current.bindings || []), []);
|
|
return {data: bindings};
|
|
} catch {
|
|
return {data: []};
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
export function openAppsModal(form: AppForm, context: AppContext): AnyAction {
|
|
return openModal({
|
|
modalId: ModalIdentifiers.APPS_MODAL,
|
|
dialogType: AppsForm,
|
|
dialogProps: {
|
|
form,
|
|
appContext: context,
|
|
},
|
|
});
|
|
}
|
|
|
|
export function postEphemeralCallResponseForPost(response: AppCallResponse, message: string, post: Post) {
|
|
return sendEphemeralPost(
|
|
message,
|
|
post.channel_id,
|
|
post.root_id || post.id,
|
|
response.app_metadata?.bot_user_id,
|
|
);
|
|
}
|
|
|
|
export function postEphemeralCallResponseForChannel(response: AppCallResponse, message: string, channelID: string) {
|
|
return sendEphemeralPost(
|
|
message,
|
|
channelID,
|
|
'',
|
|
response.app_metadata?.bot_user_id,
|
|
);
|
|
}
|
|
|
|
export function postEphemeralCallResponseForContext(response: AppCallResponse, message: string, context: AppContext) {
|
|
return sendEphemeralPost(
|
|
message,
|
|
context.channel_id,
|
|
context.root_id || context.post_id,
|
|
response.app_metadata?.bot_user_id,
|
|
);
|
|
}
|
|
|
|
export function postEphemeralCallResponseForCommandArgs(response: AppCallResponse, message: string, args: CommandArgs) {
|
|
return sendEphemeralPost(
|
|
message,
|
|
args.channel_id,
|
|
args.root_id,
|
|
response.app_metadata?.bot_user_id,
|
|
);
|
|
}
|