import React, { useCallback, useState } from 'react';
import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse, defaultSupportedSlackEventActions } from '@growi/slack';
import { useTranslation } from 'next-i18next';
import PropTypes from 'prop-types';
import { apiv3Put } from '~/client/util/apiv3-client';
import { toastError, toastSuccess } from '~/client/util/toastr';
import loggerFactory from '~/utils/logger';
const logger = loggerFactory('growi:SlackIntegration:ManageCommandsProcess');
const PermissionTypes = {
ALLOW_ALL: 'allowAll',
DENY_ALL: 'denyAll',
ALLOW_SPECIFIED: 'allowSpecified',
};
const CommandUsageTypes = {
BROADCAST_USE: 'broadcastUse',
SINGLE_USE: 'singleUse',
};
const EventTypes = {
LINK_SHARING: 'linkSharing',
};
// A utility function that returns the new state but identical to the previous state
const getUpdatedChannelsList = (prevState, commandName, value) => {
// string to array
const allowedChannelsArray = value.split(',');
// trim whitespace from all elements
const trimedAllowedChannelsArray = allowedChannelsArray.map(channelName => channelName.trim());
prevState[commandName] = trimedAllowedChannelsArray;
return prevState;
};
// A utility function that returns the new state
const getUpdatedPermissionSettings = (prevState, commandName, value) => {
const newState = { ...prevState };
switch (value) {
case PermissionTypes.ALLOW_ALL:
newState[commandName] = true;
break;
case PermissionTypes.DENY_ALL:
newState[commandName] = false;
break;
case PermissionTypes.ALLOW_SPECIFIED:
newState[commandName] = [];
break;
default:
logger.error('Not implemented');
break;
}
return newState;
};
// A utility function that returns the permission type from the permission value
const getPermissionTypeFromValue = (value) => {
if (Array.isArray(value)) {
return PermissionTypes.ALLOW_SPECIFIED;
}
if (typeof value === 'boolean') {
return value ? PermissionTypes.ALLOW_ALL : PermissionTypes.DENY_ALL;
}
logger.error('The value type must be boolean or string[]');
};
const PermissionSettingForEachPermissionTypeComponent = ({
keyName, onUpdatePermissions, onUpdateChannels, singleCommandDescription, allowedChannelsDescription, currentPermissionType, permissionSettings,
}) => {
const { t } = useTranslation();
const hiddenClass = currentPermissionType === PermissionTypes.ALLOW_SPECIFIED ? '' : 'd-none';
const permission = permissionSettings[keyName];
if (permission === undefined) logger.error('Must be implemented');
const textareaDefaultValue = Array.isArray(permission) ? permission.join(',') : '';
return (
{keyName}
{singleCommandDescription && (
{ singleCommandDescription }
)}
{t(allowedChannelsDescription, { keyName })}
);
};
PermissionSettingForEachPermissionTypeComponent.propTypes = {
keyName: PropTypes.string,
usageType: PropTypes.string,
currentPermissionType: PropTypes.string,
singleCommandDescription: PropTypes.string,
onUpdatePermissions: PropTypes.func,
onUpdateChannels: PropTypes.func,
allowedChannelsDescription: PropTypes.string,
permissionSettings: PropTypes.object,
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const ManageCommandsProcess = ({
slackAppIntegrationId, permissionsForBroadcastUseCommands, permissionsForSingleUseCommands, permissionsForSlackEventActions,
}) => {
const { t } = useTranslation();
const [permissionsForBroadcastUseCommandsState, setPermissionsForBroadcastUseCommandsState] = useState({
search: permissionsForBroadcastUseCommands.search,
});
const [permissionsForSingleUseCommandsState, setPermissionsForSingleUseCommandsState] = useState({
note: permissionsForSingleUseCommands.note,
keep: permissionsForSingleUseCommands.keep,
});
const [permissionsForEventsState, setPermissionsForEventsState] = useState({
unfurl: permissionsForSlackEventActions.unfurl,
});
const [currentPermissionTypes, setCurrentPermissionTypes] = useState(() => {
const initialState = {};
Object.entries(permissionsForBroadcastUseCommandsState).forEach((entry) => {
const [commandName, value] = entry;
initialState[commandName] = getPermissionTypeFromValue(value);
});
Object.entries(permissionsForSingleUseCommandsState).forEach((entry) => {
const [commandName, value] = entry;
initialState[commandName] = getPermissionTypeFromValue(value);
});
Object.entries(permissionsForEventsState).forEach((entry) => {
const [commandName, value] = entry;
initialState[commandName] = getPermissionTypeFromValue(value);
});
return initialState;
});
const handleUpdateSingleUsePermissions = useCallback((e) => {
const { target } = e;
const { name: commandName, value } = target;
setPermissionsForSingleUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
setCurrentPermissionTypes((prevState) => {
const newState = { ...prevState };
newState[commandName] = value;
return newState;
});
}, []);
const handleUpdateBroadcastUsePermissions = useCallback((e) => {
const { target } = e;
const { name: commandName, value } = target;
setPermissionsForBroadcastUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
setCurrentPermissionTypes((prevState) => {
const newState = { ...prevState };
newState[commandName] = value;
return newState;
});
}, []);
const handleUpdateEventsPermissions = useCallback((e) => {
const { target } = e;
const { name: commandName, value } = target;
setPermissionsForEventsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
setCurrentPermissionTypes((prevState) => {
const newState = { ...prevState };
newState[commandName] = value;
return newState;
});
}, []);
const handleUpdateSingleUseChannels = useCallback((e) => {
const { target } = e;
const { name: commandName, value } = target;
setPermissionsForSingleUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
}, []);
const handleUpdateBroadcastUseChannels = useCallback((e) => {
const { target } = e;
const { name: commandName, value } = target;
setPermissionsForBroadcastUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
}, []);
const handleUpdateEventsChannels = useCallback((e) => {
const { target } = e;
const { name: commandName, value } = target;
setPermissionsForEventsState(prev => getUpdatedChannelsList(prev, commandName, value));
}, []);
const updateSettingsHandler = async(e) => {
try {
// TODO: add new attribute 78975
await apiv3Put(`/slack-integration-settings/slack-app-integrations/${slackAppIntegrationId}/permissions`, {
permissionsForBroadcastUseCommands: permissionsForBroadcastUseCommandsState,
permissionsForSingleUseCommands: permissionsForSingleUseCommandsState,
permissionsForSlackEventActions: permissionsForEventsState,
});
toastSuccess(t('toaster.update_successed', { target: 'Token', ns: 'commons' }));
}
catch (err) {
toastError(err);
logger.error(err);
}
};
const PermissionSettingsForEachCategoryComponent = ({
currentPermissionTypes,
usageType,
menuItem,
}) => {
const permissionMap = {
broadcastUse: permissionsForBroadcastUseCommandsState,
singleUse: permissionsForSingleUseCommandsState,
linkSharing: permissionsForEventsState,
};
const {
title,
description,
defaultCommandsName,
singleCommandDescription,
updatePermissionsHandler,
updateChannelsHandler,
allowedChannelsDescription,
} = menuItem;
return (
<>
{(title || description) && (
{ title &&
{title}
}
{ description &&
{description}
}
)}
{defaultCommandsName.map(keyName => (
))}
>
);
};
PermissionSettingsForEachCategoryComponent.propTypes = {
currentPermissionTypes: PropTypes.object,
usageType: PropTypes.string,
menuItem: PropTypes.object,
};
// Using i18n in allowedChannelsDescription will cause interpolation error
const menuMap = {
broadcastUse: {
title: 'Multiple GROWI',
description: t('admin:slack_integration.accordion.multiple_growi_command'),
defaultCommandsName: defaultSupportedCommandsNameForBroadcastUse,
updatePermissionsHandler: handleUpdateBroadcastUsePermissions,
updateChannelsHandler: handleUpdateBroadcastUseChannels,
allowedChannelsDescription: 'admin:slack_integration.accordion.allowed_channels_description',
},
singleUse: {
title: 'Single GROWI',
description: t('admin:slack_integration.accordion.single_growi_command'),
defaultCommandsName: defaultSupportedCommandsNameForSingleUse,
updatePermissionsHandler: handleUpdateSingleUsePermissions,
updateChannelsHandler: handleUpdateSingleUseChannels,
allowedChannelsDescription: 'admin:slack_integration.accordion.allowed_channels_description',
},
linkSharing: {
defaultCommandsName: defaultSupportedSlackEventActions,
updatePermissionsHandler: handleUpdateEventsPermissions,
updateChannelsHandler: handleUpdateEventsChannels,
singleCommandDescription: t('admin:slack_integration.accordion.unfurl_description'),
allowedChannelsDescription: 'admin:slack_integration.accordion.unfurl_allowed_channels_description',
},
};
return (
{t('admin:slack_integration.accordion.growi_commands')}
{Object.values(CommandUsageTypes).map(commandUsageType => (
))}
Events
{Object.values(EventTypes).map(EventType => (
))}
);
};
ManageCommandsProcess.propTypes = {
slackAppIntegrationId: PropTypes.string.isRequired,
permissionsForBroadcastUseCommands: PropTypes.object.isRequired,
permissionsForSingleUseCommands: PropTypes.object.isRequired,
permissionsForSlackEventActions: PropTypes.object.isRequired,
};
export default ManageCommandsProcess;