Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-12-13 21:09:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-13 21:09:27 +0300
commit5248c5e2212b8e42b28b23e6839d69db0006829b (patch)
treef989d4b4cd06fc5dc28c024a5f230b42b0af179b /app/assets/javascripts/admin
parent0d55697d64b5f053bbd0f69da2962e7478097de3 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/admin')
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/base.vue5
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/datetime_picker.vue47
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/message_form.vue225
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue34
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue14
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/constants.js35
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/edit.js43
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/index.js5
8 files changed, 407 insertions, 1 deletions
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/base.vue b/app/assets/javascripts/admin/broadcast_messages/components/base.vue
index b7bafe46327..f869d21d55f 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/base.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/base.vue
@@ -5,14 +5,18 @@ import { buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
import { createAlert, VARIANT_DANGER } from '~/flash';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
+import { NEW_BROADCAST_MESSAGE } from '../constants';
+import MessageForm from './message_form.vue';
import MessagesTable from './messages_table.vue';
const PER_PAGE = 20;
export default {
name: 'BroadcastMessagesBase',
+ NEW_BROADCAST_MESSAGE,
components: {
GlPagination,
+ MessageForm,
MessagesTable,
},
@@ -97,6 +101,7 @@ export default {
<template>
<div>
+ <message-form :broadcast-message="$options.NEW_BROADCAST_MESSAGE" />
<messages-table
v-if="hasVisibleMessages"
:messages="visibleMessages"
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/datetime_picker.vue b/app/assets/javascripts/admin/broadcast_messages/components/datetime_picker.vue
new file mode 100644
index 00000000000..07814ef2511
--- /dev/null
+++ b/app/assets/javascripts/admin/broadcast_messages/components/datetime_picker.vue
@@ -0,0 +1,47 @@
+<script>
+import { GlDatepicker, GlFormInput } from '@gitlab/ui';
+import { dateToTimeInputValue, timeToHoursMinutes } from '~/lib/utils/datetime/date_format_utility';
+
+export default {
+ name: 'DatetimePicker',
+ components: {
+ GlDatepicker,
+ GlFormInput,
+ },
+ props: {
+ value: {
+ type: Date,
+ required: true,
+ },
+ },
+ computed: {
+ date: {
+ get() {
+ return this.value;
+ },
+ set(val) {
+ const dup = new Date(this.value.getTime());
+ dup.setFullYear(val.getFullYear(), val.getMonth(), val.getDate());
+ this.$emit('input', dup);
+ },
+ },
+ time: {
+ get() {
+ return dateToTimeInputValue(this.value);
+ },
+ set(val) {
+ const dup = new Date(this.value.getTime());
+ const { hours, minutes } = timeToHoursMinutes(val);
+ dup.setHours(hours, minutes);
+ this.$emit('input', dup);
+ },
+ },
+ },
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-gap-3 gl-align-items-center">
+ <gl-datepicker v-model="date" />
+ <gl-form-input v-model="time" size="sm" type="time" data-testid="time-picker" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
new file mode 100644
index 00000000000..36796708e78
--- /dev/null
+++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
@@ -0,0 +1,225 @@
+<script>
+import {
+ GlButton,
+ GlBroadcastMessage,
+ GlForm,
+ GlFormCheckbox,
+ GlFormCheckboxGroup,
+ GlFormInput,
+ GlFormSelect,
+ GlFormText,
+ GlFormTextarea,
+} from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
+import { s__ } from '~/locale';
+import { createAlert, VARIANT_DANGER } from '~/flash';
+import { redirectTo } from '~/lib/utils/url_utility';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { BROADCAST_MESSAGES_PATH, THEMES, TYPES, TYPE_BANNER } from '../constants';
+import MessageFormGroup from './message_form_group.vue';
+import DatetimePicker from './datetime_picker.vue';
+
+const FORM_HEADERS = { headers: { 'Content-Type': 'application/json; charset=utf-8' } };
+
+export default {
+ name: 'MessageForm',
+ components: {
+ DatetimePicker,
+ GlButton,
+ GlBroadcastMessage,
+ GlForm,
+ GlFormCheckbox,
+ GlFormCheckboxGroup,
+ GlFormInput,
+ GlFormSelect,
+ GlFormText,
+ GlFormTextarea,
+ MessageFormGroup,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ inject: ['targetAccessLevelOptions'],
+ i18n: {
+ message: s__('BroadcastMessages|Message'),
+ messagePlaceholder: s__('BroadcastMessages|Your message here'),
+ type: s__('BroadcastMessages|Type'),
+ theme: s__('BroadcastMessages|Theme'),
+ dismissable: s__('BroadcastMessages|Dismissable'),
+ dismissableDescription: s__('BroadcastMessages|Allow users to dismiss the broadcast message'),
+ targetRoles: s__('BroadcastMessages|Target roles'),
+ targetRolesDescription: s__(
+ 'BroadcastMessages|The broadcast message displays only to users in projects and groups who have these roles.',
+ ),
+ targetPath: s__('BroadcastMessages|Target Path'),
+ targetPathDescription: s__('BroadcastMessages|Paths can contain wildcards, like */welcome'),
+ startsAt: s__('BroadcastMessages|Starts at'),
+ endsAt: s__('BroadcastMessages|Ends at'),
+ add: s__('BroadcastMessages|Add broadcast message'),
+ addError: s__('BroadcastMessages|There was an error adding broadcast message.'),
+ update: s__('BroadcastMessages|Update broadcast message'),
+ updateError: s__('BroadcastMessages|There was an error updating broadcast message.'),
+ },
+ messageThemes: THEMES,
+ messageTypes: TYPES,
+ props: {
+ broadcastMessage: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ message: this.broadcastMessage.message,
+ type: this.broadcastMessage.broadcastType,
+ theme: this.broadcastMessage.theme,
+ dismissable: this.broadcastMessage.dismissable || false,
+ targetPath: this.broadcastMessage.targetPath,
+ targetAccessLevels: this.broadcastMessage.targetAccessLevels,
+ targetAccessLevelOptions: this.targetAccessLevelOptions.map(([text, value]) => ({
+ text,
+ value,
+ })),
+ startsAt: new Date(this.broadcastMessage.startsAt.getTime()),
+ endsAt: new Date(this.broadcastMessage.endsAt.getTime()),
+ };
+ },
+ computed: {
+ isBanner() {
+ return this.type === TYPE_BANNER;
+ },
+ messageBlank() {
+ return this.message.trim() === '';
+ },
+ messagePreview() {
+ return this.messageBlank ? this.$options.i18n.messagePlaceholder : this.message;
+ },
+ isAddForm() {
+ return !this.broadcastMessage.id;
+ },
+ formPath() {
+ return this.isAddForm
+ ? BROADCAST_MESSAGES_PATH
+ : `${BROADCAST_MESSAGES_PATH}/${this.broadcastMessage.id}`;
+ },
+ formPayload() {
+ return JSON.stringify({
+ message: this.message,
+ broadcast_type: this.type,
+ theme: this.theme,
+ dismissable: this.dismissable,
+ target_path: this.targetPath,
+ target_access_levels: this.targetAccessLevels,
+ starts_at: this.startsAt.toISOString(),
+ ends_at: this.endsAt.toISOString(),
+ });
+ },
+ },
+ methods: {
+ async onSubmit() {
+ this.loading = true;
+
+ const success = await this.submitForm();
+ if (success) {
+ redirectTo(BROADCAST_MESSAGES_PATH);
+ } else {
+ this.loading = false;
+ }
+ },
+
+ async submitForm() {
+ const requestMethod = this.isAddForm ? 'post' : 'patch';
+
+ try {
+ await axios[requestMethod](this.formPath, this.formPayload, FORM_HEADERS);
+ } catch (e) {
+ const message = this.isAddForm
+ ? this.$options.i18n.addError
+ : this.$options.i18n.updateError;
+ createAlert({ message, variant: VARIANT_DANGER });
+ return false;
+ }
+ return true;
+ },
+ },
+};
+</script>
+<template>
+ <gl-form @submit.prevent="onSubmit">
+ <gl-broadcast-message class="gl-my-6" :type="type" :theme="theme" :dismissible="dismissable">
+ {{ messagePreview }}
+ </gl-broadcast-message>
+
+ <message-form-group :label="$options.i18n.message" label-for="message-textarea">
+ <gl-form-textarea
+ id="message-textarea"
+ v-model="message"
+ size="sm"
+ :placeholder="$options.i18n.messagePlaceholder"
+ />
+ </message-form-group>
+
+ <message-form-group :label="$options.i18n.type" label-for="type-select">
+ <gl-form-select id="type-select" v-model="type" :options="$options.messageTypes" />
+ </message-form-group>
+
+ <template v-if="isBanner">
+ <message-form-group :label="$options.i18n.theme" label-for="theme-select">
+ <gl-form-select
+ id="theme-select"
+ v-model="theme"
+ :options="$options.messageThemes"
+ data-testid="theme-select"
+ />
+ </message-form-group>
+
+ <message-form-group :label="$options.i18n.dismissable" label-for="dismissable-checkbox">
+ <gl-form-checkbox
+ id="dismissable-checkbox"
+ v-model="dismissable"
+ class="gl-mt-3"
+ data-testid="dismissable-checkbox"
+ >
+ <span>{{ $options.i18n.dismissableDescription }}</span>
+ </gl-form-checkbox>
+ </message-form-group>
+ </template>
+
+ <message-form-group
+ v-if="glFeatures.roleTargetedBroadcastMessages"
+ :label="$options.i18n.targetRoles"
+ data-testid="target-roles-checkboxes"
+ >
+ <gl-form-checkbox-group v-model="targetAccessLevels" :options="targetAccessLevelOptions" />
+ <gl-form-text>
+ {{ $options.i18n.targetRolesDescription }}
+ </gl-form-text>
+ </message-form-group>
+
+ <message-form-group :label="$options.i18n.targetPath" label-for="target-path-input">
+ <gl-form-input id="target-path-input" v-model="targetPath" />
+ <gl-form-text>
+ {{ $options.i18n.targetPathDescription }}
+ </gl-form-text>
+ </message-form-group>
+
+ <message-form-group :label="$options.i18n.startsAt">
+ <datetime-picker v-model="startsAt" />
+ </message-form-group>
+
+ <message-form-group :label="$options.i18n.endsAt">
+ <datetime-picker v-model="endsAt" />
+ </message-form-group>
+
+ <div class="form-actions gl-mb-3">
+ <gl-button
+ type="submit"
+ variant="confirm"
+ :loading="loading"
+ :disabled="messageBlank"
+ data-testid="submit-button"
+ >
+ {{ isAddForm ? $options.i18n.add : $options.i18n.update }}
+ </gl-button>
+ </div>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue
new file mode 100644
index 00000000000..eec51c0c28b
--- /dev/null
+++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form_group.vue
@@ -0,0 +1,34 @@
+<script>
+import { GlFormGroup } from '@gitlab/ui';
+
+export default {
+ name: 'MessageFormGroup',
+ components: {
+ GlFormGroup,
+ },
+ props: {
+ label: {
+ type: String,
+ required: true,
+ },
+ labelFor: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-form-group
+ :label="label"
+ :label-for="labelFor"
+ label-cols-sm="2"
+ label-class="gl-mt-3"
+ label-align-sm="right"
+ >
+ <slot></slot>
+ </gl-form-group>
+ </div>
+</template>
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue b/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
index 44c6bc72705..a523dd3b391 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/messages_table.vue
@@ -2,6 +2,7 @@
import { GlButton, GlTableLite } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { __ } from '~/locale';
+import { formatDate } from '~/lib/utils/datetime/date_format_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const DEFAULT_TD_CLASSES = 'gl-vertical-align-middle!';
@@ -78,6 +79,11 @@ export default {
safeHtmlConfig: {
ADD_TAGS: ['use'],
},
+ methods: {
+ formatDate(dateString) {
+ return formatDate(new Date(dateString));
+ },
+ },
};
</script>
<template>
@@ -91,6 +97,14 @@ export default {
<div v-safe-html:[$options.safeHtmlConfig]="preview"></div>
</template>
+ <template #cell(starts_at)="{ item: { starts_at } }">
+ {{ formatDate(starts_at) }}
+ </template>
+
+ <template #cell(ends_at)="{ item: { ends_at } }">
+ {{ formatDate(ends_at) }}
+ </template>
+
<template #cell(buttons)="{ item: { id, edit_path, disable_delete } }">
<gl-button
icon="pencil"
diff --git a/app/assets/javascripts/admin/broadcast_messages/constants.js b/app/assets/javascripts/admin/broadcast_messages/constants.js
new file mode 100644
index 00000000000..6250d5a943d
--- /dev/null
+++ b/app/assets/javascripts/admin/broadcast_messages/constants.js
@@ -0,0 +1,35 @@
+import { s__ } from '~/locale';
+
+export const BROADCAST_MESSAGES_PATH = '/admin/broadcast_messages';
+
+export const TYPE_BANNER = 'banner';
+export const TYPE_NOTIFICATION = 'notification';
+
+export const TYPES = [
+ { value: TYPE_BANNER, text: s__('BroadcastMessages|Banner') },
+ { value: TYPE_NOTIFICATION, text: s__('BroadcastMessages|Notification') },
+];
+
+export const THEMES = [
+ { value: 'indigo', text: s__('BroadcastMessages|Indigo') },
+ { value: 'light-indigo', text: s__('BroadcastMessages|Light Indigo') },
+ { value: 'blue', text: s__('BroadcastMessages|Blue') },
+ { value: 'light-blue', text: s__('BroadcastMessages|Light Blue') },
+ { value: 'green', text: s__('BroadcastMessages|Green') },
+ { value: 'light-green', text: s__('BroadcastMessages|Light Green') },
+ { value: 'red', text: s__('BroadcastMessages|Red') },
+ { value: 'light-red', text: s__('BroadcastMessages|Light Red') },
+ { value: 'dark', text: s__('BroadcastMessages|Dark') },
+ { value: 'light', text: s__('BroadcastMessages|Light') },
+];
+
+export const NEW_BROADCAST_MESSAGE = {
+ message: '',
+ broadcastType: TYPES[0].value,
+ theme: THEMES[0].value,
+ dismissable: false,
+ targetPath: '',
+ targetAccessLevels: [],
+ startsAt: new Date(),
+ endsAt: new Date(),
+};
diff --git a/app/assets/javascripts/admin/broadcast_messages/edit.js b/app/assets/javascripts/admin/broadcast_messages/edit.js
new file mode 100644
index 00000000000..70a270f7a56
--- /dev/null
+++ b/app/assets/javascripts/admin/broadcast_messages/edit.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import MessageForm from './components/message_form.vue';
+
+export default () => {
+ const el = document.querySelector('#js-broadcast-message');
+ const {
+ id,
+ message,
+ broadcastType,
+ theme,
+ dismissable,
+ targetAccessLevels,
+ targetAccessLevelOptions,
+ targetPath,
+ startsAt,
+ endsAt,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ name: 'EditBroadcastMessage',
+ provide: {
+ targetAccessLevelOptions: JSON.parse(targetAccessLevelOptions),
+ },
+ render(createElement) {
+ return createElement(MessageForm, {
+ props: {
+ broadcastMessage: {
+ id: parseInt(id, 10),
+ message,
+ broadcastType,
+ theme,
+ dismissable: dismissable === 'true',
+ targetAccessLevels: JSON.parse(targetAccessLevels),
+ targetPath,
+ startsAt: new Date(startsAt),
+ endsAt: new Date(endsAt),
+ },
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/admin/broadcast_messages/index.js b/app/assets/javascripts/admin/broadcast_messages/index.js
index 81952d2033e..fd8b2aad4ec 100644
--- a/app/assets/javascripts/admin/broadcast_messages/index.js
+++ b/app/assets/javascripts/admin/broadcast_messages/index.js
@@ -3,11 +3,14 @@ import BroadcastMessagesBase from './components/base.vue';
export default () => {
const el = document.querySelector('#js-broadcast-messages');
- const { page, messagesCount, messages } = el.dataset;
+ const { page, targetAccessLevelOptions, messagesCount, messages } = el.dataset;
return new Vue({
el,
name: 'BroadcastMessages',
+ provide: {
+ targetAccessLevelOptions: JSON.parse(targetAccessLevelOptions),
+ },
render(createElement) {
return createElement(BroadcastMessagesBase, {
props: {