diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-14 00:09:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-14 00:09:30 +0000 |
commit | 8957ace3159e5369a700a77614493ed6a8a98f93 (patch) | |
tree | 98ff5be0caa30cfebb4e0cd0ae2ceaf21ce92eb4 | |
parent | 232e0a31f1e5d5b3a788dfc3dba8f8d41df36bf9 (diff) | |
download | gitlab-ce-8957ace3159e5369a700a77614493ed6a8a98f93.tar.gz |
Add latest changes from gitlab-org/gitlab@master
57 files changed, 787 insertions, 735 deletions
@@ -171,7 +171,7 @@ group :unicorn do end group :puma do - gem 'gitlab-puma', '~> 4.3.1.gitlab.2', require: false + gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false gem 'rack-timeout', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index ef8bffdc592..6da9a9a1740 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -391,7 +391,7 @@ GEM gitlab-mail_room (0.0.3) gitlab-markup (1.7.0) gitlab-net-dns (0.9.1) - gitlab-puma (4.3.1.gitlab.2) + gitlab-puma (4.3.3.gitlab.2) nio4r (~> 2.0) gitlab-puma_worker_killer (0.1.1.gitlab.1) get_process_mem (~> 0.2) @@ -1237,7 +1237,7 @@ DEPENDENCIES gitlab-mail_room (~> 0.0.3) gitlab-markup (~> 1.7.0) gitlab-net-dns (~> 0.9.1) - gitlab-puma (~> 4.3.1.gitlab.2) + gitlab-puma (~> 4.3.3.gitlab.2) gitlab-puma_worker_killer (~> 0.1.1.gitlab.1) gitlab-sidekiq-fetcher (= 0.5.2) gitlab-styles (~> 3.1.0) diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 35634d63e4a..a8c94b6263e 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -1,87 +1,17 @@ -/* eslint-disable no-new */ import Vue from 'vue'; -import axios from '../../lib/utils/axios_utils'; -import notebookLab from '../../notebook/index.vue'; +import NotebookViewer from './notebook_viewer.vue'; export default () => { const el = document.getElementById('js-notebook-viewer'); - new Vue({ + return new Vue({ el, - components: { - notebookLab, + render(createElement) { + return createElement(NotebookViewer, { + props: { + endpoint: el.dataset.endpoint, + }, + }); }, - data() { - return { - error: false, - loadError: false, - loading: true, - json: {}, - }; - }, - mounted() { - if (gon.katex_css_url) { - const katexStyles = document.createElement('link'); - katexStyles.setAttribute('rel', 'stylesheet'); - katexStyles.setAttribute('href', gon.katex_css_url); - document.head.appendChild(katexStyles); - } - - if (gon.katex_js_url) { - const katexScript = document.createElement('script'); - katexScript.addEventListener('load', () => { - this.loadFile(); - }); - katexScript.setAttribute('src', gon.katex_js_url); - document.head.appendChild(katexScript); - } else { - this.loadFile(); - } - }, - methods: { - loadFile() { - axios - .get(el.dataset.endpoint) - .then(res => res.data) - .then(data => { - this.json = data; - this.loading = false; - }) - .catch(e => { - if (e.status !== 200) { - this.loadError = true; - } - - this.error = true; - }); - }, - }, - template: ` - <div class="container-fluid md prepend-top-default append-bottom-default"> - <div - class="text-center loading" - v-if="loading && !error"> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" - aria-label="iPython notebook loading"> - </i> - </div> - <notebook-lab - v-if="!loading && !error" - :notebook="json" - code-css-class="code white" /> - <p - class="text-center" - v-if="error"> - <span v-if="loadError"> - An error occurred while loading the file. Please try again later. - </span> - <span v-else> - An error occurred while parsing the file. - </span> - </p> - </div> - `, }); }; diff --git a/app/assets/javascripts/blob/notebook/notebook_viewer.vue b/app/assets/javascripts/blob/notebook/notebook_viewer.vue new file mode 100644 index 00000000000..401fe9beb62 --- /dev/null +++ b/app/assets/javascripts/blob/notebook/notebook_viewer.vue @@ -0,0 +1,81 @@ +<script> +import axios from '~/lib/utils/axios_utils'; +import notebookLab from '~/notebook/index.vue'; +import { GlLoadingIcon } from '@gitlab/ui'; + +export default { + components: { + notebookLab, + GlLoadingIcon, + }, + props: { + endpoint: { + type: String, + required: true, + }, + }, + data() { + return { + error: false, + loadError: false, + loading: true, + json: {}, + }; + }, + mounted() { + if (gon.katex_css_url) { + const katexStyles = document.createElement('link'); + katexStyles.setAttribute('rel', 'stylesheet'); + katexStyles.setAttribute('href', gon.katex_css_url); + document.head.appendChild(katexStyles); + } + + if (gon.katex_js_url) { + const katexScript = document.createElement('script'); + katexScript.addEventListener('load', () => { + this.loadFile(); + }); + katexScript.setAttribute('src', gon.katex_js_url); + document.head.appendChild(katexScript); + } else { + this.loadFile(); + } + }, + methods: { + loadFile() { + axios + .get(this.endpoint) + .then(res => res.data) + .then(data => { + this.json = data; + this.loading = false; + }) + .catch(e => { + if (e.status !== 200) { + this.loadError = true; + } + this.error = true; + }); + }, + }, +}; +</script> + +<template> + <div + class="js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default" + > + <div v-if="loading && !error" class="text-center loading"> + <gl-loading-icon class="mt-5" size="lg" /> + </div> + <notebook-lab v-if="!loading && !error" :notebook="json" code-css-class="code white" /> + <p v-if="error" class="text-center"> + <span v-if="loadError" ref="loadErrorMessage">{{ + __('An error occurred while loading the file. Please try again later.') + }}</span> + <span v-else ref="parsingErrorMessage">{{ + __('An error occurred while parsing the file.') + }}</span> + </p> + </div> +</template> diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb index 892f6dc657c..a3a18a115e9 100644 --- a/app/controllers/admin/jobs_controller.rb +++ b/app/controllers/admin/jobs_controller.rb @@ -3,10 +3,10 @@ class Admin::JobsController < Admin::ApplicationController def index # We need all builds for tabs counters - @all_builds = JobsFinder.new(current_user: current_user).execute + @all_builds = Ci::JobsFinder.new(current_user: current_user).execute @scope = params[:scope] - @builds = JobsFinder.new(current_user: current_user, params: params).execute + @builds = Ci::JobsFinder.new(current_user: current_user, params: params).execute @builds = @builds.eager_load_everything @builds = @builds.page(params[:page]).per(30) end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index cb473d6ee96..e0457925b34 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -19,10 +19,10 @@ class Projects::JobsController < Projects::ApplicationController def index # We need all builds for tabs counters - @all_builds = JobsFinder.new(current_user: current_user, project: @project).execute + @all_builds = Ci::JobsFinder.new(current_user: current_user, project: @project).execute @scope = params[:scope] - @builds = JobsFinder.new(current_user: current_user, project: @project, params: params).execute + @builds = Ci::JobsFinder.new(current_user: current_user, project: @project, params: params).execute @builds = @builds.eager_load_everything @builds = @builds.page(params[:page]).per(30).without_count end diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb index ead839e8441..e7e8a900060 100644 --- a/app/controllers/projects/pipeline_schedules_controller.rb +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -13,8 +13,8 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def index @scope = params[:scope] - @all_schedules = PipelineSchedulesFinder.new(@project).execute - @schedules = PipelineSchedulesFinder.new(@project).execute(scope: params[:scope]) + @all_schedules = Ci::PipelineSchedulesFinder.new(@project).execute + @schedules = Ci::PipelineSchedulesFinder.new(@project).execute(scope: params[:scope]) .includes(:last_pipeline) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 6d902e099d9..11b6e5eb66f 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -22,7 +22,7 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - @pipelines = PipelinesFinder + @pipelines = Ci::PipelinesFinder .new(project, current_user, scope: @scope) .execute .page(params[:page]) @@ -251,7 +251,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def limited_pipelines_count(project, scope = nil) - finder = PipelinesFinder.new(project, current_user, scope: scope) + finder = Ci::PipelinesFinder.new(project, current_user, scope: scope) view_context.limited_counter_with_delimiter(finder.execute) end diff --git a/app/finders/ci/jobs_finder.rb b/app/finders/ci/jobs_finder.rb new file mode 100644 index 00000000000..2169bf8c53e --- /dev/null +++ b/app/finders/ci/jobs_finder.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Ci + class JobsFinder + include Gitlab::Allowable + + def initialize(current_user:, project: nil, params: {}) + @current_user = current_user + @project = project + @params = params + end + + def execute + builds = init_collection.order_id_desc + filter_by_scope(builds) + rescue Gitlab::Access::AccessDeniedError + Ci::Build.none + end + + private + + attr_reader :current_user, :project, :params + + def init_collection + project ? project_builds : all_builds + end + + def all_builds + raise Gitlab::Access::AccessDeniedError unless current_user&.admin? + + Ci::Build.all + end + + def project_builds + raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project) + + project.builds.relevant + end + + def filter_by_scope(builds) + case params[:scope] + when 'pending' + builds.pending.reverse_order + when 'running' + builds.running.reverse_order + when 'finished' + builds.finished + else + builds + end + end + end +end diff --git a/app/finders/ci/pipeline_schedules_finder.rb b/app/finders/ci/pipeline_schedules_finder.rb new file mode 100644 index 00000000000..2544c8c3254 --- /dev/null +++ b/app/finders/ci/pipeline_schedules_finder.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Ci + class PipelineSchedulesFinder + attr_reader :project, :pipeline_schedules + + def initialize(project) + @project = project + @pipeline_schedules = project.pipeline_schedules + end + + # rubocop: disable CodeReuse/ActiveRecord + def execute(scope: nil) + scoped_schedules = + case scope + when 'active' + pipeline_schedules.active + when 'inactive' + pipeline_schedules.inactive + else + pipeline_schedules + end + + scoped_schedules.order(id: :desc) + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb new file mode 100644 index 00000000000..9e71e92b456 --- /dev/null +++ b/app/finders/ci/pipelines_finder.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +module Ci + class PipelinesFinder + attr_reader :project, :pipelines, :params, :current_user + + ALLOWED_INDEXED_COLUMNS = %w[id status ref updated_at user_id].freeze + + def initialize(project, current_user, params = {}) + @project = project + @current_user = current_user + @pipelines = project.all_pipelines + @params = params + end + + def execute + unless Ability.allowed?(current_user, :read_pipeline, project) + return Ci::Pipeline.none + end + + items = pipelines.no_child + items = by_scope(items) + items = by_status(items) + items = by_ref(items) + items = by_sha(items) + items = by_name(items) + items = by_username(items) + items = by_yaml_errors(items) + items = by_updated_at(items) + sort_items(items) + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def ids_for_ref(refs) + pipelines.where(ref: refs).group(:ref).select('max(id)') + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def from_ids(ids) + pipelines.unscoped.where(project_id: project.id, id: ids) + end + # rubocop: enable CodeReuse/ActiveRecord + + def branches + project.repository.branch_names + end + + def tags + project.repository.tag_names + end + + def by_scope(items) + case params[:scope] + when 'running' + items.running + when 'pending' + items.pending + when 'finished' + items.finished + when 'branches' + from_ids(ids_for_ref(branches)) + when 'tags' + from_ids(ids_for_ref(tags)) + else + items + end + end + + # rubocop: disable CodeReuse/ActiveRecord + def by_status(items) + return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) + + items.where(status: params[:status]) + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def by_ref(items) + if params[:ref].present? + items.where(ref: params[:ref]) + else + items + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def by_sha(items) + if params[:sha].present? + items.where(sha: params[:sha]) + else + items + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def by_name(items) + if params[:name].present? + items.joins(:user).where(users: { name: params[:name] }) + else + items + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def by_username(items) + if params[:username].present? + items.joins(:user).where(users: { username: params[:username] }) + else + items + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def by_yaml_errors(items) + case Gitlab::Utils.to_boolean(params[:yaml_errors]) + when true + items.where("yaml_errors IS NOT NULL") + when false + items.where("yaml_errors IS NULL") + else + items + end + end + # rubocop: enable CodeReuse/ActiveRecord + + def by_updated_at(items) + items = items.updated_before(params[:updated_before]) if params[:updated_before].present? + items = items.updated_after(params[:updated_after]) if params[:updated_after].present? + + items + end + + # rubocop: disable CodeReuse/ActiveRecord + def sort_items(items) + order_by = if ALLOWED_INDEXED_COLUMNS.include?(params[:order_by]) + params[:order_by] + else + :id + end + + sort = if params[:sort] =~ /\A(ASC|DESC)\z/i + params[:sort] + else + :desc + end + + items.order(order_by => sort) + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/finders/ci/runner_jobs_finder.rb b/app/finders/ci/runner_jobs_finder.rb new file mode 100644 index 00000000000..ffcdb407e7e --- /dev/null +++ b/app/finders/ci/runner_jobs_finder.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Ci + class RunnerJobsFinder + attr_reader :runner, :params + + ALLOWED_INDEXED_COLUMNS = %w[id].freeze + + def initialize(runner, params = {}) + @runner = runner + @params = params + end + + def execute + items = @runner.builds + items = by_status(items) + sort_items(items) + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def by_status(items) + return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) + + items.where(status: params[:status]) + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def sort_items(items) + return items unless ALLOWED_INDEXED_COLUMNS.include?(params[:order_by]) + + order_by = params[:order_by] + sort = if /\A(ASC|DESC)\z/i.match?(params[:sort]) + params[:sort] + else + :desc + end + + items.order(order_by => sort) + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/finders/jobs_finder.rb b/app/finders/jobs_finder.rb deleted file mode 100644 index bac18e69618..00000000000 --- a/app/finders/jobs_finder.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -class JobsFinder - include Gitlab::Allowable - - def initialize(current_user:, project: nil, params: {}) - @current_user = current_user - @project = project - @params = params - end - - def execute - builds = init_collection.order_id_desc - filter_by_scope(builds) - rescue Gitlab::Access::AccessDeniedError - Ci::Build.none - end - - private - - attr_reader :current_user, :project, :params - - def init_collection - project ? project_builds : all_builds - end - - def all_builds - raise Gitlab::Access::AccessDeniedError unless current_user&.admin? - - Ci::Build.all - end - - def project_builds - raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project) - - project.builds.relevant - end - - def filter_by_scope(builds) - case params[:scope] - when 'pending' - builds.pending.reverse_order - when 'running' - builds.running.reverse_order - when 'finished' - builds.finished - else - builds - end - end -end diff --git a/app/finders/pipeline_schedules_finder.rb b/app/finders/pipeline_schedules_finder.rb deleted file mode 100644 index 3beee608268..00000000000 --- a/app/finders/pipeline_schedules_finder.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -class PipelineSchedulesFinder - attr_reader :project, :pipeline_schedules - - def initialize(project) - @project = project - @pipeline_schedules = project.pipeline_schedules - end - - # rubocop: disable CodeReuse/ActiveRecord - def execute(scope: nil) - scoped_schedules = - case scope - when 'active' - pipeline_schedules.active - when 'inactive' - pipeline_schedules.inactive - else - pipeline_schedules - end - - scoped_schedules.order(id: :desc) - end - # rubocop: enable CodeReuse/ActiveRecord -end diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb deleted file mode 100644 index 0599daab564..00000000000 --- a/app/finders/pipelines_finder.rb +++ /dev/null @@ -1,156 +0,0 @@ -# frozen_string_literal: true - -class PipelinesFinder - attr_reader :project, :pipelines, :params, :current_user - - ALLOWED_INDEXED_COLUMNS = %w[id status ref updated_at user_id].freeze - - def initialize(project, current_user, params = {}) - @project = project - @current_user = current_user - @pipelines = project.all_pipelines - @params = params - end - - def execute - unless Ability.allowed?(current_user, :read_pipeline, project) - return Ci::Pipeline.none - end - - items = pipelines.no_child - items = by_scope(items) - items = by_status(items) - items = by_ref(items) - items = by_sha(items) - items = by_name(items) - items = by_username(items) - items = by_yaml_errors(items) - items = by_updated_at(items) - sort_items(items) - end - - private - - # rubocop: disable CodeReuse/ActiveRecord - def ids_for_ref(refs) - pipelines.where(ref: refs).group(:ref).select('max(id)') - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def from_ids(ids) - pipelines.unscoped.where(project_id: project.id, id: ids) - end - # rubocop: enable CodeReuse/ActiveRecord - - def branches - project.repository.branch_names - end - - def tags - project.repository.tag_names - end - - def by_scope(items) - case params[:scope] - when 'running' - items.running - when 'pending' - items.pending - when 'finished' - items.finished - when 'branches' - from_ids(ids_for_ref(branches)) - when 'tags' - from_ids(ids_for_ref(tags)) - else - items - end - end - - # rubocop: disable CodeReuse/ActiveRecord - def by_status(items) - return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) - - items.where(status: params[:status]) - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def by_ref(items) - if params[:ref].present? - items.where(ref: params[:ref]) - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def by_sha(items) - if params[:sha].present? - items.where(sha: params[:sha]) - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def by_name(items) - if params[:name].present? - items.joins(:user).where(users: { name: params[:name] }) - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def by_username(items) - if params[:username].present? - items.joins(:user).where(users: { username: params[:username] }) - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def by_yaml_errors(items) - case Gitlab::Utils.to_boolean(params[:yaml_errors]) - when true - items.where("yaml_errors IS NOT NULL") - when false - items.where("yaml_errors IS NULL") - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - - def by_updated_at(items) - items = items.updated_before(params[:updated_before]) if params[:updated_before].present? - items = items.updated_after(params[:updated_after]) if params[:updated_after].present? - - items - end - - # rubocop: disable CodeReuse/ActiveRecord - def sort_items(items) - order_by = if ALLOWED_INDEXED_COLUMNS.include?(params[:order_by]) - params[:order_by] - else - :id - end - - sort = if params[:sort] =~ /\A(ASC|DESC)\z/i - params[:sort] - else - :desc - end - - items.order(order_by => sort) - end - # rubocop: enable CodeReuse/ActiveRecord -end diff --git a/app/finders/runner_jobs_finder.rb b/app/finders/runner_jobs_finder.rb deleted file mode 100644 index ef90817416a..00000000000 --- a/app/finders/runner_jobs_finder.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class RunnerJobsFinder - attr_reader :runner, :params - - ALLOWED_INDEXED_COLUMNS = %w[id].freeze - - def initialize(runner, params = {}) - @runner = runner - @params = params - end - - def execute - items = @runner.builds - items = by_status(items) - sort_items(items) - end - - private - - # rubocop: disable CodeReuse/ActiveRecord - def by_status(items) - return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) - - items.where(status: params[:status]) - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - def sort_items(items) - return items unless ALLOWED_INDEXED_COLUMNS.include?(params[:order_by]) - - order_by = params[:order_by] - sort = if /\A(ASC|DESC)\z/i.match?(params[:sort]) - params[:sort] - else - :desc - end - - items.order(order_by => sort) - end - # rubocop: enable CodeReuse/ActiveRecord -end diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb index a6f82cc8505..46d9e174deb 100644 --- a/app/graphql/resolvers/concerns/resolves_pipelines.rb +++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb @@ -30,6 +30,6 @@ module ResolvesPipelines end def resolve_pipelines(project, params = {}) - PipelinesFinder.new(project, context[:current_user], params).execute + Ci::PipelinesFinder.new(project, context[:current_user], params).execute end end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 49dcc441780..9b9b1417e22 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1166,7 +1166,7 @@ :weight: 1 :idempotent: - :name: project_update_repository_storage - :feature_category: :source_code_management + :feature_category: :gitaly :has_external_dependencies: :urgency: :low :resource_boundary: :unknown diff --git a/app/workers/project_update_repository_storage_worker.rb b/app/workers/project_update_repository_storage_worker.rb index ea2b9e799c0..ecee33e6421 100644 --- a/app/workers/project_update_repository_storage_worker.rb +++ b/app/workers/project_update_repository_storage_worker.rb @@ -3,7 +3,7 @@ class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker - feature_category :source_code_management + feature_category :gitaly def perform(project_id, new_repository_storage_key) project = Project.find(project_id) diff --git a/changelogs/unreleased/197918-add-package-type-param-to-group-packages-api.yml b/changelogs/unreleased/197918-add-package-type-param-to-group-packages-api.yml new file mode 100644 index 00000000000..ff9eb13bea6 --- /dev/null +++ b/changelogs/unreleased/197918-add-package-type-param-to-group-packages-api.yml @@ -0,0 +1,5 @@ +--- +title: Add package_type as a filter option to the group packages list API endpoint +merge_request: 26833 +author: +type: added diff --git a/changelogs/unreleased/210332-approximate-counters-are-not-working-on-gitlab-com.yml b/changelogs/unreleased/210332-approximate-counters-are-not-working-on-gitlab-com.yml new file mode 100644 index 00000000000..e7b614173ec --- /dev/null +++ b/changelogs/unreleased/210332-approximate-counters-are-not-working-on-gitlab-com.yml @@ -0,0 +1,5 @@ +--- +title: Use batch counters instead of approximate counters in usage data +merge_request: 27218 +author: +type: performance diff --git a/changelogs/unreleased/fix-prevent-user-theme-color-api-overwrite.yml b/changelogs/unreleased/fix-prevent-user-theme-color-api-overwrite.yml new file mode 100644 index 00000000000..9fd3cb83869 --- /dev/null +++ b/changelogs/unreleased/fix-prevent-user-theme-color-api-overwrite.yml @@ -0,0 +1,5 @@ +--- +title: Prevent default overwrite for theme and color ID in user API +merge_request: 26792 +author: Fabio Huser +type: fixed diff --git a/changelogs/unreleased/update-puma-to-4-3-3.yml b/changelogs/unreleased/update-puma-to-4-3-3.yml new file mode 100644 index 00000000000..f01fa09a417 --- /dev/null +++ b/changelogs/unreleased/update-puma-to-4-3-3.yml @@ -0,0 +1,5 @@ +--- +title: Update Puma to 4.3.3 +merge_request: 27232 +author: +type: security diff --git a/doc/api/packages.md b/doc/api/packages.md index b1b6969769c..a29e1f99fd3 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -67,6 +67,7 @@ GET /groups/:id/packages | `exclude_subgroups` | boolean | false | If the param is included as true, packages from projects from subgroups are not listed. Default is `false`. | | `order_by`| string | no | The field to use as order. One of `created_at` (default), `name`, `version`, `type`, or `project_path`. | | `sort` | string | no | The direction of the order, either `asc` (default) for ascending order or `desc` for descending order. | +| `package_type` | string | no | Filter the returned packages by type. One of `conan`, `maven`, `npm` or `nuget`. (_Introduced in GitLab 12.9_) | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=true diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb index 445a37a70c0..edc99590cdb 100644 --- a/lib/api/pipeline_schedules.rb +++ b/lib/api/pipeline_schedules.rb @@ -22,7 +22,7 @@ module API get ':id/pipeline_schedules' do authorize! :read_pipeline_schedule, user_project - schedules = PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope]) + schedules = Ci::PipelineSchedulesFinder.new(user_project).execute(scope: params[:scope]) .preload([:owner, :last_pipeline]) present paginate(schedules), with: Entities::PipelineSchedule end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 66a183173af..06f8920b37c 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -27,7 +27,7 @@ module API optional :username, type: String, desc: 'The username of the user who triggered pipelines' optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' - optional :order_by, type: String, values: PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id', + optional :order_by, type: String, values: Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id', desc: 'Order pipelines' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort pipelines' @@ -36,7 +36,7 @@ module API authorize! :read_pipeline, user_project authorize! :read_build, user_project - pipelines = PipelinesFinder.new(user_project, current_user, params).execute + pipelines = Ci::PipelinesFinder.new(user_project, current_user, params).execute present paginate(pipelines), with: Entities::PipelineBasic end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index c2d371b6867..eba1b5499d0 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -115,7 +115,7 @@ module API params do requires :id, type: Integer, desc: 'The ID of the runner' optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES - optional :order_by, type: String, desc: 'Order by `id` or not', values: RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS + optional :order_by, type: String, desc: 'Order by `id` or not', values: Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)' use :pagination end @@ -123,7 +123,7 @@ module API runner = get_runner(params[:id]) authenticate_list_runners_jobs!(runner) - jobs = RunnerJobsFinder.new(runner, params).execute + jobs = Ci::RunnerJobsFinder.new(runner, params).execute present paginate(jobs), with: Entities::JobBasicWithProject end diff --git a/lib/api/users.rb b/lib/api/users.rb index cc13c8aab9e..1ca222b4ed5 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -52,8 +52,8 @@ module API optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 optional :avatar, type: File, desc: 'Avatar image for user' # rubocop:disable Scalability/FileUploads - optional :theme_id, type: Integer, default: 1, desc: 'The GitLab theme for the user' - optional :color_scheme_id, type: Integer, default: 1, desc: 'The color scheme for the file viewer' + optional :theme_id, type: Integer, desc: 'The GitLab theme for the user' + optional :color_scheme_id, type: Integer, desc: 'The color scheme for the file viewer' optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' all_or_none_of :extern_uid, :provider diff --git a/lib/gitlab/cache/import/caching.rb b/lib/gitlab/cache/import/caching.rb new file mode 100644 index 00000000000..ead94761ae7 --- /dev/null +++ b/lib/gitlab/cache/import/caching.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +module Gitlab + module Cache + module Import + module Caching + # The default timeout of the cache keys. + TIMEOUT = 24.hours.to_i + + WRITE_IF_GREATER_SCRIPT = <<-EOF.strip_heredoc.freeze + local key, value, ttl = KEYS[1], tonumber(ARGV[1]), ARGV[2] + local existing = tonumber(redis.call("get", key)) + + if existing == nil or value > existing then + redis.call("set", key, value) + redis.call("expire", key, ttl) + return true + else + return false + end + EOF + + # Reads a cache key. + # + # If the key exists and has a non-empty value its TTL is refreshed + # automatically. + # + # raw_key - The cache key to read. + # timeout - The new timeout of the key if the key is to be refreshed. + def self.read(raw_key, timeout: TIMEOUT) + key = cache_key_for(raw_key) + value = Redis::Cache.with { |redis| redis.get(key) } + + if value.present? + # We refresh the expiration time so frequently used keys stick + # around, removing the need for querying the database as much as + # possible. + # + # A key may be empty when we looked up a GitHub user (for example) but + # did not find a matching GitLab user. In that case we _don't_ want to + # refresh the TTL so we automatically pick up the right data when said + # user were to register themselves on the GitLab instance. + Redis::Cache.with { |redis| redis.expire(key, timeout) } + end + + value + end + + # Reads an integer from the cache, or returns nil if no value was found. + # + # See Caching.read for more information. + def self.read_integer(raw_key, timeout: TIMEOUT) + value = read(raw_key, timeout: timeout) + + value.to_i if value.present? + end + + # Sets a cache key to the given value. + # + # key - The cache key to write. + # value - The value to set. + # timeout - The time after which the cache key should expire. + def self.write(raw_key, value, timeout: TIMEOUT) + key = cache_key_for(raw_key) + + Redis::Cache.with do |redis| + redis.set(key, value, ex: timeout) + end + + value + end + + # Adds a value to a set. + # + # raw_key - The key of the set to add the value to. + # value - The value to add to the set. + # timeout - The new timeout of the key. + def self.set_add(raw_key, value, timeout: TIMEOUT) + key = cache_key_for(raw_key) + + Redis::Cache.with do |redis| + redis.multi do |m| + m.sadd(key, value) + m.expire(key, timeout) + end + end + end + + # Returns true if the given value is present in the set. + # + # raw_key - The key of the set to check. + # value - The value to check for. + def self.set_includes?(raw_key, value) + key = cache_key_for(raw_key) + + Redis::Cache.with do |redis| + redis.sismember(key, value) + end + end + + # Sets multiple keys to a given value. + # + # mapping - A Hash mapping the cache keys to their values. + # timeout - The time after which the cache key should expire. + def self.write_multiple(mapping, timeout: TIMEOUT) + Redis::Cache.with do |redis| + redis.multi do |multi| + mapping.each do |raw_key, value| + multi.set(cache_key_for(raw_key), value, ex: timeout) + end + end + end + end + + # Sets the expiration time of a key. + # + # raw_key - The key for which to change the timeout. + # timeout - The new timeout. + def self.expire(raw_key, timeout) + key = cache_key_for(raw_key) + + Redis::Cache.with do |redis| + redis.expire(key, timeout) + end + end + + # Sets a key to the given integer but only if the existing value is + # smaller than the given value. + # + # This method uses a Lua script to ensure the read and write are atomic. + # + # raw_key - The key to set. + # value - The new value for the key. + # timeout - The key timeout in seconds. + # + # Returns true when the key was overwritten, false otherwise. + def self.write_if_greater(raw_key, value, timeout: TIMEOUT) + key = cache_key_for(raw_key) + val = Redis::Cache.with do |redis| + redis + .eval(WRITE_IF_GREATER_SCRIPT, keys: [key], argv: [value, timeout]) + end + + val ? true : false + end + + def self.cache_key_for(raw_key) + "#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}" + end + end + end + end +end diff --git a/lib/gitlab/github_import.rb b/lib/gitlab/github_import.rb index 14a6d6443ec..9a7c406d981 100644 --- a/lib/gitlab/github_import.rb +++ b/lib/gitlab/github_import.rb @@ -16,7 +16,7 @@ module Gitlab def self.ghost_user_id key = 'github-import/ghost-user-id' - Caching.read_integer(key) || Caching.write(key, User.select(:id).ghost.id) + Gitlab::Cache::Import::Caching.read_integer(key) || Gitlab::Cache::Import::Caching.write(key, User.select(:id).ghost.id) end end end diff --git a/lib/gitlab/github_import/caching.rb b/lib/gitlab/github_import/caching.rb deleted file mode 100644 index b08f133794f..00000000000 --- a/lib/gitlab/github_import/caching.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module GithubImport - module Caching - # The default timeout of the cache keys. - TIMEOUT = 24.hours.to_i - - WRITE_IF_GREATER_SCRIPT = <<-EOF.strip_heredoc.freeze - local key, value, ttl = KEYS[1], tonumber(ARGV[1]), ARGV[2] - local existing = tonumber(redis.call("get", key)) - - if existing == nil or value > existing then - redis.call("set", key, value) - redis.call("expire", key, ttl) - return true - else - return false - end - EOF - - # Reads a cache key. - # - # If the key exists and has a non-empty value its TTL is refreshed - # automatically. - # - # raw_key - The cache key to read. - # timeout - The new timeout of the key if the key is to be refreshed. - def self.read(raw_key, timeout: TIMEOUT) - key = cache_key_for(raw_key) - value = Redis::Cache.with { |redis| redis.get(key) } - - if value.present? - # We refresh the expiration time so frequently used keys stick - # around, removing the need for querying the database as much as - # possible. - # - # A key may be empty when we looked up a GitHub user (for example) but - # did not find a matching GitLab user. In that case we _don't_ want to - # refresh the TTL so we automatically pick up the right data when said - # user were to register themselves on the GitLab instance. - Redis::Cache.with { |redis| redis.expire(key, timeout) } - end - - value - end - - # Reads an integer from the cache, or returns nil if no value was found. - # - # See Caching.read for more information. - def self.read_integer(raw_key, timeout: TIMEOUT) - value = read(raw_key, timeout: timeout) - - value.to_i if value.present? - end - - # Sets a cache key to the given value. - # - # key - The cache key to write. - # value - The value to set. - # timeout - The time after which the cache key should expire. - def self.write(raw_key, value, timeout: TIMEOUT) - key = cache_key_for(raw_key) - - Redis::Cache.with do |redis| - redis.set(key, value, ex: timeout) - end - - value - end - - # Adds a value to a set. - # - # raw_key - The key of the set to add the value to. - # value - The value to add to the set. - # timeout - The new timeout of the key. - def self.set_add(raw_key, value, timeout: TIMEOUT) - key = cache_key_for(raw_key) - - Redis::Cache.with do |redis| - redis.multi do |m| - m.sadd(key, value) - m.expire(key, timeout) - end - end - end - - # Returns true if the given value is present in the set. - # - # raw_key - The key of the set to check. - # value - The value to check for. - def self.set_includes?(raw_key, value) - key = cache_key_for(raw_key) - - Redis::Cache.with do |redis| - redis.sismember(key, value) - end - end - - # Sets multiple keys to a given value. - # - # mapping - A Hash mapping the cache keys to their values. - # timeout - The time after which the cache key should expire. - def self.write_multiple(mapping, timeout: TIMEOUT) - Redis::Cache.with do |redis| - redis.multi do |multi| - mapping.each do |raw_key, value| - multi.set(cache_key_for(raw_key), value, ex: timeout) - end - end - end - end - - # Sets the expiration time of a key. - # - # raw_key - The key for which to change the timeout. - # timeout - The new timeout. - def self.expire(raw_key, timeout) - key = cache_key_for(raw_key) - - Redis::Cache.with do |redis| - redis.expire(key, timeout) - end - end - - # Sets a key to the given integer but only if the existing value is - # smaller than the given value. - # - # This method uses a Lua script to ensure the read and write are atomic. - # - # raw_key - The key to set. - # value - The new value for the key. - # timeout - The key timeout in seconds. - # - # Returns true when the key was overwritten, false otherwise. - def self.write_if_greater(raw_key, value, timeout: TIMEOUT) - key = cache_key_for(raw_key) - val = Redis::Cache.with do |redis| - redis - .eval(WRITE_IF_GREATER_SCRIPT, keys: [key], argv: [value, timeout]) - end - - val ? true : false - end - - def self.cache_key_for(raw_key) - "#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}" - end - end - end -end diff --git a/lib/gitlab/github_import/issuable_finder.rb b/lib/gitlab/github_import/issuable_finder.rb index c81603a1aa9..136531505ea 100644 --- a/lib/gitlab/github_import/issuable_finder.rb +++ b/lib/gitlab/github_import/issuable_finder.rb @@ -23,7 +23,7 @@ module Gitlab # # This method will return `nil` if no ID could be found. def database_id - val = Caching.read(cache_key) + val = Gitlab::Cache::Import::Caching.read(cache_key) val.to_i if val.present? end @@ -32,7 +32,7 @@ module Gitlab # # database_id - The ID of the corresponding database row. def cache_database_id(database_id) - Caching.write(cache_key, database_id) + Gitlab::Cache::Import::Caching.write(cache_key, database_id) end private diff --git a/lib/gitlab/github_import/label_finder.rb b/lib/gitlab/github_import/label_finder.rb index cad39e48e43..39e669dbba4 100644 --- a/lib/gitlab/github_import/label_finder.rb +++ b/lib/gitlab/github_import/label_finder.rb @@ -15,7 +15,7 @@ module Gitlab # Returns the label ID for the given name. def id_for(name) - Caching.read_integer(cache_key_for(name)) + Gitlab::Cache::Import::Caching.read_integer(cache_key_for(name)) end # rubocop: disable CodeReuse/ActiveRecord @@ -27,7 +27,7 @@ module Gitlab hash[cache_key_for(name)] = id end - Caching.write_multiple(mapping) + Gitlab::Cache::Import::Caching.write_multiple(mapping) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/gitlab/github_import/milestone_finder.rb b/lib/gitlab/github_import/milestone_finder.rb index a157a1e1ff5..d9290e36ea1 100644 --- a/lib/gitlab/github_import/milestone_finder.rb +++ b/lib/gitlab/github_import/milestone_finder.rb @@ -18,7 +18,7 @@ module Gitlab def id_for(issuable) return unless issuable.milestone_number - Caching.read_integer(cache_key_for(issuable.milestone_number)) + Gitlab::Cache::Import::Caching.read_integer(cache_key_for(issuable.milestone_number)) end # rubocop: disable CodeReuse/ActiveRecord @@ -30,7 +30,7 @@ module Gitlab hash[cache_key_for(iid)] = id end - Caching.write_multiple(mapping) + Gitlab::Cache::Import::Caching.write_multiple(mapping) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/gitlab/github_import/page_counter.rb b/lib/gitlab/github_import/page_counter.rb index a3e7b3c1afc..3b4fd42ba2a 100644 --- a/lib/gitlab/github_import/page_counter.rb +++ b/lib/gitlab/github_import/page_counter.rb @@ -19,12 +19,12 @@ module Gitlab # # Returns true if the page number was overwritten, false otherwise. def set(page) - Caching.write_if_greater(cache_key, page) + Gitlab::Cache::Import::Caching.write_if_greater(cache_key, page) end # Returns the current value from the cache. def current - Caching.read_integer(cache_key) || 1 + Gitlab::Cache::Import::Caching.read_integer(cache_key) || 1 end end end diff --git a/lib/gitlab/github_import/parallel_scheduling.rb b/lib/gitlab/github_import/parallel_scheduling.rb index 849a66d47ed..cabc615ea11 100644 --- a/lib/gitlab/github_import/parallel_scheduling.rb +++ b/lib/gitlab/github_import/parallel_scheduling.rb @@ -42,7 +42,7 @@ module Gitlab # still scheduling duplicates while. Since all work has already been # completed those jobs will just cycle through any remaining pages while # not scheduling anything. - Caching.expire(already_imported_cache_key, 15.minutes.to_i) + Gitlab::Cache::Import::Caching.expire(already_imported_cache_key, 15.minutes.to_i) retval end @@ -112,14 +112,14 @@ module Gitlab def already_imported?(object) id = id_for_already_imported_cache(object) - Caching.set_includes?(already_imported_cache_key, id) + Gitlab::Cache::Import::Caching.set_includes?(already_imported_cache_key, id) end # Marks the given object as "already imported". def mark_as_imported(object) id = id_for_already_imported_cache(object) - Caching.set_add(already_imported_cache_key, id) + Gitlab::Cache::Import::Caching.set_add(already_imported_cache_key, id) end # Returns the ID to use for the cache used for checking if an object has diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb index 51a532437bd..9da986ae921 100644 --- a/lib/gitlab/github_import/user_finder.rb +++ b/lib/gitlab/github_import/user_finder.rb @@ -102,11 +102,11 @@ module Gitlab def email_for_github_username(username) cache_key = EMAIL_FOR_USERNAME_CACHE_KEY % username - email = Caching.read(cache_key) + email = Gitlab::Cache::Import::Caching.read(cache_key) unless email user = client.user(username) - email = Caching.write(cache_key, user.email) if user + email = Gitlab::Cache::Import::Caching.write(cache_key, user.email) if user end email @@ -125,7 +125,7 @@ module Gitlab def id_for_github_id(id) gitlab_id = query_id_for_github_id(id) || nil - Caching.write(ID_CACHE_KEY % id, gitlab_id) + Gitlab::Cache::Import::Caching.write(ID_CACHE_KEY % id, gitlab_id) end # Queries and caches the GitLab user ID for a GitHub email, if one was @@ -133,7 +133,7 @@ module Gitlab def id_for_github_email(email) gitlab_id = query_id_for_github_email(email) || nil - Caching.write(ID_FOR_EMAIL_CACHE_KEY % email, gitlab_id) + Gitlab::Cache::Import::Caching.write(ID_FOR_EMAIL_CACHE_KEY % email, gitlab_id) end # rubocop: disable CodeReuse/ActiveRecord @@ -155,7 +155,7 @@ module Gitlab # 1. A boolean indicating if the key was present or not. # 2. The ID as an Integer, or nil in case no ID could be found. def read_id_from_cache(key) - value = Caching.read(key) + value = Gitlab::Cache::Import::Caching.read(key) exists = !value.nil? number = value.to_i diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 35c69099b01..c69c49831fa 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -2,7 +2,6 @@ module Gitlab class UsageData - APPROXIMATE_COUNT_MODELS = [Label, MergeRequest, Note, Todo].freeze BATCH_SIZE = 100 class << self @@ -107,10 +106,12 @@ module Gitlab suggestions: count(Suggestion), todos: count(Todo), uploads: count(Upload), - web_hooks: count(WebHook) + web_hooks: count(WebHook), + labels: count(Label), + merge_requests: count(MergeRequest), + notes: count(Note) }.merge( services_usage, - approximate_counts, usage_counters, user_preferences_usage, ingress_modsecurity_usage @@ -251,16 +252,6 @@ module Gitlab fallback end - def approximate_counts - approx_counts = Gitlab::Database::Count.approximate_counts(APPROXIMATE_COUNT_MODELS) - - APPROXIMATE_COUNT_MODELS.each_with_object({}) do |model, result| - key = model.name.underscore.pluralize.to_sym - - result[key] = approx_counts[model] || -1 - end - end - def installation_type if Rails.env.production? Gitlab::INSTALLATION_TYPE diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1bbb8be6af5..a431612bfe7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1948,6 +1948,9 @@ msgstr "" msgid "An error occurred while parsing recent searches" msgstr "" +msgid "An error occurred while parsing the file." +msgstr "" + msgid "An error occurred while removing epics." msgstr "" diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 0ff3e45c956..9fc70412975 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -308,6 +308,48 @@ describe 'File blob', :js do end end + context 'Jupiter Notebook file' do + before do + project.add_maintainer(project.creator) + + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add Jupiter Notebook", + file_path: 'files/basic.ipynb', + file_content: project.repository.blob_at('add-ipython-files', 'files/ipython/basic.ipynb').data + ).execute + + visit_blob('files/basic.ipynb') + + wait_for_requests + end + + it 'displays the blob' do + aggregate_failures do + # shows rendered notebook + expect(page).to have_selector('.js-notebook-viewer-mounted') + + # does show a viewer switcher + expect(page).to have_selector('.js-blob-viewer-switcher') + + # show a disabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn.disabled') + + # shows a raw button + expect(page).to have_link('Open raw') + + # shows a download button + expect(page).to have_link('Download') + + # shows the rendered notebook + expect(page).to have_content('test') + end + end + end + context 'ISO file (stored in LFS)' do context 'when LFS is enabled on the project' do before do diff --git a/spec/finders/jobs_finder_spec.rb b/spec/finders/ci/jobs_finder_spec.rb index 01f9ec03c79..7083e8fbf43 100644 --- a/spec/finders/jobs_finder_spec.rb +++ b/spec/finders/ci/jobs_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe JobsFinder, '#execute' do +describe Ci::JobsFinder, '#execute' do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:user, :admin) } let_it_be(:project) { create(:project, :private, public_builds: false) } diff --git a/spec/finders/pipeline_schedules_finder_spec.rb b/spec/finders/ci/pipeline_schedules_finder_spec.rb index 8d0bde15e03..5b5154ce834 100644 --- a/spec/finders/pipeline_schedules_finder_spec.rb +++ b/spec/finders/ci/pipeline_schedules_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe PipelineSchedulesFinder do +describe Ci::PipelineSchedulesFinder do let(:project) { create(:project) } let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) } diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/ci/pipelines_finder_spec.rb index 1dbf9491118..6528093731e 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/ci/pipelines_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe PipelinesFinder do +describe Ci::PipelinesFinder do let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } let(:params) { {} } diff --git a/spec/finders/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb index c11f9182036..a3245119291 100644 --- a/spec/finders/runner_jobs_finder_spec.rb +++ b/spec/finders/ci/runner_jobs_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe RunnerJobsFinder do +describe Ci::RunnerJobsFinder do let(:project) { create(:project) } let(:runner) { create(:ci_runner, :instance) } diff --git a/spec/frontend/blob/notebook/notebook_viever_spec.js b/spec/frontend/blob/notebook/notebook_viever_spec.js new file mode 100644 index 00000000000..535d2bd544a --- /dev/null +++ b/spec/frontend/blob/notebook/notebook_viever_spec.js @@ -0,0 +1,108 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import component from '~/blob/notebook/notebook_viewer.vue'; +import NotebookLab from '~/notebook/index.vue'; +import waitForPromises from 'helpers/wait_for_promises'; + +describe('iPython notebook renderer', () => { + let wrapper; + let mock; + + const endpoint = 'test'; + const mockNotebook = { + cells: [ + { + cell_type: 'markdown', + source: ['# test'], + }, + { + cell_type: 'code', + execution_count: 1, + source: ['def test(str)', ' return str'], + outputs: [], + }, + ], + }; + + const mountComponent = () => { + wrapper = shallowMount(component, { propsData: { endpoint } }); + }; + + const findLoading = () => wrapper.find(GlLoadingIcon); + const findNotebookLab = () => wrapper.find(NotebookLab); + const findLoadErrorMessage = () => wrapper.find({ ref: 'loadErrorMessage' }); + const findParseErrorMessage = () => wrapper.find({ ref: 'parsingErrorMessage' }); + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + mock.restore(); + }); + + it('shows loading icon', () => { + mock.onGet(endpoint).reply(() => new Promise(() => {})); + mountComponent({ loadFile: jest.fn() }); + expect(findLoading().exists()).toBe(true); + }); + + describe('successful response', () => { + beforeEach(() => { + mock.onGet(endpoint).reply(200, mockNotebook); + mountComponent(); + return waitForPromises(); + }); + + it('does not show loading icon', () => { + expect(findLoading().exists()).toBe(false); + }); + + it('renders the notebook', () => { + expect(findNotebookLab().exists()).toBe(true); + }); + }); + + describe('error in JSON response', () => { + beforeEach(() => { + mock.onGet(endpoint).reply(() => + // eslint-disable-next-line prefer-promise-reject-errors + Promise.reject({ status: 200 }), + ); + + mountComponent(); + return waitForPromises(); + }); + + it('does not show loading icon', () => { + expect(findLoading().exists()).toBe(false); + }); + + it('shows error message', () => { + expect(findParseErrorMessage().text()).toEqual('An error occurred while parsing the file.'); + }); + }); + + describe('error getting file', () => { + beforeEach(() => { + mock.onGet(endpoint).reply(500, ''); + + mountComponent(); + return waitForPromises(); + }); + + it('does not show loading icon', () => { + expect(findLoading().exists()).toBe(false); + }); + + it('shows error message', () => { + expect(findLoadErrorMessage().text()).toEqual( + 'An error occurred while loading the file. Please try again later.', + ); + }); + }); +}); diff --git a/spec/frontend/fixtures/static/notebook_viewer.html b/spec/frontend/fixtures/static/notebook_viewer.html deleted file mode 100644 index 4bbb7bf1094..00000000000 --- a/spec/frontend/fixtures/static/notebook_viewer.html +++ /dev/null @@ -1 +0,0 @@ -<div class="file-content" data-endpoint="/test" id="js-notebook-viewer"></div> diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js deleted file mode 100644 index db6ca5bd22d..00000000000 --- a/spec/javascripts/blob/notebook/index_spec.js +++ /dev/null @@ -1,130 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import renderNotebook from '~/blob/notebook'; - -describe('iPython notebook renderer', () => { - preloadFixtures('static/notebook_viewer.html'); - - beforeEach(() => { - loadFixtures('static/notebook_viewer.html'); - }); - - it('shows loading icon', () => { - renderNotebook(); - - expect(document.querySelector('.loading')).not.toBeNull(); - }); - - describe('successful response', () => { - let mock; - - beforeEach(done => { - mock = new MockAdapter(axios); - mock.onGet('/test').reply(200, { - cells: [ - { - cell_type: 'markdown', - source: ['# test'], - }, - { - cell_type: 'code', - execution_count: 1, - source: ['def test(str)', ' return str'], - outputs: [], - }, - ], - }); - - renderNotebook(); - - setTimeout(() => { - done(); - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('does not show loading icon', () => { - expect(document.querySelector('.loading')).toBeNull(); - }); - - it('renders the notebook', () => { - expect(document.querySelector('.md')).not.toBeNull(); - }); - - it('renders the markdown cell', () => { - expect(document.querySelector('h1')).not.toBeNull(); - - expect(document.querySelector('h1').textContent.trim()).toBe('test'); - }); - - it('highlights code', () => { - expect(document.querySelector('.token')).not.toBeNull(); - - expect(document.querySelector('.language-python')).not.toBeNull(); - }); - }); - - describe('error in JSON response', () => { - let mock; - - beforeEach(done => { - mock = new MockAdapter(axios); - mock.onGet('/test').reply(() => - // eslint-disable-next-line prefer-promise-reject-errors - Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }), - ); - - renderNotebook(); - - setTimeout(() => { - done(); - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('does not show loading icon', () => { - expect(document.querySelector('.loading')).toBeNull(); - }); - - it('shows error message', () => { - expect(document.querySelector('.md').textContent.trim()).toBe( - 'An error occurred while parsing the file.', - ); - }); - }); - - describe('error getting file', () => { - let mock; - - beforeEach(done => { - mock = new MockAdapter(axios); - mock.onGet('/test').reply(500, ''); - - renderNotebook(); - - setTimeout(() => { - done(); - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('does not show loading icon', () => { - expect(document.querySelector('.loading')).toBeNull(); - }); - - it('shows error message', () => { - expect(document.querySelector('.md').textContent.trim()).toBe( - 'An error occurred while loading the file. Please try again later.', - ); - }); - }); -}); diff --git a/spec/lib/gitlab/github_import/caching_spec.rb b/spec/lib/gitlab/cache/import/caching_spec.rb index 18c3e382532..e4aec0f4dec 100644 --- a/spec/lib/gitlab/github_import/caching_spec.rb +++ b/spec/lib/gitlab/cache/import/caching_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::GithubImport::Caching, :clean_gitlab_redis_cache do +describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do describe '.read' do it 'reads a value from the cache' do described_class.write('foo', 'bar') diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb index b8a6feb6c73..55add863d43 100644 --- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb +++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do describe '#cache_database_id' do it 'caches the ID of a database row' do - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .with('github-import/issuable-finder/4/MergeRequest/1', 10) diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb index 039ae27ad57..bb946a15a2d 100644 --- a/spec/lib/gitlab/github_import/label_finder_spec.rb +++ b/spec/lib/gitlab/github_import/label_finder_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do it 'returns nil for an empty cache key' do key = finder.cache_key_for(bug.name) - Gitlab::GithubImport::Caching.write(key, '') + Gitlab::Cache::Import::Caching.write(key, '') expect(finder.id_for(bug.name)).to be_nil end @@ -40,7 +40,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do describe '#build_cache' do it 'builds the cache of all project labels' do - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write_multiple) .with( { diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb index 407e2e67ec9..ecb533b7e39 100644 --- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do it 'returns nil for an empty cache key' do key = finder.cache_key_for(milestone.iid) - Gitlab::GithubImport::Caching.write(key, '') + Gitlab::Cache::Import::Caching.write(key, '') expect(finder.id_for(issuable)).to be_nil end @@ -41,7 +41,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do describe '#build_cache' do it 'builds the cache of all project milestones' do - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write_multiple) .with("github-import/milestone-finder/#{project.id}/1" => milestone.id) .and_call_original diff --git a/spec/lib/gitlab/github_import/page_counter_spec.rb b/spec/lib/gitlab/github_import/page_counter_spec.rb index 87f3ce45fd3..95125c9c22f 100644 --- a/spec/lib/gitlab/github_import/page_counter_spec.rb +++ b/spec/lib/gitlab/github_import/page_counter_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do end it 'sets the initial page number to the cached value when one is present' do - Gitlab::GithubImport::Caching.write(counter.cache_key, 2) + Gitlab::Cache::Import::Caching.write(counter.cache_key, 2) expect(described_class.new(project, :issues).current).to eq(2) end diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb index f4d107e3dce..a6ae99b395c 100644 --- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb @@ -57,7 +57,7 @@ describe Gitlab::GithubImport::ParallelScheduling do expect(importer).to receive(:parallel_import) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:expire) .with(importer.already_imported_cache_key, a_kind_of(Numeric)) @@ -287,7 +287,7 @@ describe Gitlab::GithubImport::ParallelScheduling do .with(object) .and_return(object.id) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:set_add) .with(importer.already_imported_cache_key, object.id) .and_call_original diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb index 74b5c1c52cd..8764ebef32b 100644 --- a/spec/lib/gitlab/github_import/user_finder_spec.rb +++ b/spec/lib/gitlab/github_import/user_finder_spec.rb @@ -162,7 +162,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do context 'when an Email address is cached' do it 'reads the Email address from the cache' do - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:read) .and_return(email) @@ -182,7 +182,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do it 'caches the Email address when an Email address is available' do expect(client).to receive(:user).with('kittens').and_return(user) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .with(an_instance_of(String), email) @@ -195,7 +195,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do .with('kittens') .and_return(nil) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .not_to receive(:write) expect(finder.email_for_github_username('kittens')).to be_nil @@ -207,7 +207,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do let(:id) { 4 } it 'reads a user ID from the cache' do - Gitlab::GithubImport::Caching + Gitlab::Cache::Import::Caching .write(described_class::ID_CACHE_KEY % id, 4) expect(finder.cached_id_for_github_id(id)).to eq([true, 4]) @@ -222,7 +222,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do let(:email) { 'kittens@example.com' } it 'reads a user ID from the cache' do - Gitlab::GithubImport::Caching + Gitlab::Cache::Import::Caching .write(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 4) expect(finder.cached_id_for_github_email(email)).to eq([true, 4]) @@ -241,7 +241,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do .with(id) .and_return(42) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .with(described_class::ID_CACHE_KEY % id, 42) @@ -253,7 +253,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do .with(id) .and_return(nil) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .with(described_class::ID_CACHE_KEY % id, nil) @@ -269,7 +269,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do .with(email) .and_return(42) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 42) @@ -281,7 +281,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do .with(email) .and_return(nil) - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, nil) @@ -317,13 +317,13 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do describe '#read_id_from_cache' do it 'reads an ID from the cache' do - Gitlab::GithubImport::Caching.write('foo', 10) + Gitlab::Cache::Import::Caching.write('foo', 10) expect(finder.read_id_from_cache('foo')).to eq([true, 10]) end it 'reads a cache key with an empty value' do - Gitlab::GithubImport::Caching.write('foo', nil) + Gitlab::Cache::Import::Caching.write('foo', nil) expect(finder.read_id_from_cache('foo')).to eq([true, nil]) end diff --git a/spec/lib/gitlab/github_import_spec.rb b/spec/lib/gitlab/github_import_spec.rb index c3ddac01c87..290d66243aa 100644 --- a/spec/lib/gitlab/github_import_spec.rb +++ b/spec/lib/gitlab/github_import_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::GithubImport do end it 'caches the ghost user ID' do - expect(Gitlab::GithubImport::Caching) + expect(Gitlab::Cache::Import::Caching) .to receive(:write) .once .and_call_original diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 7bc9e1a9a32..fed33c89726 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -387,29 +387,6 @@ describe Gitlab::UsageData do expect(described_class.count(relation, fallback: 15, batch: false)).to eq(15) end end - - describe '#approximate_counts' do - it 'gets approximate counts for selected models', :aggregate_failures do - create(:label) - - expect(Gitlab::Database::Count).to receive(:approximate_counts) - .with(described_class::APPROXIMATE_COUNT_MODELS).once.and_call_original - - counts = described_class.approximate_counts.values - - expect(counts.count).to eq(described_class::APPROXIMATE_COUNT_MODELS.count) - expect(counts.any? { |count| count < 0 }).to be_falsey - end - - it 'returns default values if counts can not be retrieved', :aggregate_failures do - described_class::APPROXIMATE_COUNT_MODELS.map do |model| - model.name.underscore.pluralize.to_sym - end - - expect(Gitlab::Database::Count).to receive(:approximate_counts).and_return({}) - expect(described_class.approximate_counts.values.uniq).to eq([-1]) - end - end end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 7d63a031666..6d1b76a9aea 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -832,6 +832,13 @@ describe API::Users, :do_not_mock_admin_mode do expect(user.reload.private_profile).to eq(false) end + it "does have default values for theme and color-scheme ID" do + put api("/users/#{user.id}", admin), params: {} + + expect(user.reload.theme_id).to eq(Gitlab::Themes.default.id) + expect(user.reload.color_scheme_id).to eq(Gitlab::ColorSchemes.default.id) + end + it "updates private profile" do put api("/users/#{user.id}", admin), params: { private_profile: true } @@ -857,6 +864,19 @@ describe API::Users, :do_not_mock_admin_mode do expect(user.reload.private_profile).to eq(true) end + it "does not modify theme or color-scheme ID when field is not provided" do + theme = Gitlab::Themes.each.find { |t| t.id != Gitlab::Themes.default.id } + scheme = Gitlab::ColorSchemes.each.find { |t| t.id != Gitlab::ColorSchemes.default.id } + + user.update(theme_id: theme.id, color_scheme_id: scheme.id) + + put api("/users/#{user.id}", admin), params: {} + + expect(response).to have_gitlab_http_status(:ok) + expect(user.reload.theme_id).to eq(theme.id) + expect(user.reload.color_scheme_id).to eq(scheme.id) + end + it "does not update admin status" do put api("/users/#{admin_user.id}", admin), params: { can_create_group: false } |