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-07-01 18:08:45 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-01 18:08:45 +0300
commitae1efa2e1d32dee59d8f509ba17b623b5ffe4ba6 (patch)
treea4cba8561c1671934751508ead7b2b1053e783ec /app
parenta4c655515155710b3695699ea88d824f65af6446 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue5
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue4
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue59
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue40
-rw-r--r--app/assets/javascripts/jobs/components/log/line_number.vue57
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js16
-rw-r--r--app/assets/javascripts/jobs/store/state.js4
-rw-r--r--app/assets/javascripts/jobs/store/utils.js2
-rw-r--r--app/assets/stylesheets/pages/alert_management/list.scss27
-rw-r--r--app/controllers/projects/imports_controller.rb7
-rw-r--r--app/controllers/projects/jobs_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/finders/projects/integrations/jira/issues_finder.rb50
-rw-r--r--app/graphql/resolvers/projects/jira_projects_resolver.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb14
-rw-r--r--app/models/ci/build_trace.rb26
-rw-r--r--app/models/commit_collection.rb11
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/snippet.rb8
-rw-r--r--app/serializers/build_trace_entity.rb3
-rw-r--r--app/services/jira/jql_builder_service.rb30
-rw-r--r--app/services/jira/requests/base.rb16
-rw-r--r--app/services/jira/requests/issues/list_service.rb56
-rw-r--r--app/services/jira/requests/projects.rb37
-rw-r--r--app/services/jira/requests/projects/list_service.rb47
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml6
-rw-r--r--app/workers/concerns/project_export_options.rb25
-rw-r--r--app/workers/group_export_worker.rb1
-rw-r--r--app/workers/project_export_worker.rb3
-rw-r--r--app/workers/repository_import_worker.rb3
32 files changed, 310 insertions, 271 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue
index b7cd28de807..59c74d8cd27 100644
--- a/app/assets/javascripts/alert_management/components/alert_management_list.vue
+++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue
@@ -74,9 +74,8 @@ export default {
{
key: 'title',
label: s__('AlertManagement|Alert'),
- thClass: `${thClass} gl-pointer-events-none`,
+ thClass: `gl-pointer-events-none`,
tdClass,
- sortable: false,
},
{
key: 'eventCount',
@@ -88,7 +87,7 @@ export default {
{
key: 'assignees',
label: s__('AlertManagement|Assignees'),
- thClass: 'gl-w-eighth',
+ thClass: 'gl-w-eighth gl-pointer-events-none',
tdClass,
},
{
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index acc31dfabaf..f43a058b5f8 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -17,7 +17,7 @@ import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue';
import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin';
-import { isNewJobLogActive } from '../store/utils';
+import Log from './log/log.vue';
export default {
name: 'JobPageApp',
@@ -28,7 +28,7 @@ export default {
EnvironmentsBlock,
ErasedBlock,
Icon,
- Log: () => (isNewJobLogActive() ? import('./log/log.vue') : import('./job_log.vue')),
+ Log,
LogTopBar,
StuckBlock,
UnmetPrerequisitesBlock,
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
deleted file mode 100644
index 20888c0af42..00000000000
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<script>
-import { mapState, mapActions } from 'vuex';
-
-export default {
- name: 'JobLog',
- props: {
- trace: {
- type: String,
- required: true,
- },
- isComplete: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- ...mapState(['isScrolledToBottomBeforeReceivingTrace']),
- },
- updated() {
- this.$nextTick(() => {
- this.handleScrollDown();
- });
- },
- mounted() {
- this.$nextTick(() => {
- this.handleScrollDown();
- });
- },
- methods: {
- ...mapActions(['scrollBottom']),
- /**
- * The job log is sent in HTML, which means we need to use `v-html` to render it
- * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
- * in this case because it runs before `v-html` has finished running, since there's no
- * Vue binding.
- * In order to scroll the page down after `v-html` has finished, we need to use setTimeout
- */
- handleScrollDown() {
- if (this.isScrolledToBottomBeforeReceivingTrace) {
- setTimeout(() => {
- this.scrollBottom();
- }, 0);
- }
- },
- },
-};
-</script>
-<template>
- <pre class="js-build-trace build-trace qa-build-trace">
- <code class="bash" v-html="trace">
- </code>
-
- <div v-if="!isComplete" class="js-log-animation build-loader-animation">
- <div class="dot"></div>
- <div class="dot"></div>
- <div class="dot"></div>
- </div>
- </pre>
-</template>
diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
index 33ee84bd4ee..48f669ae8ed 100644
--- a/app/assets/javascripts/jobs/components/log/line.vue
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -2,9 +2,7 @@
import LineNumber from './line_number.vue';
export default {
- components: {
- LineNumber,
- },
+ functional: true,
props: {
line: {
type: Object,
@@ -15,18 +13,28 @@ export default {
required: true,
},
},
+ render(h, { props }) {
+ const { line, path } = props;
+
+ const chars = line.content.map(content => {
+ return h(
+ 'span',
+ {
+ class: ['ws-pre-wrap', content.style],
+ },
+ content.text,
+ );
+ });
+
+ return h('div', { class: 'js-line log-line' }, [
+ h(LineNumber, {
+ props: {
+ lineNumber: line.lineNumber,
+ path,
+ },
+ }),
+ ...chars,
+ ]);
+ },
};
</script>
-
-<template>
- <div class="js-line log-line">
- <line-number :line-number="line.lineNumber" :path="path" />
- <span
- v-for="(content, i) in line.content"
- :key="i"
- :class="content.style"
- class="ws-pre-wrap"
- >{{ content.text }}</span
- >
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue
index ae96c32874b..7ca9154d2fe 100644
--- a/app/assets/javascripts/jobs/components/log/line_number.vue
+++ b/app/assets/javascripts/jobs/components/log/line_number.vue
@@ -1,10 +1,6 @@
<script>
-import { GlLink } from '@gitlab/ui';
-
export default {
- components: {
- GlLink,
- },
+ functional: true,
props: {
lineNumber: {
type: Number,
@@ -15,41 +11,24 @@ export default {
required: true,
},
},
- computed: {
- /**
- * Builds the url for each line number
- *
- * @returns {String}
- */
- buildLineNumber() {
- return `${this.path}#${this.lineNumberId}`;
- },
- /**
- * Array indexes start with 0, so we add 1
- * to create the line number
- *
- * @returns {Number} the line number
- */
- parsedLineNumber() {
- return this.lineNumber + 1;
- },
+ render(h, { props }) {
+ const { lineNumber, path } = props;
- /**
- * Creates the anchor for each link
- *
- * @returns {String}
- */
- lineNumberId() {
- return `L${this.parsedLineNumber}`;
- },
+ const parsedLineNumber = lineNumber + 1;
+ const lineId = `L${parsedLineNumber}`;
+ const lineHref = `${path}#${lineId}`;
+
+ return h(
+ 'a',
+ {
+ class: 'gl-link d-inline-block text-right line-number flex-shrink-0',
+ attrs: {
+ id: lineId,
+ href: lineHref,
+ },
+ },
+ parsedLineNumber,
+ );
},
};
</script>
-<template>
- <gl-link
- :id="lineNumberId"
- class="d-inline-block text-right line-number flex-shrink-0"
- :href="buildLineNumber"
- >{{ parsedLineNumber }}</gl-link
- >
-</template>
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index 6193d8d34ab..924b811d0d6 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import * as types from './mutation_types';
-import { logLinesParser, updateIncrementalTrace, isNewJobLogActive } from './utils';
+import { logLinesParser, updateIncrementalTrace } from './utils';
export default {
[types.SET_JOB_ENDPOINT](state, endpoint) {
@@ -25,22 +25,16 @@ export default {
}
if (log.append) {
- if (isNewJobLogActive()) {
- state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
- } else {
- state.trace += log.html;
- }
+ state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
+
state.traceSize += log.size;
} else {
// When the job still does not have a trace
// the trace response will not have a defined
// html or size. We keep the old value otherwise these
// will be set to `null`
- if (isNewJobLogActive()) {
- state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
- } else {
- state.trace = log.html || state.trace;
- }
+ state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
+
state.traceSize = log.size || state.traceSize;
}
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index d76828ad19b..2fe945b2985 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -1,5 +1,3 @@
-import { isNewJobLogActive } from './utils';
-
export default () => ({
jobEndpoint: null,
traceEndpoint: null,
@@ -18,7 +16,7 @@ export default () => ({
// Used to check if we should keep the automatic scroll
isScrolledToBottomBeforeReceivingTrace: true,
- trace: isNewJobLogActive() ? [] : '',
+ trace: [],
isTraceComplete: false,
traceSize: 0,
isTraceSizeVisible: false,
diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js
index 0b28c52a78f..3b6b8a2c851 100644
--- a/app/assets/javascripts/jobs/store/utils.js
+++ b/app/assets/javascripts/jobs/store/utils.js
@@ -177,5 +177,3 @@ export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
return logLinesParser(newLog, parsedLog);
};
-
-export const isNewJobLogActive = () => gon && gon.features && gon.features.jobLogJson;
diff --git a/app/assets/stylesheets/pages/alert_management/list.scss b/app/assets/stylesheets/pages/alert_management/list.scss
index ea5e7a1bdea..5d3fd0d7dcf 100644
--- a/app/assets/stylesheets/pages/alert_management/list.scss
+++ b/app/assets/stylesheets/pages/alert_management/list.scss
@@ -8,14 +8,9 @@
outline: none;
}
- > :not([aria-sort='none']).b-table-sort-icon-left:hover::before {
- content: '' !important;
- }
-
td,
th {
- // TODO: There is no gl-pl-9 utlity for this padding, to be done and then removed.
- padding-left: 1.25rem;
+ @include gl-pl-9;
@include gl-py-5;
@include gl-outline-none;
@include gl-relative;
@@ -26,24 +21,8 @@
font-weight: $gl-font-weight-bold;
color: $gl-gray-600;
- &:hover::before {
- left: 3%;
- top: 34%;
- @include gl-absolute;
- content: url("data:image/svg+xml,%3Csvg \
- xmlns='http://www.w3.org/2000/svg' \
- width='14' height='14' viewBox='0 0 16 \
- 16'%3E%3Cpath fill='%23BABABA' fill-rule='evenodd' \
- d='M11.707085,11.7071 L7.999975,15.4142 L4.292875,11.7071 \
- C3.902375,11.3166 3.902375,10.6834 \
- 4.292875,10.2929 C4.683375,9.90237 \
- 5.316575,9.90237 5.707075,10.2929 \
- L6.999975,11.5858 L6.999975,2 C6.999975,1.44771 \
- 7.447695,1 7.999975,1 C8.552255,1 8.999975,1.44771 \
- 8.999975,2 L8.999975,11.5858 L10.292865,10.2929 \
- C10.683395,9.90237 11.316555,9.90237 11.707085,10.2929 \
- C12.097605,10.6834 12.097605,11.3166 11.707085,11.7071 \
- Z'/%3E%3C/svg%3E%0A");
+ &[aria-sort='none']:hover {
+ background-image: url('data:image/svg+xml, %3csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="4 0 8 16"%3e %3cpath style="fill: %23BABABA;" fill-rule="evenodd" d="M11.707085,11.7071 L7.999975,15.4142 L4.292875,11.7071 C3.902375,11.3166 3.902375, 10.6834 4.292875,10.2929 C4.683375,9.90237 5.316575,9.90237 5.707075,10.2929 L6.999975, 11.5858 L6.999975,2 C6.999975,1.44771 7.447695,1 7.999975,1 C8.552255,1 8.999975,1.44771 8.999975,2 L8.999975,11.5858 L10.292865,10.2929 C10.683395 ,9.90237 11.316555,9.90237 11.707085,10.2929 C12.097605,10.6834 12.097605,11.3166 11.707085,11.7071 Z"/%3e %3c/svg%3e');
}
}
}
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 67a7daf8445..deba71c9dd3 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -5,7 +5,8 @@ class Projects::ImportsController < Projects::ApplicationController
include ImportUrlParams
# Authorize
- before_action :authorize_admin_project!
+ before_action :authorize_admin_project!, only: [:new, :create]
+ before_action :require_namespace_project_creation_permission, only: :show
before_action :require_no_repo, only: [:new, :create]
before_action :redirect_if_progress, only: [:new, :create]
before_action :redirect_if_no_import, only: :show
@@ -51,6 +52,10 @@ class Projects::ImportsController < Projects::ApplicationController
end
end
+ def require_namespace_project_creation_permission
+ render_404 unless current_user.can?(:admin_project, @project) || current_user.can?(:create_projects, @project.namespace)
+ end
+
def redirect_if_progress
if @project.import_in_progress?
redirect_to project_import_path(@project)
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index e1f6cbe3dca..3f7f8da3478 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -11,9 +11,6 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
- before_action only: [:show] do
- push_frontend_feature_flag(:job_log_json, project, default_enabled: true)
- end
before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
@@ -55,15 +52,10 @@ class Projects::JobsController < Projects::ApplicationController
format.json do
build.trace.being_watched!
- # TODO: when the feature flag is removed we should not pass
- # content_format to serialize method.
- content_format = Feature.enabled?(:job_log_json, @project, default_enabled: true) ? :json : :html
-
build_trace = Ci::BuildTrace.new(
build: @build,
stream: stream,
- state: params[:state],
- content_format: content_format)
+ state: params[:state])
render json: BuildTraceSerializer
.new(project: @project, current_user: @current_user)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2937c83cd27..20ef14e8546 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -109,8 +109,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# or from cache if already merged
@commits =
set_commits_for_rendering(
- @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch),
- commits_count: @merge_request.commits_count
+ @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch).with_markdown_cache,
+ commits_count: @merge_request.commits_count
)
render json: { html: view_to_html_string('projects/merge_requests/_commits') }
diff --git a/app/finders/projects/integrations/jira/issues_finder.rb b/app/finders/projects/integrations/jira/issues_finder.rb
new file mode 100644
index 00000000000..280ed7954de
--- /dev/null
+++ b/app/finders/projects/integrations/jira/issues_finder.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Projects
+ module Integrations
+ module Jira
+ IntegrationError = Class.new(StandardError)
+ RequestError = Class.new(StandardError)
+
+ class IssuesFinder
+ attr_reader :issues, :total_count
+
+ def initialize(project, params = {})
+ @project = project
+ @jira_service = project.jira_service
+ @page = params[:page].presence || 1
+ @params = params
+ end
+
+ def execute
+ return [] unless Feature.enabled?(:jira_integration, project)
+
+ raise IntegrationError, _('Jira service not configured.') unless jira_service&.active?
+
+ project_key = jira_service.project_key
+ raise IntegrationError, _('Jira project key is not configured') if project_key.blank?
+
+ fetch_issues(project_key)
+ end
+
+ private
+
+ attr_reader :project, :jira_service, :page, :params
+
+ # rubocop: disable CodeReuse/ServiceClass
+ def fetch_issues(project_key)
+ jql = ::Jira::JqlBuilderService.new(project_key, params).execute
+ response = ::Jira::Requests::Issues::ListService.new(jira_service, { jql: jql, page: page }).execute
+
+ if response.success?
+ @total_count = response.payload[:total_count]
+ @issues = response.payload[:issues]
+ else
+ raise RequestError, response.message
+ end
+ end
+ # rubocop: enable CodeReuse/ServiceClass
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb
index 2b5d04ef39e..3b6e5c4fd42 100644
--- a/app/graphql/resolvers/projects/jira_projects_resolver.rb
+++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb
@@ -37,7 +37,7 @@ module Resolvers
def jira_projects(name:)
args = { query: name }.compact
- return Jira::Requests::Projects.new(project.jira_service, args).execute
+ return Jira::Requests::Projects::ListService.new(project.jira_service, args).execute
end
end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 8a9380f4771..e9ef7278d44 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -271,6 +271,20 @@ module GitlabRoutingHelper
end
end
+ def gitlab_raw_snippet_blob_url(snippet, path, ref = nil)
+ params = {
+ snippet_id: snippet,
+ ref: ref || snippet.repository.root_ref,
+ path: path
+ }
+
+ if snippet.is_a?(ProjectSnippet)
+ project_snippet_blob_raw_url(snippet.project, params)
+ else
+ snippet_blob_raw_url(params)
+ end
+ end
+
def gitlab_snippet_notes_path(snippet, *args)
new_args = snippet_query_params(snippet, *args)
snippet_notes_path(snippet, *new_args)
diff --git a/app/models/ci/build_trace.rb b/app/models/ci/build_trace.rb
index b9db1559836..f70e1ed69ea 100644
--- a/app/models/ci/build_trace.rb
+++ b/app/models/ci/build_trace.rb
@@ -2,40 +2,22 @@
module Ci
class BuildTrace
- CONVERTERS = {
- html: Gitlab::Ci::Ansi2html,
- json: Gitlab::Ci::Ansi2json
- }.freeze
-
attr_reader :trace, :build
delegate :state, :append, :truncated, :offset, :size, :total, to: :trace, allow_nil: true
delegate :id, :status, :complete?, to: :build, prefix: true
- def initialize(build:, stream:, state:, content_format:)
+ def initialize(build:, stream:, state:)
@build = build
- @content_format = content_format
if stream.valid?
stream.limit
- @trace = CONVERTERS.fetch(content_format).convert(stream.stream, state)
+ @trace = Gitlab::Ci::Ansi2json.convert(stream.stream, state)
end
end
- def json?
- @content_format == :json
- end
-
- def html?
- @content_format == :html
- end
-
- def json_lines
- @trace&.lines if json?
- end
-
- def html_lines
- @trace&.html if html?
+ def lines
+ @trace&.lines
end
end
end
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index 456d32bf403..b8653f47392 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -53,6 +53,17 @@ class CommitCollection
self
end
+ # Returns the collection with markdown fields preloaded.
+ #
+ # Get the markdown cache from redis using pipeline to prevent n+1 requests
+ # when rendering the markdown of an attribute (e.g. title, full_title,
+ # description).
+ def with_markdown_cache
+ Commit.preload_markdown_cache!(commits)
+
+ self
+ end
+
def unenriched
commits.reject(&:gitaly_commit?)
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 8c97547f416..7ba5f1d01f9 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -23,7 +23,7 @@ class JiraService < IssueTrackerService
# TODO: we can probably just delegate as part of
# https://gitlab.com/gitlab-org/gitlab/issues/29404
- data_field :username, :password, :url, :api_url, :jira_issue_transition_id
+ data_field :username, :password, :url, :api_url, :jira_issue_transition_id, :project_key
before_update :reset_password
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 21a9325104d..5f45407c05e 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -334,7 +334,13 @@ class Snippet < ApplicationRecord
def file_name_on_repo
return if repository.empty?
- repository.ls_files(repository.root_ref).first
+ list_files(repository.root_ref).first
+ end
+
+ def list_files(ref = nil)
+ return [] if repository.empty?
+
+ repository.ls_files(ref)
end
class << self
diff --git a/app/serializers/build_trace_entity.rb b/app/serializers/build_trace_entity.rb
index b5bac8a5d64..f4c3c7770b2 100644
--- a/app/serializers/build_trace_entity.rb
+++ b/app/serializers/build_trace_entity.rb
@@ -12,6 +12,5 @@ class BuildTraceEntity < Grape::Entity
expose :size
expose :total
- expose :json_lines, as: :lines, if: ->(*) { object.json? }
- expose :html_lines, as: :html, if: ->(*) { object.html? }
+ expose :lines
end
diff --git a/app/services/jira/jql_builder_service.rb b/app/services/jira/jql_builder_service.rb
new file mode 100644
index 00000000000..cb8cee8e38a
--- /dev/null
+++ b/app/services/jira/jql_builder_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Jira
+ class JqlBuilderService
+ DEFAULT_SORT = "created"
+ DEFAULT_SORT_DIRECTION = "DESC"
+
+ def initialize(jira_project_key, params = {})
+ @jira_project_key = jira_project_key
+ @sort = params[:sort] || DEFAULT_SORT
+ @sort_direction = params[:sort_direction] || DEFAULT_SORT_DIRECTION
+ end
+
+ def execute
+ [by_project, order_by].join(' ')
+ end
+
+ private
+
+ attr_reader :jira_project_key, :sort, :sort_direction
+
+ def by_project
+ "project = #{jira_project_key}"
+ end
+
+ def order_by
+ "order by #{sort} #{sort_direction}"
+ end
+ end
+end
diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb
index 0934730d10c..7c6db372257 100644
--- a/app/services/jira/requests/base.rb
+++ b/app/services/jira/requests/base.rb
@@ -5,12 +5,11 @@ module Jira
class Base
include ProjectServicesLoggable
- attr_reader :jira_service, :project, :query
+ JIRA_API_VERSION = 2
- def initialize(jira_service, query: nil)
+ def initialize(jira_service, params = {})
@project = jira_service&.project
@jira_service = jira_service
- @query = query
end
def execute
@@ -19,8 +18,19 @@ module Jira
request
end
+ def base_api_url
+ "/rest/api/#{api_version}"
+ end
+
private
+ attr_reader :jira_service, :project
+
+ # override this method in the specific request class implementation if a differnt API version is required
+ def api_version
+ JIRA_API_VERSION
+ end
+
def client
@client ||= jira_service.client
end
diff --git a/app/services/jira/requests/issues/list_service.rb b/app/services/jira/requests/issues/list_service.rb
new file mode 100644
index 00000000000..44a3d3966a8
--- /dev/null
+++ b/app/services/jira/requests/issues/list_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Jira
+ module Requests
+ module Issues
+ class ListService < Base
+ extend ::Gitlab::Utils::Override
+
+ PER_PAGE = 100
+
+ def initialize(jira_service, params = {})
+ super(jira_service, params)
+
+ @jql = params[:jql].to_s
+ @page = params[:page].to_i || 1
+ end
+
+ private
+
+ attr_reader :jql, :page
+
+ override :url
+ def url
+ "#{base_api_url}/search?jql=#{CGI.escape(jql)}&startAt=#{start_at}&maxResults=#{PER_PAGE}&fields=*all"
+ end
+
+ override :build_service_response
+ def build_service_response(response)
+ return ServiceResponse.success(payload: empty_payload) if response.blank? || response["issues"].blank?
+
+ ServiceResponse.success(payload: {
+ issues: map_issues(response["issues"]),
+ is_last: last?(response),
+ total_count: response["total"].to_i
+ })
+ end
+
+ def map_issues(response)
+ response.map { |v| JIRA::Resource::Issue.build(client, v) }
+ end
+
+ def empty_payload
+ { issues: [], is_last: true, total_count: 0 }
+ end
+
+ def last?(response)
+ response["total"].to_i <= response["startAt"].to_i + response["issues"].size
+ end
+
+ def start_at
+ (page - 1) * PER_PAGE
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/jira/requests/projects.rb b/app/services/jira/requests/projects.rb
deleted file mode 100644
index afb3b45fac1..00000000000
--- a/app/services/jira/requests/projects.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module Jira
- module Requests
- class Projects < Base
- extend ::Gitlab::Utils::Override
-
- private
-
- override :url
- def url
- '/rest/api/2/project'
- end
-
- override :build_service_response
- def build_service_response(response)
- return ServiceResponse.success(payload: empty_payload) unless response.present?
-
- ServiceResponse.success(payload: { projects: map_projects(response), is_last: true })
- end
-
- def map_projects(response)
- response.map { |v| JIRA::Resource::Project.build(client, v) }.select(&method(:match_query?))
- end
-
- def match_query?(jira_project)
- query = self.query.to_s.downcase
-
- jira_project&.key&.downcase&.include?(query) || jira_project&.name&.downcase&.include?(query)
- end
-
- def empty_payload
- { projects: [], is_last: true }
- end
- end
- end
-end
diff --git a/app/services/jira/requests/projects/list_service.rb b/app/services/jira/requests/projects/list_service.rb
new file mode 100644
index 00000000000..8ecfd358ffb
--- /dev/null
+++ b/app/services/jira/requests/projects/list_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Jira
+ module Requests
+ module Projects
+ class ListService < Base
+ extend ::Gitlab::Utils::Override
+
+ def initialize(jira_service, params: {})
+ super(jira_service, params)
+
+ @query = params[:query]
+ end
+
+ private
+
+ attr_reader :query
+
+ override :url
+ def url
+ "#{base_api_url}/project"
+ end
+
+ override :build_service_response
+ def build_service_response(response)
+ return ServiceResponse.success(payload: empty_payload) unless response.present?
+
+ ServiceResponse.success(payload: { projects: map_projects(response), is_last: true })
+ end
+
+ def map_projects(response)
+ response.map { |v| JIRA::Resource::Project.build(client, v) }.select(&method(:match_query?))
+ end
+
+ def match_query?(jira_project)
+ query = query.to_s.downcase
+
+ jira_project&.key&.downcase&.include?(query) || jira_project&.name&.downcase&.include?(query)
+ end
+
+ def empty_payload
+ { projects: [], is_last: true }
+ end
+ end
+ end
+ end
+end
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 126d8450568..115ebc94238 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -8,10 +8,10 @@
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
= f.label 'Two-Factor Authentication code', name: :otp_attempt
- = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.'
+ = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.', data: { qa_selector: 'two_fa_code_field' }
%p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
- = f.submit "Verify code", class: "btn btn-success"
+ = f.submit "Verify code", class: "btn btn-success", data: { qa_selector: 'verify_code_button' }
- if @user.two_factor_u2f_enabled?
= render "u2f/authenticate", params: params, resource: resource, resource_name: resource_name, render_remember_me: true, target_path: new_user_session_path
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index be0af977011..94fd40ed669 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -9,5 +9,5 @@
%span.monospace= code
.d-flex
- = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10'
+ = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10', data: { qa_selector: 'proceed_button' }
= link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 6e3de0447ac..b8c5d626d17 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -39,7 +39,7 @@
= _('To add the entry manually, provide the following details to the application on your phone.')
%p.gl-mt-0.gl-mb-0
= _('Account: %{account}') % { account: @account_string }
- %p.gl-mt-0.gl-mb-0
+ %p.gl-mt-0.gl-mb-0{ data: { qa_selector: 'otp_secret_content' } }
= _('Key: %{key}') %{ key: current_user.otp_secret.scan(/.{4}/).join(' ') }
%p.two-factor-new-manual-content
= _('Time based: Yes')
@@ -49,9 +49,9 @@
= @error
.form-group
= label_tag :pin_code, _('Pin code'), class: "label-bold"
- = text_field_tag :pin_code, nil, class: "form-control", required: true
+ = text_field_tag :pin_code, nil, class: "form-control", required: true, data: { qa_selector: 'pin_code_field' }
.gl-mt-3
- = submit_tag _('Register with two-factor app'), class: 'btn btn-success'
+ = submit_tag _('Register with two-factor app'), class: 'btn btn-success', data: { qa_selector: 'register_2fa_app_button' }
%hr
diff --git a/app/workers/concerns/project_export_options.rb b/app/workers/concerns/project_export_options.rb
deleted file mode 100644
index e9318c1ba43..00000000000
--- a/app/workers/concerns/project_export_options.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module ProjectExportOptions
- extend ActiveSupport::Concern
-
- EXPORT_RETRY_COUNT = 3
-
- included do
- sidekiq_options retry: EXPORT_RETRY_COUNT, status_expiration: StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION
-
- # We mark the project export as failed once we have exhausted all retries
- sidekiq_retries_exhausted do |job|
- project = Project.find(job['args'][1])
- # rubocop: disable CodeReuse/ActiveRecord
- job = project.export_jobs.find_by(jid: job["jid"])
- # rubocop: enable CodeReuse/ActiveRecord
-
- if job&.fail_op
- Sidekiq.logger.info "Job #{job['jid']} for project #{project.id} has been set to failed state"
- else
- Sidekiq.logger.error "Failed to set Job #{job['jid']} for project #{project.id} to failed state"
- end
- end
- end
-end
diff --git a/app/workers/group_export_worker.rb b/app/workers/group_export_worker.rb
index 6fd977e43d8..e22b691d35e 100644
--- a/app/workers/group_export_worker.rb
+++ b/app/workers/group_export_worker.rb
@@ -6,6 +6,7 @@ class GroupExportWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :importers
loggable_arguments 2
+ sidekiq_options retry: false
def perform(current_user_id, group_id, params = {})
current_user = User.find(current_user_id)
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index d29348e85bc..6c8640138a1 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -3,12 +3,13 @@
class ProjectExportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
- include ProjectExportOptions
feature_category :importers
worker_resource_boundary :memory
urgency :throttled
loggable_arguments 2, 3
+ sidekiq_options retry: false
+ sidekiq_options status_expiration: StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION
def perform(current_user_id, project_id, after_export_strategy = {}, params = {})
current_user = User.find(current_user_id)
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 30570a2227e..54052bda675 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -4,10 +4,11 @@ class RepositoryImportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ExceptionBacktrace
include ProjectStartImport
- include ProjectImportOptions
feature_category :importers
worker_has_external_dependencies!
+ sidekiq_options retry: false
+ sidekiq_options status_expiration: Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION
# technical debt: https://gitlab.com/gitlab-org/gitlab/issues/33991
sidekiq_options memory_killer_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MEMORY_GROWTH_KB', 50).to_i