Commit 8957ace3 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 232e0a31
...@@ -171,7 +171,7 @@ group :unicorn do ...@@ -171,7 +171,7 @@ group :unicorn do
end end
group :puma do 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 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false
gem 'rack-timeout', require: false gem 'rack-timeout', require: false
end end
......
...@@ -391,7 +391,7 @@ GEM ...@@ -391,7 +391,7 @@ GEM
gitlab-mail_room (0.0.3) gitlab-mail_room (0.0.3)
gitlab-markup (1.7.0) gitlab-markup (1.7.0)
gitlab-net-dns (0.9.1) gitlab-net-dns (0.9.1)
gitlab-puma (4.3.1.gitlab.2) gitlab-puma (4.3.3.gitlab.2)
nio4r (~> 2.0) nio4r (~> 2.0)
gitlab-puma_worker_killer (0.1.1.gitlab.1) gitlab-puma_worker_killer (0.1.1.gitlab.1)
get_process_mem (~> 0.2) get_process_mem (~> 0.2)
...@@ -1237,7 +1237,7 @@ DEPENDENCIES ...@@ -1237,7 +1237,7 @@ DEPENDENCIES
gitlab-mail_room (~> 0.0.3) gitlab-mail_room (~> 0.0.3)
gitlab-markup (~> 1.7.0) gitlab-markup (~> 1.7.0)
gitlab-net-dns (~> 0.9.1) 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-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2) gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 3.1.0) gitlab-styles (~> 3.1.0)
......
/* eslint-disable no-new */
import Vue from 'vue'; import Vue from 'vue';
import axios from '../../lib/utils/axios_utils'; import NotebookViewer from './notebook_viewer.vue';
import notebookLab from '../../notebook/index.vue';
export default () => { export default () => {
const el = document.getElementById('js-notebook-viewer'); const el = document.getElementById('js-notebook-viewer');
new Vue({ return new Vue({
el, el,
components: { render(createElement) {
notebookLab, 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>
`,
}); });
}; };
<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>
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
class Admin::JobsController < Admin::ApplicationController class Admin::JobsController < Admin::ApplicationController
def index def index
# We need all builds for tabs counters # 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] @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.eager_load_everything
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30)
end end
......
...@@ -19,10 +19,10 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -19,10 +19,10 @@ class Projects::JobsController < Projects::ApplicationController
def index def index
# We need all builds for tabs counters # 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] @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.eager_load_everything
@builds = @builds.page(params[:page]).per(30).without_count @builds = @builds.page(params[:page]).per(30).without_count
end end
......
...@@ -13,8 +13,8 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -13,8 +13,8 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def index def index
@scope = params[:scope] @scope = params[:scope]
@all_schedules = PipelineSchedulesFinder.new(@project).execute @all_schedules = Ci::PipelineSchedulesFinder.new(@project).execute
@schedules = PipelineSchedulesFinder.new(@project).execute(scope: params[:scope]) @schedules = Ci::PipelineSchedulesFinder.new(@project).execute(scope: params[:scope])
.includes(:last_pipeline) .includes(:last_pipeline)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -22,7 +22,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -22,7 +22,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def index def index
@scope = params[:scope] @scope = params[:scope]
@pipelines = PipelinesFinder @pipelines = Ci::PipelinesFinder
.new(project, current_user, scope: @scope) .new(project, current_user, scope: @scope)
.execute .execute
.page(params[:page]) .page(params[:page])
...@@ -251,7 +251,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -251,7 +251,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def limited_pipelines_count(project, scope = nil) 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) view_context.limited_counter_with_delimiter(finder.execute)
end end
......
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
...@@ -30,6 +30,6 @@ module ResolvesPipelines ...@@ -30,6 +30,6 @@ module ResolvesPipelines
end end
def resolve_pipelines(project, params = {}) def resolve_pipelines(project, params = {})
PipelinesFinder.new(project, context[:current_user], params).execute Ci::PipelinesFinder.new(project, context[:current_user], params).execute
end end
end end
...@@ -1166,7 +1166,7 @@ ...@@ -1166,7 +1166,7 @@
:weight: 1 :weight: 1
:idempotent: :idempotent:
- :name: project_update_repository_storage - :name: project_update_repository_storage
:feature_category: :source_code_management :feature_category: :gitaly
:has_external_dependencies: :has_external_dependencies:
:urgency: :low :urgency: :low
:resource_boundary: :unknown :resource_boundary: :unknown
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management feature_category :gitaly
def perform(project_id, new_repository_storage_key) def perform(project_id, new_repository_storage_key)
project = Project.find(project_id) project = Project.find(project_id)
......
---
title: Add package_type as a filter option to the group packages list API endpoint
merge_request: 26833
author:
type: added
---
title: Use batch counters instead of approximate counters in usage data
merge_request: 27218
author:
type: performance
---
title: Prevent default overwrite for theme and color ID in user API
merge_request: 26792
author: Fabio Huser
type: fixed
---
title: Update Puma to 4.3.3
merge_request: 27232
author:
type: security
...@@ -67,6 +67,7 @@ GET /groups/:id/packages ...@@ -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`. | | `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`. | | `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. | | `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 ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=true curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=true
......
...@@ -22,7 +22,7 @@ module API ...@@ -22,7 +22,7 @@ module API
get ':id/pipeline_schedules' do get ':id/pipeline_schedules' do
authorize! :read_pipeline_schedule, user_project 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]) .preload([:owner, :last_pipeline])
present paginate(schedules), with: Entities::PipelineSchedule present paginate(schedules), with: Entities::PipelineSchedule
end end
......
...@@ -27,7 +27,7 @@ module API ...@@ -27,7 +27,7 @@ module API
optional :username, type: String, desc: 'The username of the user who triggered pipelines' 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_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 :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' desc: 'Order pipelines'
optional :sort, type: String, values: %w[asc desc], default: 'desc', optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Sort pipelines' desc: 'Sort pipelines'
...@@ -36,7 +36,7 @@ module API ...@@ -36,7 +36,7 @@ module API
authorize! :read_pipeline, user_project authorize! :read_pipeline, user_project
authorize! :read_build, 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 present paginate(pipelines), with: Entities::PipelineBasic
end end
......
...@@ -115,7 +115,7 @@ module API ...@@ -115,7 +115,7 @@ module API
params do params do
requires :id, type: Integer, desc: 'The ID of the runner' 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 :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)' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination use :pagination
end end
...@@ -123,7 +123,7 @@ module API ...@@ -123,7 +123,7 @@ module API
runner = get_runner(params[:id]) runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner) authenticate_list_runners_jobs!(runner)
jobs = RunnerJobsFinder.new(runner, params).execute jobs = Ci::RunnerJobsFinder.new(runner, params).execute
present paginate(jobs), with: Entities::JobBasicWithProject present paginate(jobs), with: Entities::JobBasicWithProject
end end
......
...@@ -52,8 +52,8 @@ module API ...@@ -52,8 +52,8 @@ module API
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' 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 # 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 :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 :theme_id, type: Integer, desc: 'The GitLab theme for the user'
optional :color_scheme_id, type: Integer, default: 1, desc: 'The color scheme for the file viewer' 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' optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
......
# 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
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
def self.ghost_user_id def self.ghost_user_id
key = 'github-import/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 end
end end
# 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
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
# #
# This method will return `nil` if no ID could be found. # This method will return `nil` if no ID could be found.
def database_id def database_id
val = Caching.read(cache_key) val = Gitlab::Cache::Import::Caching.read(cache_key)
val.to_i if val.present? val.to_i if val.present?
end end
...@@ -32,7 +32,7 @@ module Gitlab ...@@ -32,7 +32,7 @@ module Gitlab
# #
# database_id - The ID of the corresponding database row. # database_id - The ID of the corresponding database row.
def cache_database_id(database_id) def cache_database_id(database_id)
Caching.write(cache_key, database_id) Gitlab::Cache::Import::Caching.write(cache_key, database_id)
end end
private private
......
...@@ -15,7 +15,7 @@ module Gitlab ...@@ -15,7 +15,7 @@ module Gitlab
# Returns the label ID for the given name. # Returns the label ID for the given name.
def id_for(name) def id_for(name)
Caching.read_integer(cache_key_for(name)) Gitlab::Cache::Import::Caching.read_integer(cache_key_for(name))
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
...@@ -27,7 +27,7 @@ module Gitlab ...@@ -27,7 +27,7 @@ module Gitlab
hash[cache_key_for(name)] = id hash[cache_key_for(name)] = id
end end
Caching.write_multiple(mapping) Gitlab::Cache::Import::Caching.write_multiple(mapping)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -18,7 +18,7 @@ module Gitlab ...@@ -18,7 +18,7 @@ module Gitlab
def id_for(issuable) def id_for(issuable)
return unless issuable.milestone_number 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 end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
hash[cache_key_for(iid)] = id hash[cache_key_for(iid)] = id
end end
Caching.write_multiple(mapping) Gitlab::Cache::Import::Caching.write_multiple(mapping)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -19,12 +19,12 @@ module Gitlab ...@@ -19,12 +19,12 @@ module Gitlab
# #
# Returns true if the page number was overwritten, false otherwise. # Returns true if the page number was overwritten, false otherwise.
def set(page) def set(page)
Caching.write_if_greater(cache_key, page) Gitlab::Cache::Import::Caching.write_if_greater(cache_key, page)
end end
# Returns the current value from the cache. # Returns the current value from the cache.
def current def current
Caching.read_integer(cache_key) || 1 Gitlab::Cache::Import::Caching.read_integer(cache_key) || 1
end end
end end
end end
......
...@@ -42,7 +42,7 @@ module Gitlab ...@@ -42,7 +42,7 @@ module Gitlab
# still scheduling duplicates while. Since all work has already been # still scheduling duplicates while. Since all work has already been
# completed those jobs will just cycle through any remaining pages while # completed those jobs will just cycle through any remaining pages while
# not scheduling anything. # 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 retval
end end
...@@ -112,14 +112,14 @@ module Gitlab ...@@ -112,14 +112,14 @@ module Gitlab
def already_imported?(object) def already_imported?(object)
id = id_for_already_imported_cache(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 end
# Marks the given object as "already imported". # Marks the given object as "already imported".
def mark_as_imported(object) def mark_as_imported(object)
id = id_for_already_imported_cache(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 end
# Returns the ID to use for the cache used for checking if an object has # Returns the ID to use for the cache used for checking if an object has
......
...@@ -102,11 +102,11 @@ module Gitlab ...@@ -102,11 +102,11 @@ module Gitlab
def email_for_github_username(username) def email_for_github_username(username)
cache_key = EMAIL_FOR_USERNAME_CACHE_KEY % username cache_key = EMAIL_FOR_USERNAME_CACHE_KEY % username
email = Caching.read(cache_key) email = Gitlab::Cache::Import::Caching.read(cache_key)
unless email unless email
user = client.user(username) 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 end
email email
...@@ -125,7 +125,7 @@ module Gitlab ...@@ -125,7 +125,7 @@ module Gitlab
def id_for_github_id(id) def id_for_github_id(id)
gitlab_id = query_id_for_github_id(id) || nil 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 end
# Queries and caches the GitLab user ID for a GitHub email, if one was # Queries and caches the GitLab user ID for a GitHub email, if one was
...@@ -133,7 +133,7 @@ module Gitlab ...@@ -133,7 +133,7 @@ module Gitlab
def id_for_github_email(email) def id_for_github_email(email)
gitlab_id = query_id_for_github_email(email) || nil 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 end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
...@@ -155,7 +155,7 @@ module Gitlab ...@@ -155,7 +155,7 @@ module Gitlab
# 1. A boolean indicating if the key was present or not. # 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. # 2. The ID as an Integer, or nil in case no ID could be found.
def read_id_from_cache(key) def read_id_from_cache(key)
value = Caching.read(key) value = Gitlab::Cache::Import::Caching.read(key)
exists = !value.nil? exists = !value.nil?
number = value.to_i number = value.to_i
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
module Gitlab module Gitlab
class UsageData class UsageData
APPROXIMATE_COUNT_MODELS = [Label, MergeRequest, Note, Todo].freeze
BATCH_SIZE = 100 BATCH_SIZE = 100
class << self class << self
...@@ -107,10 +106,12 @@ module Gitlab ...@@ -107,10 +106,12 @@ module Gitlab
suggestions: count(Suggestion), suggestions: count(Suggestion),
todos: count(Todo), todos: count(Todo),
uploads: count(Upload), uploads: count(Upload),
web_hooks: count(WebHook) web_hooks: count(WebHook),
labels: count(Label),
merge_requests: count(MergeRequest),
notes: count(Note)
}.merge( }.merge(
services_usage, services_usage,
approximate_counts,
usage_counters, usage_counters,
user_preferences_usage, user_preferences_usage,
ingress_modsecurity_usage ingress_modsecurity_usage
...@@ -251,16 +252,6 @@ module Gitlab ...@@ -251,16 +252,6 @@ module Gitlab
fallback fallback
end 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 def installation_type
if Rails.env.production? if Rails.env.production?
Gitlab::INSTALLATION_TYPE Gitlab::INSTALLATION_TYPE
......
...@@ -1948,6 +1948,9 @@ msgstr "" ...@@ -1948,6 +1948,9 @@ msgstr ""
msgid "An error occurred while parsing recent searches" msgid "An error occurred while parsing recent searches"
msgstr "" msgstr ""
msgid "An error occurred while parsing the file."
msgstr ""
msgid "An error occurred while removing epics." msgid "An error occurred while removing epics."
msgstr "" msgstr ""
......
...@@ -308,6 +308,48 @@ describe 'File blob', :js do ...@@ -308,6 +308,48 @@ describe 'File blob', :js do
end end
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 'ISO file (stored in LFS)' do
context 'when LFS is enabled on the project' do context 'when LFS is enabled on the project' do
before do before do
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe JobsFinder, '#execute' do describe Ci::JobsFinder, '#execute' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:user, :admin) } let_it_be(:admin) { create(:user, :admin) }
let_it_be(:project) { create(:project, :private, public_builds: false) } let_it_be(:project) { create(:project, :private, public_builds: false) }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe PipelineSchedulesFinder do describe Ci::PipelineSchedulesFinder do
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) } let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe PipelinesFinder do describe Ci::PipelinesFinder do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:current_user) { nil } let(:current_user) { nil }
let(:params) { {} } let(:params) { {} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe RunnerJobsFinder do describe Ci::RunnerJobsFinder do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :instance) } let(:runner) { create(:ci_runner, :instance) }
......
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.',
);
});
});
});
<div class="file-content" data-endpoint="/test" id="js-notebook-viewer"></div>
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.',
);
});
});
});
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' 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 describe '.read' do
it 'reads a value from the cache' do it 'reads a value from the cache' do
described_class.write('foo', 'bar') described_class.write('foo', 'bar')
......
...@@ -30,7 +30,7 @@ describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do ...@@ -30,7 +30,7 @@ describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
describe '#cache_database_id' do describe '#cache_database_id' do
it 'caches the ID of a database row' do it 'caches the ID of a database row' do
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.with('github-import/issuable-finder/4/MergeRequest/1', 10) .with('github-import/issuable-finder/4/MergeRequest/1', 10)
......
...@@ -21,7 +21,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do ...@@ -21,7 +21,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
it 'returns nil for an empty cache key' do it 'returns nil for an empty cache key' do
key = finder.cache_key_for(bug.name) 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 expect(finder.id_for(bug.name)).to be_nil
end end
...@@ -40,7 +40,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do ...@@ -40,7 +40,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
describe '#build_cache' do describe '#build_cache' do
it 'builds the cache of all project labels' do it 'builds the cache of all project labels' do
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write_multiple) .to receive(:write_multiple)
.with( .with(
{ {
......
...@@ -22,7 +22,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do ...@@ -22,7 +22,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
it 'returns nil for an empty cache key' do it 'returns nil for an empty cache key' do
key = finder.cache_key_for(milestone.iid) 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 expect(finder.id_for(issuable)).to be_nil
end end
...@@ -41,7 +41,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do ...@@ -41,7 +41,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
describe '#build_cache' do describe '#build_cache' do
it 'builds the cache of all project milestones' do it 'builds the cache of all project milestones' do
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write_multiple) .to receive(:write_multiple)
.with("github-import/milestone-finder/#{project.id}/1" => milestone.id) .with("github-import/milestone-finder/#{project.id}/1" => milestone.id)
.and_call_original .and_call_original
......
...@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do ...@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do
end end
it 'sets the initial page number to the cached value when one is present' do 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) expect(described_class.new(project, :issues).current).to eq(2)
end end
......
...@@ -57,7 +57,7 @@ describe Gitlab::GithubImport::ParallelScheduling do ...@@ -57,7 +57,7 @@ describe Gitlab::GithubImport::ParallelScheduling do
expect(importer).to receive(:parallel_import) expect(importer).to receive(:parallel_import)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:expire) .to receive(:expire)
.with(importer.already_imported_cache_key, a_kind_of(Numeric)) .with(importer.already_imported_cache_key, a_kind_of(Numeric))
...@@ -287,7 +287,7 @@ describe Gitlab::GithubImport::ParallelScheduling do ...@@ -287,7 +287,7 @@ describe Gitlab::GithubImport::ParallelScheduling do
.with(object) .with(object)
.and_return(object.id) .and_return(object.id)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:set_add) .to receive(:set_add)
.with(importer.already_imported_cache_key, object.id) .with(importer.already_imported_cache_key, object.id)
.and_call_original .and_call_original
......
...@@ -162,7 +162,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -162,7 +162,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
context 'when an Email address is cached' do context 'when an Email address is cached' do
it 'reads the Email address from the cache' do it 'reads the Email address from the cache' do
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:read) .to receive(:read)
.and_return(email) .and_return(email)
...@@ -182,7 +182,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -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 it 'caches the Email address when an Email address is available' do
expect(client).to receive(:user).with('kittens').and_return(user) expect(client).to receive(:user).with('kittens').and_return(user)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.with(an_instance_of(String), email) .with(an_instance_of(String), email)
...@@ -195,7 +195,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -195,7 +195,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with('kittens') .with('kittens')
.and_return(nil) .and_return(nil)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.not_to receive(:write) .not_to receive(:write)
expect(finder.email_for_github_username('kittens')).to be_nil expect(finder.email_for_github_username('kittens')).to be_nil
...@@ -207,7 +207,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -207,7 +207,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:id) { 4 } let(:id) { 4 }
it 'reads a user ID from the cache' do it 'reads a user ID from the cache' do
Gitlab::GithubImport::Caching Gitlab::Cache::Import::Caching
.write(described_class::ID_CACHE_KEY % id, 4) .write(described_class::ID_CACHE_KEY % id, 4)
expect(finder.cached_id_for_github_id(id)).to eq([true, 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 ...@@ -222,7 +222,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:email) { 'kittens@example.com' } let(:email) { 'kittens@example.com' }
it 'reads a user ID from the cache' do 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) .write(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 4)
expect(finder.cached_id_for_github_email(email)).to eq([true, 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 ...@@ -241,7 +241,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(id) .with(id)
.and_return(42) .and_return(42)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.with(described_class::ID_CACHE_KEY % id, 42) .with(described_class::ID_CACHE_KEY % id, 42)
...@@ -253,7 +253,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -253,7 +253,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(id) .with(id)
.and_return(nil) .and_return(nil)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.with(described_class::ID_CACHE_KEY % id, nil) .with(described_class::ID_CACHE_KEY % id, nil)
...@@ -269,7 +269,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -269,7 +269,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(email) .with(email)
.and_return(42) .and_return(42)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 42) .with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 42)
...@@ -281,7 +281,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -281,7 +281,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(email) .with(email)
.and_return(nil) .and_return(nil)
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, nil) .with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, nil)
...@@ -317,13 +317,13 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do ...@@ -317,13 +317,13 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
describe '#read_id_from_cache' do describe '#read_id_from_cache' do
it 'reads an ID from the 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]) expect(finder.read_id_from_cache('foo')).to eq([true, 10])
end end
it 'reads a cache key with an empty value' do 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]) expect(finder.read_id_from_cache('foo')).to eq([true, nil])
end end
......
...@@ -35,7 +35,7 @@ describe Gitlab::GithubImport do ...@@ -35,7 +35,7 @@ describe Gitlab::GithubImport do
end end
it 'caches the ghost user ID' do it 'caches the ghost user ID' do
expect(Gitlab::GithubImport::Caching) expect(Gitlab::Cache::Import::Caching)
.to receive(:write) .to receive(:write)
.once .once
.and_call_original .and_call_original
......
...@@ -387,29 +387,6 @@ describe Gitlab::UsageData do ...@@ -387,29 +387,6 @@ describe Gitlab::UsageData do
expect(described_class.count(relation, fallback: 15, batch: false)).to eq(15) expect(described_class.count(relation, fallback: 15, batch: false)).to eq(15)
end end
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 end
end end
...@@ -832,6 +832,13 @@ describe API::Users, :do_not_mock_admin_mode do ...@@ -832,6 +832,13 @@ describe API::Users, :do_not_mock_admin_mode do
expect(user.reload.private_profile).to eq(false) expect(user.reload.private_profile).to eq(false)
end 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 it "updates private profile" do
put api("/users/#{user.id}", admin), params: { private_profile: true } put api("/users/#{user.id}", admin), params: { private_profile: true }
...@@ -857,6 +864,19 @@ describe API::Users, :do_not_mock_admin_mode do ...@@ -857,6 +864,19 @@ describe API::Users, :do_not_mock_admin_mode do
expect(user.reload.private_profile).to eq(true) expect(user.reload.private_profile).to eq(true)
end 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 it "does not update admin status" do
put api("/users/#{admin_user.id}", admin), params: { can_create_group: false } put api("/users/#{admin_user.id}", admin), params: { can_create_group: false }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment