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-05-22 15:08:15 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-22 15:08:15 +0300
commit808c799a67a1cf2489a343a6976f55c74aec398b (patch)
tree4902ff7dbcdd7f3a9dde5ab3bd20dba94835a8a7 /app
parent4a3ba3e5f261eb09e6b2b4fd44373e7a1c454a72 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_key_field.vue2
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue2
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss2
-rw-r--r--app/assets/stylesheets/components/dashboard_skeleton.scss6
-rw-r--r--app/assets/stylesheets/framework/animations.scss8
-rw-r--r--app/assets/stylesheets/framework/common.scss3
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss2
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/tables.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss10
-rw-r--r--app/assets/stylesheets/pages/labels.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss6
-rw-r--r--app/assets/stylesheets/pages/projects.scss2
-rw-r--r--app/graphql/resolvers/projects/jira_projects_resolver.rb69
-rw-r--r--app/graphql/types/projects/services/jira_project_type.rb21
-rw-r--r--app/graphql/types/projects/services/jira_service_type.rb11
-rw-r--r--app/helpers/active_sessions_helper.rb2
-rw-r--r--app/models/project_services/jira_service.rb20
-rw-r--r--app/serializers/diff_file_base_entity.rb6
-rw-r--r--app/serializers/diff_file_metadata_entity.rb5
-rw-r--r--app/services/ci/authorize_job_artifact_service.rb53
-rw-r--r--app/services/event_create_service.rb26
-rw-r--r--app/services/resource_events/change_state_service.rb36
-rw-r--r--app/services/resource_events/merge_into_notes_service.rb5
-rw-r--r--app/services/resource_events/synthetic_state_notes_builder_service.rb20
-rw-r--r--app/services/system_notes/issuables_service.rb12
-rw-r--r--app/views/admin/users/_user_detail.html.haml2
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml2
-rw-r--r--app/views/shared/milestones/_form_dates.html.haml12
-rw-r--r--app/workers/concerns/gitlab/jira_import/import_worker.rb1
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