diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-22 15:08:15 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-22 15:08:15 +0300 |
commit | 808c799a67a1cf2489a343a6976f55c74aec398b (patch) | |
tree | 4902ff7dbcdd7f3a9dde5ab3bd20dba94835a8a7 /app | |
parent | 4a3ba3e5f261eb09e6b2b4fd44373e7a1c454a72 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
34 files changed, 313 insertions, 55 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue b/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue index f5c2cc57f3f..c15d638d92b 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue @@ -154,7 +154,7 @@ export default { v-for="(result, i) in results" :key="i" role="option" - :class="{ 'gl-bg-gray-100': i === arrowCounter }" + :class="{ 'gl-bg-gray-50': i === arrowCounter }" :aria-selected="i === arrowCounter" > <gl-button tabindex="-1" class="btn-transparent pl-2" @click="selectToken(result)">{{ diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 1b8c75202fb..f9132c3307e 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -150,7 +150,7 @@ export default { class="metadata align-items-md-center d-flex flex-grow-1 flex-shrink-0 flex-wrap justify-content-md-between" > <item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" /> - <item-stats :item="group" class="group-stats prepend-top-2 d-none d-md-flex" /> + <item-stats :item="group" class="group-stats gl-mt-2 d-none d-md-flex" /> </div> </div> </div> diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index 1c15400542a..f819c2dbce1 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -111,7 +111,7 @@ kbd { code { padding: 2px 4px; color: $code-color; - background-color: $gray-100; + background-color: $gray-50; border-radius: $border-radius-default; .code > & { diff --git a/app/assets/stylesheets/components/dashboard_skeleton.scss b/app/assets/stylesheets/components/dashboard_skeleton.scss index ce33aa94df3..64091201221 100644 --- a/app/assets/stylesheets/components/dashboard_skeleton.scss +++ b/app/assets/stylesheets/components/dashboard_skeleton.scss @@ -67,10 +67,10 @@ background-repeat: no-repeat; background-size: cover; background-image: linear-gradient(to right, - $gray-100 0%, + $gray-50 0%, $gray-10 20%, - $gray-100 40%, - $gray-100 100%); + $gray-50 40%, + $gray-50 100%); border-radius: $gl-padding; height: $gl-padding; margin-top: -$gl-padding-8; diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 13174687e5d..566ab5bc5c4 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -177,7 +177,7 @@ a { [class^='skeleton-line-'] { position: relative; - background-color: $gray-100; + background-color: $gray-50; height: 10px; overflow: hidden; @@ -192,10 +192,10 @@ a { background-repeat: no-repeat; background-size: cover; background-image: linear-gradient(to right, - $gray-100 0%, + $gray-50 0%, $gray-10 20%, - $gray-100 40%, - $gray-100 100%); + $gray-50 40%, + $gray-50 100%); height: 10px; } } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 90a6f471374..158d2133f13 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -396,7 +396,6 @@ img.emoji { 🚨 Do not use these classes — they are deprecated and being removed. 🚨 See https://gitlab.com/gitlab-org/gitlab/-/issues/217418 for more details. **/ -.prepend-top-2 { margin-top: 2px; } .prepend-top-4 { margin-top: $gl-padding-4; } .prepend-top-5 { margin-top: 5px; } .prepend-top-8 { margin-top: $grid-size; } @@ -576,7 +575,7 @@ img.emoji { bottom: 40px; right: 40px; font-size: $gl-font-size-small; - background: $gray-100; + background: $gray-50; width: 200px; border-radius: 24px; box-shadow: 0 2px 4px $issue-boards-card-shadow; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 1df9818a877..8c75bff2741 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -940,7 +940,7 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu { position: absolute; top: 13px; right: 25px; - color: $gray-100; + color: $gray-50; } } @@ -979,7 +979,7 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu { &:hover { .frequent-items-item-avatar-container .avatar { - border-color: $gray-100; + border-color: $gray-50; } } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 7ee3e68ceea..eef6d9031f8 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -480,7 +480,7 @@ span.idiff { padding-bottom: $gl-padding; .discussion-reply-holder { - border-bottom: 1px solid $gray-100; + border-bottom: 1px solid $gray-50; border-radius: 0; } } diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 6a2f36d2509..4b50bd2d88b 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -314,12 +314,12 @@ body { $gray-800, $gray-700, $gray-700, - $gray-100, + $gray-50, $gray-700 ); .navbar-gitlab { - background-color: $gray-100; + background-color: $gray-50; box-shadow: 0 1px 0 0 $border-color; .logo-text svg { diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 97698fefbee..2a97009e605 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -123,7 +123,7 @@ .markdown-area { border-radius: 0; background: $white; - border: 1px solid $gray-100; + border: 1px solid $gray-50; min-height: 140px; max-height: 500px; padding: 5px; diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 5739f048e86..5bc2874ea05 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -47,7 +47,7 @@ table { } th { - @include gl-bg-gray-100; + @include gl-bg-gray-50; border-bottom: 0; &.wide { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index ac4d431ea57..1253ff698e0 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -165,7 +165,7 @@ $red-950: #4b140b; $gray-10: #fafafa; $gray-50: #f0f0f0; -$gray-100: #f2f2f2; +$gray-100: #dbdbdb; $gray-200: #dfdfdf; $gray-300: #ccc; $gray-400: #bababa; diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 295af0311ae..ddfdf8d0553 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -387,7 +387,7 @@ $ide-commit-header-height: 48px; &:hover, &:focus { - background: var(--ide-background, $gray-100); + background: var(--ide-background, $gray-50); outline: 0; } @@ -559,7 +559,7 @@ $ide-commit-header-height: 48px; &:hover { color: var(--ide-text-color, $gl-text-color); - background-color: var(--ide-background-hover, $gray-100); + background-color: var(--ide-background-hover, $gray-50); } &:focus { @@ -1041,7 +1041,7 @@ $ide-commit-header-height: 48px; .ide-entry-dropdown-toggle { padding: $gl-padding-4; color: var(--ide-text-color, $gl-text-color); - background-color: var(--ide-background, $gray-100); + background-color: var(--ide-background, $gray-50); &:hover { background-color: var(--ide-file-row-btn-hover-background, $gray-200); @@ -1142,12 +1142,12 @@ $ide-commit-header-height: 48px; } .file-row.is-active { - background: var(--ide-background, $gray-100); + background: var(--ide-background, $gray-50); } .file-row:hover, .file-row:focus { - background: var(--ide-background, $gray-100); + background: var(--ide-background, $gray-50); .ide-new-btn { display: block; diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 22c1cb127cd..c3bac053a0a 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -86,7 +86,7 @@ justify-content: space-between; padding: $gl-padding; border-radius: $border-radius-default; - border: 1px solid $gray-100; + border: 1px solid $gray-50; &:last-child { margin-bottom: 0; @@ -276,7 +276,7 @@ } .label-badge-gray { - background-color: $gray-100; + background-color: $gray-50; } .label-links { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 57afe45a74b..c3f3dbc223b 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -253,11 +253,11 @@ table { background-color: $gray-light; border-radius: 0 0 3px 3px; padding: $gl-padding; - border-top: 1px solid $gray-100; + border-top: 1px solid $gray-50; + .new-note { background-color: $gray-light; - border-top: 1px solid $gray-100; + border-top: 1px solid $gray-50; } &.is-replying { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index bed147aa3a7..e8cdfd717c0 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -5,7 +5,7 @@ $note-form-margin-left: 72px; @mixin vertical-line($left) { &::before { content: ''; - border-left: 2px solid $gray-100; + border-left: 2px solid $gray-50; position: absolute; top: 0; bottom: 0; @@ -83,8 +83,8 @@ $note-form-margin-left: 72px; .replies-toggle { background-color: $gray-light; padding: $gl-padding-8 $gl-padding; - border-top: 1px solid $gray-100; - border-bottom: 1px solid $gray-100; + border-top: 1px solid $gray-50; + border-bottom: 1px solid $gray-50; .collapse-replies-btn:hover { color: $blue-600; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index c0a1cf10fe4..438f6c2546e 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -396,7 +396,7 @@ margin-right: $gl-padding-4; margin-bottom: $gl-padding-4; color: $gl-text-color-secondary; - background-color: $gray-100; + background-color: $gray-50; line-height: $gl-btn-line-height; &:hover { diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb new file mode 100644 index 00000000000..7004976adab --- /dev/null +++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Resolvers + module Projects + class JiraProjectsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + argument :name, + GraphQL::STRING_TYPE, + required: false, + description: 'Project name or key' + + def resolve(name: nil, **args) + authorize!(project) + + response, start_cursor, end_cursor = jira_projects(name: name, **compute_pagination_params(args)) + end_cursor = nil if !!response.payload[:is_last] + + response.success? ? Gitlab::Graphql::ExternallyPaginatedArray.new(start_cursor, end_cursor, *response.payload[:projects]) : nil + end + + def authorized_resource?(project) + Feature.enabled?(:jira_issue_import, project) && Ability.allowed?(context[:current_user], :admin_project, project) + end + + private + + alias_method :jira_service, :object + + def project + jira_service&.project + end + + def compute_pagination_params(params) + after_cursor = Base64.decode64(params[:after].to_s) + before_cursor = Base64.decode64(params[:before].to_s) + + # differentiate between 0 cursor and nil or invalid cursor that decodes into zero. + after_index = after_cursor.to_i == 0 && after_cursor != "0" ? nil : after_cursor.to_i + before_index = before_cursor.to_i == 0 && before_cursor != "0" ? nil : before_cursor.to_i + + if after_index.present? && before_index.present? + if after_index >= before_index + { start_at: 0, limit: 0 } + else + { start_at: after_index + 1, limit: before_index - after_index - 1 } + end + elsif after_index.present? + { start_at: after_index + 1, limit: nil } + elsif before_index.present? + { start_at: 0, limit: before_index - 1 } + else + { start_at: 0, limit: nil } + end + end + + def jira_projects(name:, start_at:, limit:) + args = { query: name, start_at: start_at, limit: limit }.compact + + response = jira_service&.jira_projects(args) + projects = response.payload[:projects] + start_cursor = start_at == 0 ? nil : Base64.encode64((start_at - 1).to_s) + end_cursor = Base64.encode64((start_at + projects.size - 1).to_s) + + [response, start_cursor, end_cursor] + end + end + end +end diff --git a/app/graphql/types/projects/services/jira_project_type.rb b/app/graphql/types/projects/services/jira_project_type.rb new file mode 100644 index 00000000000..ccf9107f398 --- /dev/null +++ b/app/graphql/types/projects/services/jira_project_type.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Types + module Projects + module Services + # rubocop:disable Graphql/AuthorizeTypes + class JiraProjectType < BaseObject + graphql_name 'JiraProject' + + field :key, GraphQL::STRING_TYPE, null: false, + description: 'Key of the Jira project' + field :project_id, GraphQL::INT_TYPE, null: false, + description: 'ID of the Jira project', + method: :id + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the Jira project' + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/projects/services/jira_service_type.rb b/app/graphql/types/projects/services/jira_service_type.rb index 4fd9e61f5a4..e81963f752d 100644 --- a/app/graphql/types/projects/services/jira_service_type.rb +++ b/app/graphql/types/projects/services/jira_service_type.rb @@ -9,9 +9,14 @@ module Types implements(Types::Projects::ServiceType) authorize :admin_project - # This is a placeholder for now for the actuall implementation of the JiraServiceType - # Here we will want to expose a field with jira_projects fetched through Jira Rest API - # MR implementing it https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28190 + + field :projects, + Types::Projects::Services::JiraProjectType.connection_type, + null: true, + connection: false, + extensions: [Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension], + description: 'List of Jira projects fetched through Jira REST API', + resolver: Resolvers::Projects::JiraProjectsResolver end end end diff --git a/app/helpers/active_sessions_helper.rb b/app/helpers/active_sessions_helper.rb index 84aa1160f12..8fb23f99cb3 100644 --- a/app/helpers/active_sessions_helper.rb +++ b/app/helpers/active_sessions_helper.rb @@ -20,6 +20,6 @@ module ActiveSessionsHelper 'monitor-o' end - sprite_icon(icon_name, size: 16, css_class: 'prepend-top-2') + sprite_icon(icon_name, size: 16, css_class: 'gl-mt-2') end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 53da874ede8..c71c939f452 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -6,6 +6,8 @@ class JiraService < IssueTrackerService include ApplicationHelper include ActionView::Helpers::AssetUrlHelper + PROJECTS_PER_PAGE = 50 + validates :url, public_url: true, presence: true, if: :activated? validates :api_url, public_url: true, allow_blank: true validates :username, presence: true, if: :activated? @@ -224,8 +226,26 @@ class JiraService < IssueTrackerService true end + def jira_projects(query: '', limit: PROJECTS_PER_PAGE, start_at: 0) + return ServiceResponse.success(payload: { projects: [], is_last: true }) if limit.to_i <= 0 + + response = jira_request { client.get(projects_url(query: query, limit: limit.to_i, start_at: start_at.to_i)) } + + return ServiceResponse.error(message: @error.message) if @error.present? + return ServiceResponse.success(payload: { projects: [] }) unless response['values'].present? + + projects = response['values'].map { |v| JIRA::Resource::Project.build(client, v) } + + ServiceResponse.success(payload: { projects: projects, is_last: response['isLast'] }) + end + private + def projects_url(query:, limit:, start_at:) + '/rest/api/2/project/search?query=%{query}&maxResults=%{limit}&startAt=%{start_at}' % + { query: CGI.escape(query.to_s), limit: limit, start_at: start_at } + end + def test_settings return unless client_url.present? diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb index 8c2b3a65d57..33eb33d314b 100644 --- a/app/serializers/diff_file_base_entity.rb +++ b/app/serializers/diff_file_base_entity.rb @@ -67,10 +67,8 @@ class DiffFileBaseEntity < Grape::Entity end end - expose :file_hash do |diff_file| - Digest::SHA1.hexdigest(diff_file.file_path) - end - + expose :file_identifier_hash + expose :file_hash expose :file_path expose :old_path expose :new_path diff --git a/app/serializers/diff_file_metadata_entity.rb b/app/serializers/diff_file_metadata_entity.rb index 05280518f39..460f4967e99 100644 --- a/app/serializers/diff_file_metadata_entity.rb +++ b/app/serializers/diff_file_metadata_entity.rb @@ -7,7 +7,6 @@ class DiffFileMetadataEntity < Grape::Entity expose :old_path expose :new_file?, as: :new_file expose :deleted_file?, as: :deleted_file - expose :file_hash do |diff_file| - Digest::SHA1.hexdigest(diff_file.file_path) - end + expose :file_identifier_hash + expose :file_hash end diff --git a/app/services/ci/authorize_job_artifact_service.rb b/app/services/ci/authorize_job_artifact_service.rb new file mode 100644 index 00000000000..cfcbdcae450 --- /dev/null +++ b/app/services/ci/authorize_job_artifact_service.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Ci + class AuthorizeJobArtifactService + include Gitlab::Utils::StrongMemoize + + # Max size of the zipped LSIF artifact + LSIF_ARTIFACT_MAX_SIZE = 20.megabytes + LSIF_ARTIFACT_TYPE = 'lsif' + + def initialize(job, params, max_size:) + @job = job + @max_size = max_size + @size = params[:filesize] + @type = params[:artifact_type].to_s + end + + def forbidden? + lsif? && !code_navigation_enabled? + end + + def too_large? + size && max_size <= size.to_i + end + + def headers + default_headers = JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size) + default_headers.tap do |h| + h[:ProcessLsif] = true if lsif? && code_navigation_enabled? + end + end + + private + + attr_reader :job, :size, :type + + def code_navigation_enabled? + strong_memoize(:code_navigation_enabled) do + Feature.enabled?(:code_navigation) + end + end + + def lsif? + strong_memoize(:lsif) do + type == LSIF_ARTIFACT_TYPE + end + end + + def max_size + lsif? ? LSIF_ARTIFACT_MAX_SIZE : @max_size.to_i + end + end +end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 522f36cda46..c879c7432a9 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -11,30 +11,44 @@ class EventCreateService IllegalActionError = Class.new(StandardError) def open_issue(issue, current_user) + create_resource_event(issue, current_user, :opened) + create_record_event(issue, current_user, Event::CREATED) end def close_issue(issue, current_user) + create_resource_event(issue, current_user, :closed) + create_record_event(issue, current_user, Event::CLOSED) end def reopen_issue(issue, current_user) + create_resource_event(issue, current_user, :reopened) + create_record_event(issue, current_user, Event::REOPENED) end def open_mr(merge_request, current_user) + create_resource_event(merge_request, current_user, :opened) + create_record_event(merge_request, current_user, Event::CREATED) end def close_mr(merge_request, current_user) + create_resource_event(merge_request, current_user, :closed) + create_record_event(merge_request, current_user, Event::CLOSED) end def reopen_mr(merge_request, current_user) + create_resource_event(merge_request, current_user, :reopened) + create_record_event(merge_request, current_user, Event::REOPENED) end def merge_mr(merge_request, current_user) + create_resource_event(merge_request, current_user, :merged) + create_record_event(merge_request, current_user, Event::MERGED) end @@ -157,6 +171,18 @@ class EventCreateService Event.create!(attributes) end + + def create_resource_event(issuable, current_user, status) + return unless state_change_tracking_enabled?(issuable) + + ResourceEvents::ChangeStateService.new(resource: issuable, user: current_user) + .execute(status) + end + + def state_change_tracking_enabled?(issuable) + issuable&.respond_to?(:resource_state_events) && + ::Feature.enabled?(:track_resource_state_change_events, issuable&.project) + end end EventCreateService.prepend_if_ee('EE::EventCreateService') diff --git a/app/services/resource_events/change_state_service.rb b/app/services/resource_events/change_state_service.rb new file mode 100644 index 00000000000..8beb76d8aee --- /dev/null +++ b/app/services/resource_events/change_state_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ResourceEvents + class ChangeStateService + attr_reader :resource, :user + + def initialize(user:, resource:) + @user, @resource = user, resource + end + + def execute(state) + ResourceStateEvent.create( + user: user, + issue: issue, + merge_request: merge_request, + state: ResourceStateEvent.states[state], + created_at: Time.zone.now) + + resource.expire_note_etag_cache + end + + private + + def issue + return unless resource.is_a?(Issue) + + resource + end + + def merge_request + return unless resource.is_a?(MergeRequest) + + resource + end + end +end diff --git a/app/services/resource_events/merge_into_notes_service.rb b/app/services/resource_events/merge_into_notes_service.rb index 4aa9bb80229..122bcb8550f 100644 --- a/app/services/resource_events/merge_into_notes_service.rb +++ b/app/services/resource_events/merge_into_notes_service.rb @@ -11,7 +11,8 @@ module ResourceEvents SYNTHETIC_NOTE_BUILDER_SERVICES = [ SyntheticLabelNotesBuilderService, - SyntheticMilestoneNotesBuilderService + SyntheticMilestoneNotesBuilderService, + SyntheticStateNotesBuilderService ].freeze attr_reader :resource, :current_user, :params @@ -23,7 +24,7 @@ module ResourceEvents end def execute(notes = []) - (notes + synthetic_notes).sort_by { |n| n.created_at } + (notes + synthetic_notes).sort_by(&:created_at) end private diff --git a/app/services/resource_events/synthetic_state_notes_builder_service.rb b/app/services/resource_events/synthetic_state_notes_builder_service.rb new file mode 100644 index 00000000000..763134d98d8 --- /dev/null +++ b/app/services/resource_events/synthetic_state_notes_builder_service.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ResourceEvents + class SyntheticStateNotesBuilderService < BaseSyntheticNotesBuilderService + private + + def synthetic_notes + state_change_events.map do |event| + StateNote.from_event(event, resource: resource, resource_parent: resource_parent) + end + end + + def state_change_events + return [] unless resource.respond_to?(:resource_state_events) + + events = resource.resource_state_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord + since_fetch_at(events) + end + end +end diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb index 275c64bea89..fcb7fc908f1 100644 --- a/app/services/system_notes/issuables_service.rb +++ b/app/services/system_notes/issuables_service.rb @@ -225,7 +225,12 @@ module SystemNotes action = status == 'reopened' ? 'opened' : status - create_note(NoteSummary.new(noteable, project, author, body, action: action)) + # A state event which results in a synthetic note will be + # created by EventCreateService if change event tracking + # is enabled. + unless state_change_tracking_enabled? + create_note(NoteSummary.new(noteable, project, author, body, action: action)) + end end # Check if a cross reference to a noteable from a mentioner already exists @@ -318,6 +323,11 @@ module SystemNotes def self.cross_reference?(note_text) note_text =~ /\A#{cross_reference_note_prefix}/i end + + def state_change_tracking_enabled? + noteable.respond_to?(:resource_state_events) && + ::Feature.enabled?(:track_resource_state_change_events, noteable.project) + end end end diff --git a/app/views/admin/users/_user_detail.html.haml b/app/views/admin/users/_user_detail.html.haml index 3cc3fc6fa92..a29f369b9de 100644 --- a/app/views/admin/users/_user_detail.html.haml +++ b/app/views/admin/users/_user_detail.html.haml @@ -3,7 +3,7 @@ = image_tag avatar_icon_for_user(user), class: 'avatar s32 d-none d-md-flex', alt: _('Avatar for %{name}') % { name: sanitize_name(user.name) } .row-main-content .row-title.str-truncated-100 - = image_tag avatar_icon_for_user(user), class: 'avatar s16 d-xs-flex d-md-none mr-1 prepend-top-2', alt: _('Avatar for %{name}') % { name: sanitize_name(user.name) } + = 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 diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index 7d9924719a2..7618b53e3ae 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -6,7 +6,7 @@ = current_user.name = current_user.to_reference - if current_user.status - .user-status.d-flex.align-items-center.prepend-top-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } + .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 %span.user-status-message.str-truncated diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml index 4de89d7c7a0..6dbc460d9bf 100644 --- a/app/views/shared/milestones/_form_dates.html.haml +++ b/app/views/shared/milestones/_form_dates.html.haml @@ -1,13 +1,13 @@ .col-md-6 .form-group.row .col-form-label.col-sm-2 - = f.label :start_date, "Start Date" + = f.label :start_date, _('Start Date') .col-sm-10 - = f.text_field :start_date, class: "datepicker form-control", placeholder: "Select start date", autocomplete: 'off' - %a.inline.float-right.prepend-top-5.js-clear-start-date{ href: "#" } Clear start date + = f.text_field :start_date, class: "datepicker form-control", placeholder: _('Select start date'), autocomplete: 'off' + %a.inline.float-right.prepend-top-5.js-clear-start-date{ href: "#" }= _('Clear start date') .form-group.row .col-form-label.col-sm-2 - = f.label :due_date, "Due Date" + = f.label :due_date, _('Due Date') .col-sm-10 - = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date", autocomplete: 'off' - %a.inline.float-right.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date + = f.text_field :due_date, class: "datepicker form-control", placeholder: _('Select due date'), autocomplete: 'off' + %a.inline.float-right.prepend-top-5.js-clear-due-date{ href: "#" }= _('Clear due date') diff --git a/app/workers/concerns/gitlab/jira_import/import_worker.rb b/app/workers/concerns/gitlab/jira_import/import_worker.rb index 537300e6eba..7606cf76c0f 100644 --- a/app/workers/concerns/gitlab/jira_import/import_worker.rb +++ b/app/workers/concerns/gitlab/jira_import/import_worker.rb @@ -7,6 +7,7 @@ module Gitlab included do include ApplicationWorker + include ProjectImportOptions include Gitlab::JiraImport::QueueOptions end |