diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-28 12:08:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-28 12:08:05 +0300 |
commit | 4c788f43cbcd70bcceb4e40891d329952aa016d0 (patch) | |
tree | 9cd741579d79355a207ab74d37f26af768281fa0 /app | |
parent | 616129d41caac06a1ea0b0a36d1b3018eabac833 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
26 files changed, 417 insertions, 116 deletions
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index c2166de351b..bf5aee2a2a4 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -83,13 +83,7 @@ class List { } destroy() { - const index = boardsStore.state.lists.indexOf(this); - boardsStore.state.lists.splice(index, 1); - boardsStore.updateNewListDropdown(this.id); - - boardsStore.destroyList(this.id).catch(() => { - // TODO: handle request error - }); + boardsStore.destroy(this); } update() { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 6f66bc5d9b7..cde7256a258 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -547,6 +547,15 @@ const boardsStore = { destroyList(id) { return axios.delete(`${this.state.endpoints.listsEndpoint}/${id}`); }, + destroy(list) { + const index = this.state.lists.indexOf(list); + this.state.lists.splice(index, 1); + this.updateNewListDropdown(list.id); + + this.destroyList(list.id).catch(() => { + // TODO: handle request error + }); + }, saveList(list) { const entity = list.label || list.assignee || list.milestone; diff --git a/app/assets/javascripts/logs/constants.js b/app/assets/javascripts/logs/constants.js index 51770aa7a1c..2bca1ecfd38 100644 --- a/app/assets/javascripts/logs/constants.js +++ b/app/assets/javascripts/logs/constants.js @@ -1,3 +1,10 @@ export const dateFormatMask = 'mmm dd HH:MM:ss.l'; export const TOKEN_TYPE_POD_NAME = 'TOKEN_TYPE_POD_NAME'; + +export const tracking = { + USED_SEARCH_BAR: 'used_search_bar', + POD_LOG_CHANGED: 'pod_log_changed', + TIME_RANGE_SET: 'time_range_set', + ENVIRONMENT_SELECTED: 'environment_selected', +}; diff --git a/app/assets/javascripts/logs/logs_tracking_helper.js b/app/assets/javascripts/logs/logs_tracking_helper.js new file mode 100644 index 00000000000..91b0392f71f --- /dev/null +++ b/app/assets/javascripts/logs/logs_tracking_helper.js @@ -0,0 +1,18 @@ +import Tracking from '~/tracking'; + +/** + * The value of 1 in count, means there was one action performed + * related to the tracked action, in either of the following categories + * 1. Refreshing the logs + * 2. Select an environment + * 3. Change the time range + * 4. Use the search bar + */ +const trackLogs = label => + Tracking.event(document.body.dataset.page, 'logs_view', { + label, + property: 'count', + value: 1, + }); + +export default trackLogs; diff --git a/app/assets/javascripts/logs/stores/actions.js b/app/assets/javascripts/logs/stores/actions.js index a86d3c775a9..79fde1c7f2b 100644 --- a/app/assets/javascripts/logs/stores/actions.js +++ b/app/assets/javascripts/logs/stores/actions.js @@ -2,7 +2,8 @@ import { backOff } from '~/lib/utils/common_utils'; import httpStatusCodes from '~/lib/utils/http_status'; import axios from '~/lib/utils/axios_utils'; import { convertToFixedRange } from '~/lib/utils/datetime_range'; -import { TOKEN_TYPE_POD_NAME } from '../constants'; +import { TOKEN_TYPE_POD_NAME, tracking } from '../constants'; +import trackLogs from '../logs_tracking_helper'; import * as types from './mutation_types'; @@ -81,22 +82,22 @@ export const showFilteredLogs = ({ dispatch, commit }, filters = []) => { commit(types.SET_CURRENT_POD_NAME, podName); commit(types.SET_SEARCH, search); - dispatch('fetchLogs'); + dispatch('fetchLogs', tracking.USED_SEARCH_BAR); }; export const showPodLogs = ({ dispatch, commit }, podName) => { commit(types.SET_CURRENT_POD_NAME, podName); - dispatch('fetchLogs'); + dispatch('fetchLogs', tracking.POD_LOG_CHANGED); }; export const setTimeRange = ({ dispatch, commit }, timeRange) => { commit(types.SET_TIME_RANGE, timeRange); - dispatch('fetchLogs'); + dispatch('fetchLogs', tracking.TIME_RANGE_SET); }; export const showEnvironment = ({ dispatch, commit }, environmentName) => { commit(types.SET_PROJECT_ENVIRONMENT, environmentName); - dispatch('fetchLogs'); + dispatch('fetchLogs', tracking.ENVIRONMENT_SELECTED); }; /** @@ -111,19 +112,22 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => { .get(environmentsPath) .then(({ data }) => { commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data.environments); - dispatch('fetchLogs'); + dispatch('fetchLogs', tracking.ENVIRONMENT_SELECTED); }) .catch(() => { commit(types.RECEIVE_ENVIRONMENTS_DATA_ERROR); }); }; -export const fetchLogs = ({ commit, state }) => { +export const fetchLogs = ({ commit, state }, trackingLabel) => { commit(types.REQUEST_LOGS_DATA); return requestLogsUntilData({ commit, state }) .then(({ data }) => { const { pod_name, pods, logs, cursor } = data; + if (logs && logs.length > 0) { + trackLogs(trackingLabel); + } commit(types.RECEIVE_LOGS_DATA_SUCCESS, { logs, cursor }); commit(types.SET_CURRENT_POD_NAME, pod_name); commit(types.RECEIVE_PODS_DATA_SUCCESS, pods); diff --git a/app/assets/javascripts/registry/explorer/components/image_list.vue b/app/assets/javascripts/registry/explorer/components/image_list.vue index bc209b12738..9d48769cbad 100644 --- a/app/assets/javascripts/registry/explorer/components/image_list.vue +++ b/app/assets/javascripts/registry/explorer/components/image_list.vue @@ -1,24 +1,12 @@ <script> -import { GlPagination, GlTooltipDirective, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; - -import { - ASYNC_DELETE_IMAGE_ERROR_MESSAGE, - LIST_DELETE_BUTTON_DISABLED, - REMOVE_REPOSITORY_LABEL, - ROW_SCHEDULED_FOR_DELETION, -} from '../constants'; +import { GlPagination } from '@gitlab/ui'; +import ImageListRow from './image_list_row.vue'; export default { name: 'ImageList', components: { GlPagination, - ClipboardButton, - GlDeprecatedButton, - GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, + ImageListRow, }, props: { images: { @@ -30,12 +18,6 @@ export default { required: true, }, }, - i18n: { - LIST_DELETE_BUTTON_DISABLED, - REMOVE_REPOSITORY_LABEL, - ROW_SCHEDULED_FOR_DELETION, - ASYNC_DELETE_IMAGE_ERROR_MESSAGE, - }, computed: { currentPage: { get() { @@ -46,79 +28,25 @@ export default { }, }, }, - methods: { - encodeListItem(item) { - const params = JSON.stringify({ name: item.path, tags_path: item.tags_path, id: item.id }); - return window.btoa(params); - }, - }, }; </script> <template> <div class="gl-display-flex gl-flex-direction-column"> - <div + <image-list-row v-for="(listItem, index) in images" :key="index" - v-gl-tooltip="{ - placement: 'left', - disabled: !listItem.deleting, - title: $options.i18n.ROW_SCHEDULED_FOR_DELETION, - }" - data-testid="rowItem" - > - <div - class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-py-2 gl-px-1 border-bottom" - :class="{ 'border-top': index === 0, 'disabled-content': listItem.deleting }" - > - <div class="gl-display-flex gl-align-items-center"> - <router-link - data-testid="detailsLink" - :to="{ name: 'details', params: { id: encodeListItem(listItem) } }" - > - {{ listItem.path }} - </router-link> - <clipboard-button - v-if="listItem.location" - :disabled="listItem.deleting" - :text="listItem.location" - :title="listItem.location" - css-class="btn-default btn-transparent btn-clipboard" - /> - <gl-icon - v-if="listItem.failedDelete" - v-gl-tooltip - :title="$options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE" - name="warning" - class="text-warning align-middle" - /> - </div> - <div - v-gl-tooltip="{ disabled: listItem.destroy_path }" - class="d-none d-sm-block" - :title="$options.i18n.LIST_DELETE_BUTTON_DISABLED" - > - <gl-deprecated-button - v-gl-tooltip - data-testid="deleteImageButton" - :disabled="!listItem.destroy_path || listItem.deleting" - :title="$options.i18n.REMOVE_REPOSITORY_LABEL" - :aria-label="$options.i18n.REMOVE_REPOSITORY_LABEL" - class="btn-inverted" - variant="danger" - @click="$emit('delete', listItem)" - > - <gl-icon name="remove" /> - </gl-deprecated-button> - </div> - </div> - </div> + :item="listItem" + :show-top-border="index === 0" + @delete="$emit('delete', $event)" + /> + <gl-pagination v-model="currentPage" :per-page="pagination.perPage" :total-items="pagination.total" align="center" - class="w-100 gl-mt-2" + class="w-100 gl-mt-3" /> </div> </template> diff --git a/app/assets/javascripts/registry/explorer/components/image_list_row.vue b/app/assets/javascripts/registry/explorer/components/image_list_row.vue new file mode 100644 index 00000000000..1b631ca36de --- /dev/null +++ b/app/assets/javascripts/registry/explorer/components/image_list_row.vue @@ -0,0 +1,136 @@ +<script> +import { GlTooltipDirective, GlButton, GlIcon, GlSprintf } from '@gitlab/ui'; +import { n__ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + +import { + ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + LIST_DELETE_BUTTON_DISABLED, + REMOVE_REPOSITORY_LABEL, + ROW_SCHEDULED_FOR_DELETION, +} from '../constants'; + +export default { + name: 'ImageListrow', + components: { + ClipboardButton, + GlButton, + GlSprintf, + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + item: { + type: Object, + required: true, + }, + showTopBorder: { + type: Boolean, + default: false, + required: false, + }, + }, + i18n: { + LIST_DELETE_BUTTON_DISABLED, + REMOVE_REPOSITORY_LABEL, + ROW_SCHEDULED_FOR_DELETION, + ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + }, + computed: { + encodedItem() { + const params = JSON.stringify({ + name: this.item.path, + tags_path: this.item.tags_path, + id: this.item.id, + }); + return window.btoa(params); + }, + disabledDelete() { + return !this.item.destroy_path || this.item.deleting; + }, + tagsCountText() { + return n__( + 'ContainerRegistry|%{count} Tag', + 'ContainerRegistry|%{count} Tags', + this.item.tags_count, + ); + }, + }, +}; +</script> + +<template> + <div + v-gl-tooltip="{ + placement: 'left', + disabled: !item.deleting, + title: $options.i18n.ROW_SCHEDULED_FOR_DELETION, + }" + > + <div + class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-py-2 gl-px-1 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-py-4 " + :class="{ + 'gl-border-t-solid gl-border-t-1': showTopBorder, + 'disabled-content': item.deleting, + }" + > + <div class="gl-display-flex gl-flex-direction-column"> + <div class="gl-display-flex gl-align-items-center"> + <router-link + class="gl-text-black-normal gl-font-weight-bold" + data-testid="detailsLink" + :to="{ name: 'details', params: { id: encodedItem } }" + > + {{ item.path }} + </router-link> + <clipboard-button + v-if="item.location" + :disabled="item.deleting" + :text="item.location" + :title="item.location" + css-class="btn-default btn-transparent btn-clipboard gl-text-gray-500" + /> + <gl-icon + v-if="item.failedDelete" + v-gl-tooltip + :title="$options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE" + name="warning" + class="text-warning" + /> + </div> + <div class="gl-font-sm gl-text-gray-500"> + <span class="gl-display-flex gl-align-items-center" data-testid="tagsCount"> + <gl-icon name="tag" class="gl-mr-2" /> + <gl-sprintf :message="tagsCountText"> + <template #count> + {{ item.tags_count }} + </template> + </gl-sprintf> + </span> + </div> + </div> + <div + v-gl-tooltip="{ + disabled: item.destroy_path, + title: $options.i18n.LIST_DELETE_BUTTON_DISABLED, + }" + class="d-none d-sm-block" + data-testid="deleteButtonWrapper" + > + <gl-button + v-gl-tooltip + data-testid="deleteImageButton" + :disabled="disabledDelete" + :title="$options.i18n.REMOVE_REPOSITORY_LABEL" + :aria-label="$options.i18n.REMOVE_REPOSITORY_LABEL" + class="btn-inverted" + variant="danger" + icon="remove" + @click="$emit('delete', item)" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/stylesheets/framework/broadcast_messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss index 9903d10d27c..6b5d1794f9a 100644 --- a/app/assets/stylesheets/framework/broadcast_messages.scss +++ b/app/assets/stylesheets/framework/broadcast_messages.scss @@ -42,7 +42,6 @@ } .broadcast-message-dismiss { - height: 100%; color: $gray-800; } } diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index ee42baa8326..fc0acd8f99a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -241,7 +241,8 @@ class Admin::UsersController < Admin::ApplicationController :theme_id, :twitter, :username, - :website_url + :website_url, + :note ] end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 2331674f42c..1bf143c9a91 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -162,8 +162,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic def renderable_notes define_diff_comment_vars unless @notes - @notes + draft_notes = + if current_user + merge_request.draft_notes.authored_by(current_user) + else + [] + end + + @notes.concat(draft_notes) end end - -Projects::MergeRequests::DiffsController.prepend_if_ee('EE::Projects::MergeRequests::DiffsController') diff --git a/app/controllers/projects/merge_requests/drafts_controller.rb b/app/controllers/projects/merge_requests/drafts_controller.rb new file mode 100644 index 00000000000..f4846b1aa81 --- /dev/null +++ b/app/controllers/projects/merge_requests/drafts_controller.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +class Projects::MergeRequests::DraftsController < Projects::MergeRequests::ApplicationController + include Gitlab::Utils::StrongMemoize + + respond_to :json + + before_action :authorize_create_note!, only: [:create, :publish] + before_action :authorize_admin_draft!, only: [:update, :destroy] + before_action :authorize_admin_draft!, if: -> { action_name == 'publish' && params[:id].present? } + + def index + drafts = prepare_notes_for_rendering(draft_notes) + render json: DraftNoteSerializer.new(current_user: current_user).represent(drafts) + end + + def create + create_params = draft_note_params.merge(in_reply_to_discussion_id: params[:in_reply_to_discussion_id]) + create_service = DraftNotes::CreateService.new(merge_request, current_user, create_params) + + draft_note = create_service.execute + + prepare_notes_for_rendering(draft_note) + + render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note) + end + + def update + draft_note.update!(draft_note_params) + + prepare_notes_for_rendering(draft_note) + + render json: DraftNoteSerializer.new(current_user: current_user).represent(draft_note) + end + + def destroy + DraftNotes::DestroyService.new(merge_request, current_user).execute(draft_note) + + head :ok + end + + def publish + result = DraftNotes::PublishService.new(merge_request, current_user).execute(draft_note(allow_nil: true)) + + if result[:status] == :success + head :ok + else + render json: { message: result[:message] }, status: result[:status] + end + end + + def discard + DraftNotes::DestroyService.new(merge_request, current_user).execute + + head :ok + end + + private + + def draft_note(allow_nil: false) + strong_memoize(:draft_note) do + draft_notes.find(params[:id]) + end + rescue ActiveRecord::RecordNotFound => ex + # draft_note is allowed to be nil in #publish + raise ex unless allow_nil + end + + def draft_notes + return unless current_user + + strong_memoize(:draft_notes) do + merge_request.draft_notes.authored_by(current_user) + end + end + + # rubocop: disable CodeReuse/ActiveRecord + def merge_request + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id]) + end + # rubocop: enable CodeReuse/ActiveRecord + + def draft_note_params + params.require(:draft_note).permit( + :commit_id, + :note, + :position, + :resolve_discussion + ).tap do |h| + # Old FE version will still be sending `draft_note[commit_id]` as 'undefined'. + # That can result to having a note linked to a commit with 'undefined' ID + # which is non-existent. + h[:commit_id] = nil if h[:commit_id] == 'undefined' + end + end + + def prepare_notes_for_rendering(notes) + return [] unless notes + + notes = Array.wrap(notes) + + # Preload author and access-level information + DraftNote.preload_author(notes) + user_ids = notes.map(&:author_id) + project.team.max_member_access_for_user_ids(user_ids) + + notes.map(&method(:render_draft_note)) + end + + def render_draft_note(note) + params = { target_id: merge_request.id, target_type: 'MergeRequest', text: note.note } + result = PreviewMarkdownService.new(@project, current_user, params).execute + markdown_params = { markdown_engine: result[:markdown_engine], issuable_state_filter_enabled: true } + + note.rendered_note = view_context.markdown(result[:text], markdown_params) + note.users_referenced = result[:users] + note.commands_changes = view_context.markdown(result[:commands]) + + note + end + + def authorize_admin_draft! + access_denied! unless can?(current_user, :admin_note, draft_note) + end + + def authorize_create_note! + access_denied! unless can?(current_user, :create_note, merge_request) + end +end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index bb9892ad596..3a752521e4b 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -61,8 +61,8 @@ module NotesHelper class: 'add-diff-note js-add-diff-note-button', type: 'submit', name: 'button', data: diff_view_line_data(line_code, position, line_type), - title: 'Add a comment to this line' do - icon('comment-o') + title: _('Add a comment to this line') do + sprite_icon('comment', size: 12) end end diff --git a/app/policies/draft_note_policy.rb b/app/policies/draft_note_policy.rb new file mode 100644 index 00000000000..be99d12c5f8 --- /dev/null +++ b/app/policies/draft_note_policy.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class DraftNotePolicy < BasePolicy + delegate { @subject.merge_request } + + condition(:is_author) { @user && @subject.author == @user } + + rule { is_author }.policy do + enable :read_note + enable :admin_note + enable :resolve_note + end +end diff --git a/app/serializers/draft_note_entity.rb b/app/serializers/draft_note_entity.rb new file mode 100644 index 00000000000..cab4849ebc9 --- /dev/null +++ b/app/serializers/draft_note_entity.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +class DraftNoteEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :author, using: NoteUserEntity + expose :merge_request_id + expose :position, if: -> (note, _) { note.on_diff? } + expose :line_code + expose :file_identifier_hash + expose :file_hash + expose :file_path + expose :note + expose :rendered_note, as: :note_html + expose :references + expose :discussion_id + expose :resolve_discussion + expose :noteable_type + + expose :current_user do + expose :can_edit do |note| + can?(current_user, :admin_note, note) + end + + expose :can_award_emoji do |note| + note.emoji_awardable? + end + + expose :can_resolve do |note| + note.resolvable? && can?(current_user, :resolve_note, note) + end + end + + private + + def current_user + request.current_user + end +end diff --git a/app/serializers/draft_note_serializer.rb b/app/serializers/draft_note_serializer.rb new file mode 100644 index 00000000000..282d7f9bdda --- /dev/null +++ b/app/serializers/draft_note_serializer.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +class DraftNoteSerializer < BaseSerializer + entity DraftNoteEntity +end diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb index 3938d675596..f06f00a5c3f 100644 --- a/app/services/users/build_service.rb +++ b/app/services/users/build_service.rb @@ -82,7 +82,8 @@ module Users :organization, :location, :public_email, - :user_type + :user_type, + :note ] end diff --git a/app/views/admin/users/_admin_notes.html.haml b/app/views/admin/users/_admin_notes.html.haml new file mode 100644 index 00000000000..5d91ba1a1ca --- /dev/null +++ b/app/views/admin/users/_admin_notes.html.haml @@ -0,0 +1,7 @@ +%fieldset + %legend= _('Admin notes') + .form-group.row + .col-sm-2.col-form-label.text-right + = f.label :note, s_('AdminNote|Note') + .col-sm-10 + = f.text_area :note, class: 'form-control' diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 3281718071c..38c6c8b2a62 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -83,7 +83,7 @@ .col-sm-10 = f.text_field :website_url, class: 'form-control' - = render_if_exists 'admin/users/admin_notes', f: f + = render 'admin/users/admin_notes', f: f .form-actions - if @user.new_record? diff --git a/app/views/admin/users/_user_detail.html.haml b/app/views/admin/users/_user_detail.html.haml index a29f369b9de..3839231cb95 100644 --- a/app/views/admin/users/_user_detail.html.haml +++ b/app/views/admin/users/_user_detail.html.haml @@ -6,7 +6,7 @@ = image_tag avatar_icon_for_user(user), class: 'avatar s16 d-xs-flex d-md-none mr-1 gl-mt-2', alt: _('Avatar for %{name}') % { name: sanitize_name(user.name) } = link_to user.name, admin_user_path(user), class: 'text-plain js-user-link', data: { user_id: user.id, qa_selector: 'username_link' } - = render_if_exists 'admin/users/user_listing_note', user: user + = render 'admin/users/user_listing_note', user: user - user_badges_in_admin_section(user).each do |badge| - css_badge = "badge badge-#{badge[:variant]}" if badge[:variant].present? diff --git a/app/views/admin/users/_user_detail_note.html.haml b/app/views/admin/users/_user_detail_note.html.haml new file mode 100644 index 00000000000..4f2a682c5ca --- /dev/null +++ b/app/views/admin/users/_user_detail_note.html.haml @@ -0,0 +1,7 @@ +- if @user.note.present? + - text = @user.note + .card.border-info + .card-header.bg-info.text-white + = _('Admin Note') + .card-body + %p= text diff --git a/app/views/admin/users/_user_listing_note.html.haml b/app/views/admin/users/_user_listing_note.html.haml new file mode 100644 index 00000000000..df4af009c5c --- /dev/null +++ b/app/views/admin/users/_user_listing_note.html.haml @@ -0,0 +1,3 @@ +- if user.note.present? + %span.has-tooltip.user-note{ title: user.note } + = icon("sticky-note-o cgrey") diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index cd07fee8e59..fa707b73d3e 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -154,7 +154,7 @@ %br = link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?', qa_selector: 'confirm_user_button' } - = render_if_exists 'admin/users/user_detail_note' + = render 'admin/users/user_detail_note' - if @user.deactivated? .card.border-info diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 88d1ec54cb0..4442bdcdf1d 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -22,8 +22,8 @@ .header-action-buttons - if defined?(@notes_count) && @notes_count > 0 - %span.btn.disabled.btn-grouped.d-none.d-sm-block.append-right-10 - = icon('comment') + %span.btn.disabled.btn-grouped.d-none.d-sm-block.append-right-10.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count } + = sprite_icon('comment') = @notes_count = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 d-none d-sm-none d-md-inline" do #{ _('Browse files') } diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 855b719dc45..7395c16c38b 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -14,7 +14,7 @@ .file-actions.d-none.d-sm-block - if blob&.readable_text? = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: _("Toggle comments for this file"), disabled: @diff_notes_disabled do - = icon('comment') + = sprite_icon('comment', size: 16) \ - if editable_diff?(diff_file) - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {} diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 7496ca97d56..55f1b9098c3 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,4 +1,4 @@ -- page_title _('CI / CD Charts') +- page_title _('CI / CD Analytics') #js-project-pipelines-charts-app{ data: { counts: @counts, success_ratio: success_ratio(@counts), times_chart: { labels: @charts[:pipeline_times].labels, values: @charts[:pipeline_times].pipeline_times }, diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb index ee1d2237001..b31311b0e44 100644 --- a/app/workers/new_note_worker.rb +++ b/app/workers/new_note_worker.rb @@ -19,14 +19,11 @@ class NewNoteWorker # rubocop:disable Scalability/IdempotentWorker Gitlab::AppLogger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job") end end + # rubocop: enable CodeReuse/ActiveRecord private - # EE-only method def skip_notification?(note) - false + note.review.present? end - # rubocop: enable CodeReuse/ActiveRecord end - -NewNoteWorker.prepend_if_ee('EE::NewNoteWorker') |