Skip to content

Commit

Permalink
feat: apply api for notification associated member channel (#4416)
Browse files Browse the repository at this point in the history
* feat: apply api for notification associated member channel

Signed-off-by: NaYeong,Kim <[email protected]>

* chore: update translations

Signed-off-by: NaYeong,Kim <[email protected]>

* chore: changed design

Signed-off-by: NaYeong,Kim <[email protected]>

* feat: changed names

Signed-off-by: NaYeong,Kim <[email protected]>

* chore: apply review

Signed-off-by: NaYeong,Kim <[email protected]>

---------

Signed-off-by: NaYeong,Kim <[email protected]>
Signed-off-by: NaYeong,Kim <[email protected]>
  • Loading branch information
skdud4659 authored Jul 24, 2024
1 parent 8a6e6d1 commit 4709bdb
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import NotificationAddLevel from '@/services/my-page/components/NotificationAddL
import NotificationAddMemberGroup from '@/services/my-page/components/NotificationAddMemberGroup.vue';
import type { NotificationAddFormDataPayload } from '@/services/my-page/types/notification-add-form-type';
const SPACEONE_USER_CHANNEL_TYPE = 'SpaceONE User' as const;
const allReferenceStore = useAllReferenceStore();
const PROTOCOL_TYPE = {
Expand Down Expand Up @@ -72,7 +71,7 @@ const state = reactive({
isInputNotEmpty: computed<boolean>(() => state.channelName !== undefined && Object.keys(state.schemaForm).length !== 0),
isInputValid: computed<boolean>(() => state.isInputNotEmpty && (state.isSchemaFormValid && !state.isNameInvalid)),
isDataValid: computed<boolean>(() => (!state.isJsonSchema && !state.isNameInvalid) || (state.isJsonSchema && state.isInputValid)),
selectedMember: [] as string[],
selectedMember: ['*'] as string[],
radioMenuList: computed<MenuItem[]>(() => [
{
label: i18n.t('PROJECT.DETAIL.NOTIFICATION_ALL_USERS'),
Expand Down Expand Up @@ -101,7 +100,6 @@ const getSchema = async (): Promise<JsonSchema|null> => {
};
const emitChange = () => {
// TODO: check users value when radio button is changed
emit('change', {
channelName: state.channelName,
data: (props.protocolType === PROTOCOL_TYPE.EXTERNAL)
Expand Down Expand Up @@ -142,7 +140,7 @@ const initStates = () => {
state.notificationLevel = 'LV1';
state.schemaForm = {};
state.isSchemaFormValid = false;
state.selectedMember = [];
state.selectedMember = ['*'];
state.schema = null;
};
Expand Down Expand Up @@ -184,7 +182,7 @@ watch([() => props.protocolId, () => props.protocolType], async ([protocolId, pr
@change="handleSchemaFormChange"
/>
<div v-if="props.projectId && state.protocol?.name === SPACEONE_USER_CHANNEL_TYPE && props.protocolType === PROTOCOL_TYPE.INTERNAL">
<p-field-group :label="$t('MENU.ADMINISTRATION_USER')"
<p-field-group :label="$t('IAM.USER.FORM.MEMBER')"
required
>
<template #default>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const onEdit = (value?: EditTarget) => {
<template>
<p-pane-layout class="channel-card-wrapper">
<div class="card-header">
<p-field-title :label="props.channelData.protocol_name">
<p-field-title :label="props.channelData.protocol_name.toLowerCase().includes('spaceone') ? i18n.t('IAM.USER.NOTIFICATION.ASSOCIATED_MEMBER') : props.channelData.protocol_name">
<template #left>
<p-toggle-button :value="state.isActivated"
:disabled="props.manageDisabled"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { computed, reactive, watch } from 'vue';
import { cloneDeep } from 'lodash';
import {
PBadge, PButton, PI, PJsonSchemaForm,
PBadge, PButton, PI, PJsonSchemaForm, PRadio, PRadioGroup,
} from '@cloudforet/mirinae';
import type { MenuItem } from '@cloudforet/mirinae/types/inputs/context-menu/type';
import { i18n } from '@/translations';
import { useAllReferenceStore } from '@/store/reference/all-reference-store';
import type { UserReferenceMap } from '@/store/reference/user-reference-store';
Expand Down Expand Up @@ -50,11 +53,23 @@ const state = reactive({
schema: props.channelData.schema,
isSecretData: computed<boolean>(() => !!props.channelData.secret_id),
isSpaceOneUserProtocol: computed<boolean>(() => state.keyListForEdit.includes('users')),
isAllUsers: computed<boolean>(() => props.channelData.data.users.some((i) => i === '*')),
//
isSchemaDataValid: false,
isJsonSchema: computed(() => Object.keys(state.schema).length !== 0),
isInputValid: computed(() => state.isSchemaDataValid),
isDataValid: computed(() => state.isJsonSchema && state.isInputValid),
radioMenuList: computed<MenuItem[]>(() => [
{
label: i18n.t('PROJECT.DETAIL.NOTIFICATION_ALL_USERS'),
name: 'all',
},
{
label: i18n.t('PROJECT.DETAIL.NOTIFICATION_SPECIFIC_MEMBER'),
name: 'specific',
},
]),
selectedRadioIdx: 0,
});
const setKeyListForEdit = () => {
Expand Down Expand Up @@ -89,13 +104,24 @@ const onClickSave = async () => {
const onChangeUser = (value: Record<string, any>) => {
if (!notificationItemState.dataForEdit) return;
notificationItemState.dataForEdit.users = value.users;
if (state.selectedRadioIdx === 1) {
notificationItemState.dataForEdit.users = value;
} else {
notificationItemState.dataForEdit.users = ['*'];
}
};
const handleSchemaValidate = (isValid: boolean) => {
state.isSchemaDataValid = isValid;
};
watch(() => notificationItemState.isEditMode, (mode) => {
if (!mode) return;
if (!state.isAllUsers) {
state.selectedRadioIdx = 1;
}
});
(async () => {
await Promise.allSettled([setKeyListForEdit(), setKeyListForRead(), setValueList()]);
})();
Expand All @@ -112,7 +138,7 @@ const handleSchemaValidate = (isValid: boolean) => {
<p v-for="(item, index) in state.keyListForEdit"
:key="`channel-data-key-${index}`"
>
{{ item.replace(/\_/g, ' ') }}
{{ state.isSpaceOneUserProtocol ? $t('IAM.USER.FORM.MEMBER') : item.replace(/\_/g, ' ') }}
</p>
</div>
Expand All @@ -123,7 +149,7 @@ const handleSchemaValidate = (isValid: boolean) => {
<span v-for="(item, index) in state.keyListForRead"
:key="`channel-data-key-${index}`"
>
{{ item.replace(/\_/g, ' ') }}
{{ state.isSpaceOneUserProtocol ? $t('IAM.USER.FORM.MEMBER') : item.replace(/\_/g, ' ') }}
</span>
</div>
Expand All @@ -133,7 +159,20 @@ const handleSchemaValidate = (isValid: boolean) => {
>
<div class="left-section">
<p v-if="state.isSpaceOneUserProtocol">
<notification-add-member-group :users="props.channelData.data.users"
<p-radio-group>
<p-radio v-for="(item, idx) in state.radioMenuList"
:key="idx"
v-model="state.selectedRadioIdx"
:value="idx"
@change="onChangeUser"
>
<span class="radio-item">
{{ item.label }}
</span>
</p-radio>
</p-radio-group>
<notification-add-member-group v-if="state.selectedRadioIdx === 1"
:users="state.isAllUsers ? [] : props.channelData.data.users"
:project-id="props.projectId"
@change="onChangeUser"
/>
Expand Down Expand Up @@ -223,14 +262,26 @@ const handleSchemaValidate = (isValid: boolean) => {
<style lang="postcss" scoped>
@import '../styles/NotificationChannelItem.pcss';
.content-wrapper .edit-button {
display: flex;
align-items: center;
gap: 0.5rem;
&.edit-disable {
@apply text-gray-300 cursor-not-allowed;
&:active {
@apply pointer-events-none;
.content-wrapper {
.edit-button {
display: flex;
align-items: center;
gap: 0.5rem;
&.edit-disable {
@apply text-gray-300 cursor-not-allowed;
&:active {
@apply pointer-events-none;
}
}
}
/* custom design-system component - p-radio */
:deep(.p-radio) {
@apply inline-flex items-center;
.radio-item {
@apply flex items-center;
margin-left: 0.25rem;
gap: 0.25rem;
}
}
}
Expand Down
128 changes: 75 additions & 53 deletions apps/web/src/services/my-page/components/NotificationChannelList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,15 @@ import ErrorHandler from '@/common/composables/error/errorHandler';
import NotificationChannelItem from '@/services/my-page/components/NotificationChannelItem.vue';
import { MY_PAGE_ROUTE } from '@/services/my-page/routes/route-constant';
import type { NotiChannelItem } from '@/services/my-page/types/notification-channel-item-type';
import type { UserNotificationAddPageUrlQuery } from '@/services/my-page/types/user-notification-add-page-url-query-type';
import { PROJECT_ROUTE } from '@/services/project/routes/route-constant';
import type { ProjectNotificationAddPageUrlQuery } from '@/services/project/types/project-notification-add-page-url-query-type';
interface EnrichedProtocolItem extends ProtocolModel {
label: TranslateResult;
link: Partial<Location>;
protocolType: string;
tags: Tags;
icon: any;
id: string;
}
const allReferenceStore = useAllReferenceStore();
Expand All @@ -63,30 +61,35 @@ const state = reactive({
userId: computed<string>(() => (route.params.userId ? decodeURIComponent(route.params.userId) : store.state.user.userId)),
channelList: [] as NotiChannelItem[],
protocolResp: [] as ProtocolModel[],
protocolList: computed<EnrichedProtocolItem[]>(() => state.protocolResp.map((d) => {
const query: ProjectNotificationAddPageUrlQuery|UserNotificationAddPageUrlQuery = {
protocolLabel: d.name,
protocolType: d.protocol_type,
};
return {
label: i18n.t('IDENTITY.USER.NOTIFICATION.FORM.ADD_CHANNEL', { type: d.name }),
link: {
name: props.projectId ? PROJECT_ROUTE.DETAIL.TAB.NOTIFICATIONS.ADD._NAME : MY_PAGE_ROUTE.NOTIFICATION.ADD._NAME,
params: {
protocolId: d.protocol_id,
},
query,
},
protocolType: d.protocol_type,
tags: d.tags,
plugin_info: d.plugin_info,
icon: state.plugins[d.plugin_info?.plugin_id]?.icon || '',
name: d.name,
};
})),
defaultProtocolResp: computed<ProtocolModel[]>(() => state.protocolResp.filter((d) => d.protocol_type !== 'INTERNAL')),
associatedMemberProtocol: computed<EnrichedProtocolItem>(() => (
state.protocolResp.filter((d) => d.protocol_type === 'INTERNAL').map((d) => createProtocolItem(d))
)),
protocolList: computed<EnrichedProtocolItem[]>(() => (
state.defaultProtocolResp.map((d) => createProtocolItem(d))
)),
plugins: computed<PluginReferenceMap>(() => allReferenceStore.getters.plugin),
});
const createProtocolItem = (d) => {
const query = {
protocolLabel: d.name,
protocolType: d.protocol_type,
};
return {
label: d.protocol_type === 'INTERNAL' ? i18n.t('IAM.USER.NOTIFICATION.ASSOCIATED_MEMBER') : i18n.t('IDENTITY.USER.NOTIFICATION.FORM.ADD_CHANNEL', { type: d.name }),
link: {
name: props.projectId ? PROJECT_ROUTE.DETAIL.TAB.NOTIFICATIONS.ADD._NAME : MY_PAGE_ROUTE.NOTIFICATION.ADD._NAME,
params: { protocolId: d.protocol_id },
query,
},
protocolType: d.protocol_type,
tags: d.tags,
plugin_info: d.plugin_info,
icon: state.plugins[d.plugin_info?.plugin_id]?.icon || '',
name: d.name,
};
};
const apiQuery = new ApiQueryHelper();
const listProtocol = async () => {
Expand Down Expand Up @@ -198,42 +201,56 @@ onActivated(async () => {
:class="{disabled: props.manageDisabled}"
>
<router-link :to="item.link">
<li class="channel-item">
<p-lazy-img v-if="item.protocolType === 'INTERNAL'"
:src="assetUrlConverter('https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/notifications_member.svg')"
<li class="channel-item"
:class="{disabled: props.manageDisabled}"
>
<p-lazy-img :src="assetUrlConverter(item.icon)"
width="2.25rem"
height="2.25rem"
class="service-img"
/>
<p-lazy-img v-else
:src="assetUrlConverter(item.icon)"
<span class="text">
<p-i name="ic_plus_bold"
width="1rem"
height="1rem"
color="inherit transparent"
/>
{{ item.label }}
</span>
</li>
</router-link>
</ul>
</div>
<div class="associated-member-item-wrapper">
<ul v-for="item in state.associatedMemberProtocol"
:key="item.protocol_id"
class="associated-member-item"
:class="{disabled: props.manageDisabled}"
>
<router-link :to="item.link">
<li class="channel-item"
:class="{disabled: props.manageDisabled}"
>
<p-lazy-img :src="assetUrlConverter('https://spaceone-custom-assets.s3.ap-northeast-2.amazonaws.com/console-assets/icons/notifications_member.svg')"
width="2.25rem"
height="2.25rem"
class="service-img"
/>
<span class="text"
:class="{disabled: props.manageDisabled}"
>
<span class="text">
<p-i name="ic_plus_bold"
width="1rem"
height="1rem"
color="inherit transparent"
/>
{{ item.label }}
</span>
<span class="description">
{{ $t('IAM.USER.NOTIFICATION.ASSOCIATED_MEMBER_DESC') }}
</span>
</li>
</router-link>
</ul>
</div>
<p v-if="projectId"
class="spaceone-desc"
>
<p-i name="ic_users"
width="1.125rem"
class="mr-2"
/>
<b>SpaceOne User:</b> {{ $t('MY_PAGE.NOTIFICATION.SPACEONE_USER_DESC') }}
</p>
<template #no-data>
<p-empty class="empty-msg protocol">
{{ $t('MY_PAGE.NOTIFICATION.NO_PROTOCOL') }}
Expand Down Expand Up @@ -290,15 +307,6 @@ onActivated(async () => {
overflow-y: hidden;
}
}
.spaceone-desc {
@apply bg-gray-100 text-gray-700;
min-height: 2.125rem;
width: 100%;
margin-top: 0.5rem;
padding: 0.5rem 0.75rem;
line-height: 150%;
font-size: 0.75rem;
}
.channel-list-wrapper {
display: grid;
row-gap: 0.5rem;
Expand All @@ -308,6 +316,15 @@ onActivated(async () => {
gap: 0.5rem;
overflow-y: hidden;
}
.associated-member-item-wrapper {
@apply bg-gray-100;
margin-top: 0.75rem;
padding: 0.5rem;
border-radius: 0.375rem;
.associated-member-item {
@apply bg-white;
}
}
.channel-item-wrapper {
&.hide {
display: none;
Expand All @@ -325,13 +342,18 @@ onActivated(async () => {
align-items: center;
height: 8.625rem;
min-height: 8.625rem;
&.disabled {
.text, .description {
@apply text-gray-300;
}
}
.text {
@apply text-primaryDark font-bold;
text-align: center;
line-height: 160%;
&.disabled {
@apply text-gray-300;
}
}
.description {
@apply text-violet-800 text-label-sm text-center;
}
.item-desc {
@apply text-violet-800;
Expand Down
Loading

0 comments on commit 4709bdb

Please sign in to comment.