Skip to content

Commit 5248c5e

Browse files
author
GitLab Bot
committed
Add latest changes from gitlab-org/gitlab@master
1 parent 0d55697 commit 5248c5e

File tree

160 files changed

+2601
-892
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

160 files changed

+2601
-892
lines changed

.lefthook/pre-push/merge_conflicts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
3+
# Adjusted from https://gitlab.com/fdegier/pre-push-hooks with hardcoded values for speed
4+
ORIGIN=origin
5+
DEFAULT_BRANCH=master
6+
7+
if [[ -n "$ORIGIN" ]]
8+
then
9+
# Pull the default branch from remote
10+
git fetch --quiet origin "$DEFAULT_BRANCH":"$DEFAULT_BRANCH"
11+
fi
12+
13+
# Check for merge conflicts and abort
14+
if git merge --autostash --no-commit --no-ff --no-edit "$DEFAULT_BRANCH" > /dev/null 2>&1
15+
then
16+
# Able to merge without conflicts
17+
git merge --abort > /dev/null 2>&1
18+
exit 0
19+
else
20+
echo "Merge conflicts detected when merging to $DEFAULT_BRANCH!"
21+
git merge --abort > /dev/null 2>&1
22+
exit 1
23+
fi

.rubocop_todo/gitlab/strong_memoize_attr.yml

-1
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,6 @@ Gitlab/StrongMemoizeAttr:
619619
- 'lib/gitlab/ci/pipeline/logger.rb'
620620
- 'lib/gitlab/ci/pipeline/metrics.rb'
621621
- 'lib/gitlab/ci/pipeline/quota/deployments.rb'
622-
- 'lib/gitlab/ci/pipeline/seed/pipeline.rb'
623622
- 'lib/gitlab/ci/pipeline/seed/processable/resource_group.rb'
624623
- 'lib/gitlab/ci/project_config/auto_devops.rb'
625624
- 'lib/gitlab/ci/project_config/external_project.rb'

GITALY_SERVER_VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
b3df64b5c2838a52aed21700299f7aa69b82c992
1+
52fb0853a49b14abddfc5a824d680bd255773d39

app/assets/javascripts/admin/broadcast_messages/components/base.vue

+5
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ import { buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
55
import { createAlert, VARIANT_DANGER } from '~/flash';
66
import { s__ } from '~/locale';
77
import axios from '~/lib/utils/axios_utils';
8+
import { NEW_BROADCAST_MESSAGE } from '../constants';
9+
import MessageForm from './message_form.vue';
810
import MessagesTable from './messages_table.vue';
911
1012
const PER_PAGE = 20;
1113
1214
export default {
1315
name: 'BroadcastMessagesBase',
16+
NEW_BROADCAST_MESSAGE,
1417
components: {
1518
GlPagination,
19+
MessageForm,
1620
MessagesTable,
1721
},
1822
@@ -97,6 +101,7 @@ export default {
97101

98102
<template>
99103
<div>
104+
<message-form :broadcast-message="$options.NEW_BROADCAST_MESSAGE" />
100105
<messages-table
101106
v-if="hasVisibleMessages"
102107
:messages="visibleMessages"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script>
2+
import { GlDatepicker, GlFormInput } from '@gitlab/ui';
3+
import { dateToTimeInputValue, timeToHoursMinutes } from '~/lib/utils/datetime/date_format_utility';
4+
5+
export default {
6+
name: 'DatetimePicker',
7+
components: {
8+
GlDatepicker,
9+
GlFormInput,
10+
},
11+
props: {
12+
value: {
13+
type: Date,
14+
required: true,
15+
},
16+
},
17+
computed: {
18+
date: {
19+
get() {
20+
return this.value;
21+
},
22+
set(val) {
23+
const dup = new Date(this.value.getTime());
24+
dup.setFullYear(val.getFullYear(), val.getMonth(), val.getDate());
25+
this.$emit('input', dup);
26+
},
27+
},
28+
time: {
29+
get() {
30+
return dateToTimeInputValue(this.value);
31+
},
32+
set(val) {
33+
const dup = new Date(this.value.getTime());
34+
const { hours, minutes } = timeToHoursMinutes(val);
35+
dup.setHours(hours, minutes);
36+
this.$emit('input', dup);
37+
},
38+
},
39+
},
40+
};
41+
</script>
42+
<template>
43+
<div class="gl-display-flex gl-gap-3 gl-align-items-center">
44+
<gl-datepicker v-model="date" />
45+
<gl-form-input v-model="time" size="sm" type="time" data-testid="time-picker" />
46+
</div>
47+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<script>
2+
import {
3+
GlButton,
4+
GlBroadcastMessage,
5+
GlForm,
6+
GlFormCheckbox,
7+
GlFormCheckboxGroup,
8+
GlFormInput,
9+
GlFormSelect,
10+
GlFormText,
11+
GlFormTextarea,
12+
} from '@gitlab/ui';
13+
import axios from '~/lib/utils/axios_utils';
14+
import { s__ } from '~/locale';
15+
import { createAlert, VARIANT_DANGER } from '~/flash';
16+
import { redirectTo } from '~/lib/utils/url_utility';
17+
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
18+
import { BROADCAST_MESSAGES_PATH, THEMES, TYPES, TYPE_BANNER } from '../constants';
19+
import MessageFormGroup from './message_form_group.vue';
20+
import DatetimePicker from './datetime_picker.vue';
21+
22+
const FORM_HEADERS = { headers: { 'Content-Type': 'application/json; charset=utf-8' } };
23+
24+
export default {
25+
name: 'MessageForm',
26+
components: {
27+
DatetimePicker,
28+
GlButton,
29+
GlBroadcastMessage,
30+
GlForm,
31+
GlFormCheckbox,
32+
GlFormCheckboxGroup,
33+
GlFormInput,
34+
GlFormSelect,
35+
GlFormText,
36+
GlFormTextarea,
37+
MessageFormGroup,
38+
},
39+
mixins: [glFeatureFlagsMixin()],
40+
inject: ['targetAccessLevelOptions'],
41+
i18n: {
42+
message: s__('BroadcastMessages|Message'),
43+
messagePlaceholder: s__('BroadcastMessages|Your message here'),
44+
type: s__('BroadcastMessages|Type'),
45+
theme: s__('BroadcastMessages|Theme'),
46+
dismissable: s__('BroadcastMessages|Dismissable'),
47+
dismissableDescription: s__('BroadcastMessages|Allow users to dismiss the broadcast message'),
48+
targetRoles: s__('BroadcastMessages|Target roles'),
49+
targetRolesDescription: s__(
50+
'BroadcastMessages|The broadcast message displays only to users in projects and groups who have these roles.',
51+
),
52+
targetPath: s__('BroadcastMessages|Target Path'),
53+
targetPathDescription: s__('BroadcastMessages|Paths can contain wildcards, like */welcome'),
54+
startsAt: s__('BroadcastMessages|Starts at'),
55+
endsAt: s__('BroadcastMessages|Ends at'),
56+
add: s__('BroadcastMessages|Add broadcast message'),
57+
addError: s__('BroadcastMessages|There was an error adding broadcast message.'),
58+
update: s__('BroadcastMessages|Update broadcast message'),
59+
updateError: s__('BroadcastMessages|There was an error updating broadcast message.'),
60+
},
61+
messageThemes: THEMES,
62+
messageTypes: TYPES,
63+
props: {
64+
broadcastMessage: {
65+
type: Object,
66+
required: true,
67+
},
68+
},
69+
data() {
70+
return {
71+
loading: false,
72+
message: this.broadcastMessage.message,
73+
type: this.broadcastMessage.broadcastType,
74+
theme: this.broadcastMessage.theme,
75+
dismissable: this.broadcastMessage.dismissable || false,
76+
targetPath: this.broadcastMessage.targetPath,
77+
targetAccessLevels: this.broadcastMessage.targetAccessLevels,
78+
targetAccessLevelOptions: this.targetAccessLevelOptions.map(([text, value]) => ({
79+
text,
80+
value,
81+
})),
82+
startsAt: new Date(this.broadcastMessage.startsAt.getTime()),
83+
endsAt: new Date(this.broadcastMessage.endsAt.getTime()),
84+
};
85+
},
86+
computed: {
87+
isBanner() {
88+
return this.type === TYPE_BANNER;
89+
},
90+
messageBlank() {
91+
return this.message.trim() === '';
92+
},
93+
messagePreview() {
94+
return this.messageBlank ? this.$options.i18n.messagePlaceholder : this.message;
95+
},
96+
isAddForm() {
97+
return !this.broadcastMessage.id;
98+
},
99+
formPath() {
100+
return this.isAddForm
101+
? BROADCAST_MESSAGES_PATH
102+
: `${BROADCAST_MESSAGES_PATH}/${this.broadcastMessage.id}`;
103+
},
104+
formPayload() {
105+
return JSON.stringify({
106+
message: this.message,
107+
broadcast_type: this.type,
108+
theme: this.theme,
109+
dismissable: this.dismissable,
110+
target_path: this.targetPath,
111+
target_access_levels: this.targetAccessLevels,
112+
starts_at: this.startsAt.toISOString(),
113+
ends_at: this.endsAt.toISOString(),
114+
});
115+
},
116+
},
117+
methods: {
118+
async onSubmit() {
119+
this.loading = true;
120+
121+
const success = await this.submitForm();
122+
if (success) {
123+
redirectTo(BROADCAST_MESSAGES_PATH);
124+
} else {
125+
this.loading = false;
126+
}
127+
},
128+
129+
async submitForm() {
130+
const requestMethod = this.isAddForm ? 'post' : 'patch';
131+
132+
try {
133+
await axios[requestMethod](this.formPath, this.formPayload, FORM_HEADERS);
134+
} catch (e) {
135+
const message = this.isAddForm
136+
? this.$options.i18n.addError
137+
: this.$options.i18n.updateError;
138+
createAlert({ message, variant: VARIANT_DANGER });
139+
return false;
140+
}
141+
return true;
142+
},
143+
},
144+
};
145+
</script>
146+
<template>
147+
<gl-form @submit.prevent="onSubmit">
148+
<gl-broadcast-message class="gl-my-6" :type="type" :theme="theme" :dismissible="dismissable">
149+
{{ messagePreview }}
150+
</gl-broadcast-message>
151+
152+
<message-form-group :label="$options.i18n.message" label-for="message-textarea">
153+
<gl-form-textarea
154+
id="message-textarea"
155+
v-model="message"
156+
size="sm"
157+
:placeholder="$options.i18n.messagePlaceholder"
158+
/>
159+
</message-form-group>
160+
161+
<message-form-group :label="$options.i18n.type" label-for="type-select">
162+
<gl-form-select id="type-select" v-model="type" :options="$options.messageTypes" />
163+
</message-form-group>
164+
165+
<template v-if="isBanner">
166+
<message-form-group :label="$options.i18n.theme" label-for="theme-select">
167+
<gl-form-select
168+
id="theme-select"
169+
v-model="theme"
170+
:options="$options.messageThemes"
171+
data-testid="theme-select"
172+
/>
173+
</message-form-group>
174+
175+
<message-form-group :label="$options.i18n.dismissable" label-for="dismissable-checkbox">
176+
<gl-form-checkbox
177+
id="dismissable-checkbox"
178+
v-model="dismissable"
179+
class="gl-mt-3"
180+
data-testid="dismissable-checkbox"
181+
>
182+
<span>{{ $options.i18n.dismissableDescription }}</span>
183+
</gl-form-checkbox>
184+
</message-form-group>
185+
</template>
186+
187+
<message-form-group
188+
v-if="glFeatures.roleTargetedBroadcastMessages"
189+
:label="$options.i18n.targetRoles"
190+
data-testid="target-roles-checkboxes"
191+
>
192+
<gl-form-checkbox-group v-model="targetAccessLevels" :options="targetAccessLevelOptions" />
193+
<gl-form-text>
194+
{{ $options.i18n.targetRolesDescription }}
195+
</gl-form-text>
196+
</message-form-group>
197+
198+
<message-form-group :label="$options.i18n.targetPath" label-for="target-path-input">
199+
<gl-form-input id="target-path-input" v-model="targetPath" />
200+
<gl-form-text>
201+
{{ $options.i18n.targetPathDescription }}
202+
</gl-form-text>
203+
</message-form-group>
204+
205+
<message-form-group :label="$options.i18n.startsAt">
206+
<datetime-picker v-model="startsAt" />
207+
</message-form-group>
208+
209+
<message-form-group :label="$options.i18n.endsAt">
210+
<datetime-picker v-model="endsAt" />
211+
</message-form-group>
212+
213+
<div class="form-actions gl-mb-3">
214+
<gl-button
215+
type="submit"
216+
variant="confirm"
217+
:loading="loading"
218+
:disabled="messageBlank"
219+
data-testid="submit-button"
220+
>
221+
{{ isAddForm ? $options.i18n.add : $options.i18n.update }}
222+
</gl-button>
223+
</div>
224+
</gl-form>
225+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script>
2+
import { GlFormGroup } from '@gitlab/ui';
3+
4+
export default {
5+
name: 'MessageFormGroup',
6+
components: {
7+
GlFormGroup,
8+
},
9+
props: {
10+
label: {
11+
type: String,
12+
required: true,
13+
},
14+
labelFor: {
15+
type: String,
16+
required: false,
17+
default: '',
18+
},
19+
},
20+
};
21+
</script>
22+
<template>
23+
<div>
24+
<gl-form-group
25+
:label="label"
26+
:label-for="labelFor"
27+
label-cols-sm="2"
28+
label-class="gl-mt-3"
29+
label-align-sm="right"
30+
>
31+
<slot></slot>
32+
</gl-form-group>
33+
</div>
34+
</template>

0 commit comments

Comments
 (0)