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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-17 18:09:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-17 18:09:28 +0300
commit6535cf9c79362862c31ea7d26c61541b84db18d9 (patch)
tree6d646edcf11d38e8ac23bceed1340ff8907b850d /app
parent9a8f801d7352b7965fe690a599410fb50005ce67 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue12
-rw-r--r--app/assets/javascripts/api.js3
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js15
-rw-r--r--app/assets/javascripts/header.js24
-rw-r--r--app/assets/javascripts/issue_show/components/header_actions.vue4
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue18
-rw-r--r--app/assets/javascripts/pages/groups/details/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/labels/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/labels/index/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/labels/new/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js18
-rw-r--r--app/assets/javascripts/set_status_modal/components/user_availability_status.vue26
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue61
-rw-r--r--app/assets/javascripts/set_status_modal/utils.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue9
-rw-r--r--app/helpers/page_layout_helper.rb11
-rw-r--r--app/helpers/profiles_helper.rb14
-rw-r--r--app/models/pages/lookup_path.rb36
-rw-r--r--app/services/concerns/users/participable_service.rb3
-rw-r--r--app/uploaders/gitlab_uploader.rb8
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml9
-rw-r--r--app/views/layouts/header/_default.html.haml3
-rw-r--r--app/views/profiles/show.html.haml10
-rw-r--r--app/views/projects/_merge_request_merge_options_settings.html.haml1
-rw-r--r--app/views/users/show.html.haml4
25 files changed, 252 insertions, 58 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
index a6a2d762fb6..12c0409629f 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
@@ -99,7 +99,17 @@ export default {
};
},
mounted() {
- this.trackPageViews();
+ const callback = entries => {
+ const isVisible = entries.some(entry => entry.isIntersecting);
+
+ if (isVisible) {
+ this.trackPageViews();
+ this.observer.disconnect();
+ }
+ };
+
+ this.observer = new IntersectionObserver(callback);
+ this.observer.observe(this.$el);
},
methods: {
tbodyTrClass(item) {
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 1fff097a799..f469f49ce20 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -566,12 +566,13 @@ const Api = {
});
},
- postUserStatus({ emoji, message }) {
+ postUserStatus({ emoji, message, availability }) {
const url = Api.buildUrl(this.userPostStatusPath);
return axios.put(url, {
emoji,
message,
+ availability,
});
},
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 62948f74aaa..7acd687ac81 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,7 +1,9 @@
import $ from 'jquery';
import '~/lib/utils/jquery_at_who';
import { escape, template } from 'lodash';
+import { s__ } from '~/locale';
import SidebarMediator from '~/sidebar/sidebar_mediator';
+import { isUserBusy } from '~/set_status_modal/utils';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
import { spriteIcon } from './lib/utils/common_utils';
@@ -39,6 +41,7 @@ export function membersBeforeSave(members) {
title: sanitize(title),
search: sanitize(`${member.username} ${member.name}`),
icon: avatarIcon,
+ availability: member.availability,
};
});
}
@@ -253,13 +256,17 @@ class GfmAutoComplete {
alias: 'users',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
- const { avatarTag, username, title, icon } = value;
+ const { avatarTag, username, title, icon, availability } = value;
if (username != null) {
tmpl = GfmAutoComplete.Members.templateFunction({
avatarTag,
username,
title,
icon,
+ availabilityStatus:
+ availability && isUserBusy(availability)
+ ? `<span class="gl-text-gray-500"> ${s__('UserAvailability|(Busy)')}</span>`
+ : '',
});
}
return tmpl;
@@ -775,8 +782,10 @@ GfmAutoComplete.Emoji = {
};
// Team Members
GfmAutoComplete.Members = {
- templateFunction({ avatarTag, username, title, icon }) {
- return `<li>${avatarTag} ${username} <small>${escape(title)}</small> ${icon}</li>`;
+ templateFunction({ avatarTag, username, title, icon, availabilityStatus }) {
+ return `<li>${avatarTag} ${username} <small>${escape(
+ title,
+ )}${availabilityStatus}</small> ${icon}</li>`;
},
};
GfmAutoComplete.Labels = {
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index b833cca1db6..1cedb557d46 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
+import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate';
import { highCountTrim } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
@@ -34,26 +35,45 @@ function initStatusTriggers() {
const statusModalElement = document.createElement('div');
setStatusModalWrapperEl.appendChild(statusModalElement);
+ Vue.use(GlToast);
Vue.use(Translate);
// eslint-disable-next-line no-new
new Vue({
el: statusModalElement,
data() {
- const { currentEmoji, currentMessage } = setStatusModalWrapperEl.dataset;
+ const {
+ currentEmoji,
+ defaultEmoji,
+ currentMessage,
+ currentAvailability,
+ canSetUserAvailability,
+ } = setStatusModalWrapperEl.dataset;
return {
currentEmoji,
+ defaultEmoji,
currentMessage,
+ currentAvailability,
+ canSetUserAvailability,
};
},
render(createElement) {
- const { currentEmoji, currentMessage } = this;
+ const {
+ currentEmoji,
+ defaultEmoji,
+ currentMessage,
+ currentAvailability,
+ canSetUserAvailability,
+ } = this;
return createElement(SetStatusModalWrapper, {
props: {
currentEmoji,
+ defaultEmoji,
currentMessage,
+ currentAvailability,
+ canSetUserAvailability,
},
});
},
diff --git a/app/assets/javascripts/issue_show/components/header_actions.vue b/app/assets/javascripts/issue_show/components/header_actions.vue
index ae0aca0fd27..4c8c86390f4 100644
--- a/app/assets/javascripts/issue_show/components/header_actions.vue
+++ b/app/assets/javascripts/issue_show/components/header_actions.vue
@@ -87,6 +87,9 @@ export default {
? sprintf(__('Reopen %{issueType}'), { issueType: this.issueType })
: sprintf(__('Close %{issueType}'), { issueType: this.issueType });
},
+ qaSelector() {
+ return this.isClosed ? 'reopen_issue_button' : 'close_issue_button';
+ },
buttonVariant() {
return this.isClosed ? 'default' : 'warning';
},
@@ -216,6 +219,7 @@ export default {
v-if="showToggleIssueStateButton"
class="gl-display-none gl-display-sm-inline-flex!"
category="secondary"
+ :data-qa-selector="qaSelector"
:loading="isUpdatingState"
:variant="buttonVariant"
@click="toggleIssueState"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 1a97756c689..cacf209ed81 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,7 +1,8 @@
<script>
/* eslint-disable vue/no-v-html */
import { mapActions } from 'vuex';
-import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlLoadingIcon, GlTooltipDirective, GlSprintf } from '@gitlab/ui';
+import { isUserBusy } from '~/set_status_modal/utils';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
@@ -11,6 +12,7 @@ export default {
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
GlIcon,
GlLoadingIcon,
+ GlSprintf,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -85,9 +87,16 @@ export default {
authorStatus() {
return this.author.status_tooltip_html;
},
+ authorIsBusy() {
+ const { status } = this.author;
+ return status?.availability && isUserBusy(status.availability);
+ },
emojiElement() {
return this.$refs?.authorStatus?.querySelector('gl-emoji');
},
+ authorName() {
+ return this.author.name;
+ },
},
mounted() {
this.emojiTitle = this.emojiElement ? this.emojiElement.getAttribute('title') : '';
@@ -146,7 +155,12 @@ export default {
:data-username="author.username"
>
<slot name="note-header-info"></slot>
- <span class="note-header-author-name bold">{{ author.name }}</span>
+ <span class="note-header-author-name gl-font-weight-bold">
+ <gl-sprintf v-if="authorIsBusy" :message="s__('UserAvailability|%{author} (Busy)')">
+ <template #author>{{ authorName }}</template>
+ </gl-sprintf>
+ <template v-else>{{ authorName }}</template>
+ </span>
</a>
<span
v-if="authorStatus"
diff --git a/app/assets/javascripts/pages/groups/details/index.js b/app/assets/javascripts/pages/groups/details/index.js
index 3bcaa0f0232..0417134f2a7 100644
--- a/app/assets/javascripts/pages/groups/details/index.js
+++ b/app/assets/javascripts/pages/groups/details/index.js
@@ -1,5 +1,3 @@
import initGroupDetails from '../shared/group_details';
-document.addEventListener('DOMContentLoaded', () => {
- initGroupDetails('details');
-});
+initGroupDetails('details');
diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js
index 83d6ac9fd14..2e8308fe084 100644
--- a/app/assets/javascripts/pages/groups/labels/edit/index.js
+++ b/app/assets/javascripts/pages/groups/labels/edit/index.js
@@ -1,3 +1,4 @@
import Labels from 'ee_else_ce/labels';
-document.addEventListener('DOMContentLoaded', () => new Labels());
+// eslint-disable-next-line no-new
+new Labels();
diff --git a/app/assets/javascripts/pages/groups/labels/index/index.js b/app/assets/javascripts/pages/groups/labels/index/index.js
index 6e45de2a724..87d522d7654 100644
--- a/app/assets/javascripts/pages/groups/labels/index/index.js
+++ b/app/assets/javascripts/pages/groups/labels/index/index.js
@@ -1,3 +1,3 @@
import initLabels from '~/init_labels';
-document.addEventListener('DOMContentLoaded', initLabels);
+initLabels();
diff --git a/app/assets/javascripts/pages/groups/labels/new/index.js b/app/assets/javascripts/pages/groups/labels/new/index.js
index 83d6ac9fd14..2e8308fe084 100644
--- a/app/assets/javascripts/pages/groups/labels/new/index.js
+++ b/app/assets/javascripts/pages/groups/labels/new/index.js
@@ -1,3 +1,4 @@
import Labels from 'ee_else_ce/labels';
-document.addEventListener('DOMContentLoaded', () => new Labels());
+// eslint-disable-next-line no-new
+new Labels();
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index 71c67ac74ed..2832cbed5ac 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -7,15 +7,13 @@ import { FILTERED_SEARCH } from '~/pages/constants';
const ISSUABLE_BULK_UPDATE_PREFIX = 'merge_request_';
-document.addEventListener('DOMContentLoaded', () => {
- addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
- issuableInitBulkUpdateSidebar.init(ISSUABLE_BULK_UPDATE_PREFIX);
+addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
+issuableInitBulkUpdateSidebar.init(ISSUABLE_BULK_UPDATE_PREFIX);
- initFilteredSearch({
- page: FILTERED_SEARCH.MERGE_REQUESTS,
- isGroupDecendent: true,
- useDefaultState: true,
- filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
- });
- projectSelect();
+initFilteredSearch({
+ page: FILTERED_SEARCH.MERGE_REQUESTS,
+ isGroupDecendent: true,
+ useDefaultState: true,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
+projectSelect();
diff --git a/app/assets/javascripts/set_status_modal/components/user_availability_status.vue b/app/assets/javascripts/set_status_modal/components/user_availability_status.vue
new file mode 100644
index 00000000000..e86d94f86c6
--- /dev/null
+++ b/app/assets/javascripts/set_status_modal/components/user_availability_status.vue
@@ -0,0 +1,26 @@
+<script>
+import { AVAILABILITY_STATUS, isUserBusy, isValidAvailibility } from '../utils';
+
+export default {
+ name: 'UserAvailabilityStatus',
+ props: {
+ availability: {
+ type: String,
+ required: true,
+ validator: isValidAvailibility,
+ },
+ },
+ computed: {
+ isBusy() {
+ const { availability = AVAILABILITY_STATUS.NOT_SET } = this;
+ return isUserBusy(availability);
+ },
+ },
+};
+</script>
+
+<template>
+ <span v-if="isBusy" class="gl-font-weight-normal gl-text-gray-500">{{
+ s__('UserAvailability|(Busy)')
+ }}</span>
+</template>
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index dcb76b4ab21..30e4e92d0cc 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -2,24 +2,35 @@
/* eslint-disable vue/no-v-html */
import $ from 'jquery';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
-import { GlModal, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { GlModal, GlTooltipDirective, GlIcon, GlFormCheckbox } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __, s__ } from '~/locale';
import Api from '~/api';
import EmojiMenuInModal from './emoji_menu_in_modal';
+import { isUserBusy, isValidAvailibility } from './utils';
import * as Emoji from '~/emoji';
const emojiMenuClass = 'js-modal-status-emoji-menu';
+export const AVAILABILITY_STATUS = {
+ BUSY: 'busy',
+ NOT_SET: 'not_set',
+};
export default {
components: {
GlIcon,
GlModal,
+ GlFormCheckbox,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
+ defaultEmoji: {
+ type: String,
+ required: false,
+ default: '',
+ },
currentEmoji: {
type: String,
required: true,
@@ -28,6 +39,17 @@ export default {
type: String,
required: true,
},
+ currentAvailability: {
+ type: String,
+ required: false,
+ validator: isValidAvailibility,
+ default: '',
+ },
+ canSetUserAvailability: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -39,11 +61,15 @@ export default {
message: this.currentMessage,
modalId: 'set-user-status-modal',
noEmoji: true,
+ availability: isUserBusy(this.currentAvailability),
};
},
computed: {
+ isCustomEmoji() {
+ return this.emoji !== this.defaultEmoji;
+ },
isDirty() {
- return this.message.length || this.emoji.length;
+ return Boolean(this.message.length || this.isCustomEmoji);
},
},
mounted() {
@@ -67,7 +93,7 @@ export default {
this.emojiTag = Emoji.glEmojiTag(this.emoji);
}
this.noEmoji = this.emoji === '';
- this.defaultEmojiTag = Emoji.glEmojiTag('speech_balloon');
+ this.defaultEmojiTag = Emoji.glEmojiTag(this.defaultEmoji);
this.emojiMenu = new EmojiMenuInModal(
Emoji,
@@ -76,6 +102,7 @@ export default {
this.setEmoji,
this.$refs.userStatusForm,
);
+ this.setDefaultEmoji();
})
.catch(() => createFlash(__('Failed to load emoji list.')));
},
@@ -94,7 +121,7 @@ export default {
},
setDefaultEmoji() {
const { emojiTag } = this;
- const hasStatusMessage = this.message;
+ const hasStatusMessage = Boolean(this.message.length);
if (hasStatusMessage && emojiTag) {
return;
}
@@ -126,20 +153,26 @@ export default {
this.hideEmojiMenu();
},
removeStatus() {
+ this.availability = false;
this.clearStatusInputs();
this.setStatus();
},
setStatus() {
- const { emoji, message } = this;
+ const { emoji, message, availability } = this;
Api.postUserStatus({
emoji,
message,
+ availability: availability ? AVAILABILITY_STATUS.BUSY : AVAILABILITY_STATUS.NOT_SET,
})
.then(this.onUpdateSuccess)
.catch(this.onUpdateFail);
},
onUpdateSuccess() {
+ this.$toast.show(s__('SetStatusModal|Status updated'), {
+ type: 'success',
+ position: 'top-center',
+ });
this.closeModal();
window.location.reload();
},
@@ -175,7 +208,7 @@ export default {
name="user[status][emoji]"
/>
<div ref="userStatusForm" class="form-group position-relative m-0">
- <div class="input-group">
+ <div class="input-group gl-mb-5">
<span class="input-group-prepend">
<button
ref="toggleEmojiMenuButton"
@@ -223,6 +256,22 @@ export default {
</button>
</span>
</div>
+ <div v-if="canSetUserAvailability" class="form-group">
+ <div class="gl-display-flex">
+ <gl-form-checkbox
+ v-model="availability"
+ data-testid="user-availability-checkbox"
+ class="gl-mb-0"
+ >
+ <span class="gl-font-weight-bold">{{ s__('SetStatusModal|Busy') }}</span>
+ </gl-form-checkbox>
+ </div>
+ <div class="gl-display-flex">
+ <span class="gl-text-gray-600 gl-ml-5">
+ {{ s__('SetStatusModal|"Busy" will be shown next to your name') }}
+ </span>
+ </div>
+ </div>
</div>
</div>
</gl-modal>
diff --git a/app/assets/javascripts/set_status_modal/utils.js b/app/assets/javascripts/set_status_modal/utils.js
new file mode 100644
index 00000000000..dccb66be11f
--- /dev/null
+++ b/app/assets/javascripts/set_status_modal/utils.js
@@ -0,0 +1,9 @@
+export const AVAILABILITY_STATUS = {
+ BUSY: 'busy',
+ NOT_SET: 'not_set',
+};
+
+export const isUserBusy = status => status === AVAILABILITY_STATUS.BUSY;
+
+export const isValidAvailibility = availability =>
+ availability.length ? Object.values(AVAILABILITY_STATUS).includes(availability) : true;
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 3f5738b2b93..2ab4c55d9b0 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -6,6 +6,7 @@ import {
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlIcon,
} from '@gitlab/ui';
+import UserAvailabilityStatus from '~/set_status_modal/components/user_availability_status.vue';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
@@ -25,6 +26,7 @@ export default {
GlPopover,
GlSkeletonLoading,
UserAvatarImage,
+ UserAvailabilityStatus,
},
props: {
target: {
@@ -63,6 +65,9 @@ export default {
websiteUrl.length
);
},
+ availabilityStatus() {
+ return this.user?.status?.availability || null;
+ },
},
};
</script>
@@ -89,6 +94,10 @@ export default {
<div class="gl-mb-3">
<h5 class="gl-m-0">
{{ user.name }}
+ <user-availability-status
+ v-if="availabilityStatus"
+ :availability="availabilityStatus"
+ />
</h5>
<span class="gl-text-gray-500">@{{ user.username }}</span>
</div>
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index e39dfa44d86..e3d82e7a091 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -158,6 +158,17 @@ module PageLayoutHelper
end
end
+ def user_status_properties(user)
+ default_properties = { current_emoji: '', current_message: '', can_set_user_availability: Feature.enabled?(:set_user_availability_status, user), default_emoji: UserStatus::DEFAULT_EMOJI }
+ return default_properties unless user&.status
+
+ default_properties.merge({
+ current_emoji: user.status.emoji.to_s,
+ current_message: user.status.message.to_s,
+ current_availability: user.status.availability.to_s
+ })
+ end
+
private
def generic_canonical_url
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index 5a42e581867..04a3b915493 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -29,4 +29,18 @@ module ProfilesHelper
def user_profile?
params[:controller] == 'users'
end
+
+ def availability_values
+ Types::AvailabilityEnum.enum
+ end
+
+ def user_status_set_to_busy?(status)
+ status&.availability == availability_values[:busy]
+ end
+
+ def show_status_emoji?(status)
+ return false unless status
+
+ status.message.present? || status.emoji != UserStatus::DEFAULT_EMOJI
+ end
end
diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb
index 89f6591ea1e..9855731778f 100644
--- a/app/models/pages/lookup_path.rb
+++ b/app/models/pages/lookup_path.rb
@@ -40,37 +40,35 @@ module Pages
def artifacts_archive
return unless Feature.enabled?(:pages_serve_from_artifacts_archive, project)
- archive = project.pages_metadatum.artifacts_archive
-
- archive&.file
+ project.pages_metadatum.artifacts_archive
end
def deployment
return unless Feature.enabled?(:pages_serve_from_deployments, project)
- deployment = project.pages_metadatum.pages_deployment
-
- deployment&.file
+ project.pages_metadatum.pages_deployment
end
def zip_source
source = deployment || artifacts_archive
- return unless source
+ return unless source&.file
- if source.file_storage?
- return unless Feature.enabled?(:pages_serve_with_zip_file_protocol, project)
+ return if source.file.file_storage? && !Feature.enabled?(:pages_serve_with_zip_file_protocol, project)
- {
- type: 'zip',
- path: 'file://' + source.path
- }
- else
- {
- type: 'zip',
- path: source.url(expire_at: 1.day.from_now)
- }
- end
+ # artifacts archive doesn't support this
+ file_count = source.file_count if source.respond_to?(:file_count)
+
+ global_id = ::Gitlab::GlobalId.build(source, id: source.id).to_s
+
+ {
+ type: 'zip',
+ path: source.file.url_or_file_path(expire_at: 1.day.from_now),
+ global_id: global_id,
+ sha256: source.file_sha256,
+ file_size: source.size,
+ file_count: file_count
+ }
end
def file_source
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 6fde9abfdb0..fac8e91d216 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -45,7 +45,8 @@ module Users
type: user.class.name,
username: user.username,
name: user.name,
- avatar_url: user.avatar_url
+ avatar_url: user.avatar_url,
+ availability: user&.status&.availability
}
end
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 411d8b2614f..9758d3c87aa 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -118,6 +118,14 @@ class GitlabUploader < CarrierWave::Uploader::Base
storage.store!(file)
end
+ def url_or_file_path(url_options = {})
+ if file_storage?
+ 'file://' + path
+ else
+ url(url_options)
+ end
+ end
+
private
# Designed to be overridden by child uploaders that have a dynamic path
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 4c6bfc0b33c..addf2375222 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -2,13 +2,16 @@
%ul
%li.current-user
- .user-name.bold
+ .user-name.gl-font-weight-bold
= current_user.name
+ - if current_user&.status && user_status_set_to_busy?(current_user.status)
+ %span.gl-font-weight-normal.gl-text-gray-500= s_("UserProfile|(Busy)")
= current_user.to_reference
- if current_user.status
.user-status.d-flex.align-items-center.gl-mt-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
- %span.user-status-emoji.d-flex.align-items-center
- = emoji_icon current_user.status.emoji
+ - if show_status_emoji?(current_user.status)
+ .user-status-emoji.d-flex.align-items-center
+ = emoji_icon current_user.status.emoji
%span.user-status-message.str-truncated
= current_user.status.message_html.html_safe
%li.divider
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index f6dc808aa55..794d1589172 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,4 +1,5 @@
- has_impersonation_link = header_link?(:admin_impersonation)
+- user_status_data = user_status_properties(current_user)
%header.navbar.navbar-gitlab.navbar-expand-sm.js-navbar{ data: { qa_selector: 'navbar' } }
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
@@ -103,4 +104,4 @@
#whats-new-app{ data: { storage_key: whats_new_storage_key } }
- if can?(current_user, :update_user_status, current_user)
- .js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } }
+ .js-set-status-modal-wrapper{ data: user_status_data }
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index f5fab727a57..bf9f1336a4f 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -2,6 +2,8 @@
- page_title s_("Profiles|Edit Profile")
- @content_class = "limit-container-width" unless fluid_layout
- gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host
+- availability = availability_values
+- custom_emoji = show_status_emoji?(@user.status)
= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user gl-mt-3 js-quick-submit gl-show-field-errors' }, authenticity_token: true do |f|
= form_errors(@user)
@@ -48,9 +50,9 @@
- emoji_button = button_tag type: :button,
class: 'js-toggle-emoji-menu emoji-menu-toggle-button gl-button btn has-tooltip',
title: s_("Profiles|Add status emoji") do
- - if @user.status
+ - if custom_emoji
= emoji_icon @user.status.emoji
- %span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) }
+ %span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if custom_emoji) }
= sprite_icon('slight-smile', css_class: 'award-control-icon-neutral')
= sprite_icon('smiley', css_class: 'award-control-icon-positive')
= sprite_icon('smile', css_class: 'award-control-icon-super-positive')
@@ -68,6 +70,10 @@
prepend: emoji_button,
append: reset_message_button,
placeholder: s_("Profiles|What's your status?")
+ - if Feature.enabled?(:set_user_availability_status, @user)
+ .checkbox-icon-inline-wrapper
+ = status_form.check_box :availability, { data: { testid: "user-availability-checkbox" }, label: s_("Profiles|Busy"), wrapper_class: 'gl-mr-0 gl-font-weight-bold' }, availability["busy"], availability["not_set"]
+ .gl-text-gray-600.gl-ml-5= s_('Profiles|"Busy" will be shown next to your name')
- if Feature.enabled?(:user_time_settings)
%hr
.row.user-time-preferences
diff --git a/app/views/projects/_merge_request_merge_options_settings.html.haml b/app/views/projects/_merge_request_merge_options_settings.html.haml
index 047b4dafbfc..8951f2ed22f 100644
--- a/app/views/projects/_merge_request_merge_options_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_options_settings.html.haml
@@ -4,6 +4,7 @@
%b= s_('ProjectSettings|Merge options')
%p.text-secondary= s_('ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed')
= render_if_exists 'projects/merge_pipelines_settings', form: form
+ = render_if_exists 'projects/merge_trains_settings', form: form
.form-check.mb-2
= form.check_box :resolve_outdated_diff_discussions, class: 'form-check-input'
= form.label :resolve_outdated_diff_discussions, class: 'form-check-label' do
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 90e0b230948..ee037a7d66a 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -47,8 +47,10 @@
.user-info
.cover-title{ itemprop: 'name' }
= @user.name
+ - if @user&.status && user_status_set_to_busy?(@user.status)
+ %span.gl-font-base.gl-text-gray-500.gl-vertical-align-middle= s_("UserProfile|(Busy)")
- - if @user.status
+ - if show_status_emoji?(@user.status)
.cover-status
= emoji_icon(@user.status.emoji)
= markdown_field(@user.status, :message)