diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-17 18:09:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-17 18:09:28 +0300 |
commit | 6535cf9c79362862c31ea7d26c61541b84db18d9 (patch) | |
tree | 6d646edcf11d38e8ac23bceed1340ff8907b850d /app | |
parent | 9a8f801d7352b7965fe690a599410fb50005ce67 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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) |