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
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
......
......@@ -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)
......
/* 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>
`,
});
};
<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 @@
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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
# 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
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
......@@ -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
......
......@@ -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)
......
---
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
| `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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
# 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
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
# 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
#
# 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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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 ""
......
......@@ -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
......
......@@ -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) }
......
......@@ -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) }
......
......@@ -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) { {} }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe RunnerJobsFinder do
describe Ci::RunnerJobsFinder do
let(:project) { create(:project) }
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 @@
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')
......
......@@ -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)
......
......@@ -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(
{
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -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 }
......
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