Commit b2421171 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent b160a974252b
......@@ -75,18 +75,18 @@
changes: *code-backstage-qa-patterns
when: on_success
.rails:rules:master-refs-code-backstage-qa:
.rails:rules:master-refs-code-backstage:
rules:
- <<: *if-master-refs
changes: *code-backstage-qa-patterns
changes: *code-backstage-patterns
when: on_success
.rails:rules:master-refs-code-backstage-qa-ee-only:
.rails:rules:master-refs-code-backstage-ee-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-master-refs
changes: *code-backstage-qa-patterns
changes: *code-backstage-patterns
when: on_success
.rails:rules:ee-only:
......@@ -332,12 +332,12 @@ coverage:
rspec quarantine pg9:
extends:
- .rspec-base-quarantine
- .rails:rules:master-refs-code-backstage-qa
- .rails:rules:master-refs-code-backstage
.rspec-base-pg10:
extends:
- .rspec-base
- .rails:rules:master-refs-code-backstage-qa
- .rails:rules:master-refs-code-backstage
- .use-pg10
rspec unit pg10:
......@@ -359,7 +359,7 @@ rspec system pg10:
rspec-ee quarantine pg9:
extends:
- .rspec-base-quarantine
- .rails:rules:master-refs-code-backstage-qa-ee-only
- .rails:rules:master-refs-code-backstage-ee-only
variables:
RSPEC_OPTS: "--tag quarantine -- ee/spec/"
......@@ -367,25 +367,25 @@ rspec-ee migration pg10:
extends:
- .rspec-ee-base-pg10
- .rspec-base-migration
- .rails:rules:master-refs-code-backstage-qa
- .rails:rules:master-refs-code-backstage
parallel: 2
rspec-ee unit pg10:
extends:
- .rspec-ee-base-pg10
- .rails:rules:master-refs-code-backstage-qa
- .rails:rules:master-refs-code-backstage
parallel: 10
rspec-ee integration pg10:
extends:
- .rspec-ee-base-pg10
- .rails:rules:master-refs-code-backstage-qa
- .rails:rules:master-refs-code-backstage
parallel: 3
rspec-ee system pg10:
extends:
- .rspec-ee-base-pg10
- .rails:rules:master-refs-code-backstage-qa
- .rails:rules:master-refs-code-backstage
parallel: 5
# ee + master-only jobs #
#########################
......
import initRegistryImages from '~/registry/list';
import initRegistryImages from '~/registry/list/index';
import registryExplorer from '~/registry/explorer/index';
document.addEventListener('DOMContentLoaded', initRegistryImages);
document.addEventListener('DOMContentLoaded', () => {
initRegistryImages();
registryExplorer();
});
import initRegistryImages from '~/registry/list/index';
import registryExplorer from '~/registry/explorer/index';
document.addEventListener('DOMContentLoaded', initRegistryImages);
document.addEventListener('DOMContentLoaded', () => {
initRegistryImages();
registryExplorer();
});
import { __ } from '~/locale';
export const FETCH_IMAGES_LIST_ERROR_MESSAGE = __(
'Something went wrong while fetching the packages list.',
);
export const FETCH_TAGS_LIST_ERROR_MESSAGE = __(
'Something went wrong while fetching the tags list.',
);
export const DELETE_IMAGE_ERROR_MESSAGE = __('Something went wrong while deleting the image.');
export const DELETE_IMAGE_SUCCESS_MESSAGE = __('Image deleted successfully');
export const DELETE_TAG_ERROR_MESSAGE = __('Something went wrong while deleting the tag.');
export const DELETE_TAG_SUCCESS_MESSAGE = __('Tag deleted successfully');
export const DELETE_TAGS_ERROR_MESSAGE = __('Something went wrong while deleting the tags.');
export const DELETE_TAGS_SUCCESS_MESSAGE = __('Tags deleted successfully');
export const DEFAULT_PAGE = 1;
export const DEFAULT_PAGE_SIZE = 10;
export const GROUP_PAGE_TYPE = 'groups';
export const LIST_KEY_TAG = 'name';
export const LIST_KEY_IMAGE_ID = 'short_revision';
export const LIST_KEY_SIZE = 'total_size';
export const LIST_KEY_LAST_UPDATED = 'created_at';
export const LIST_KEY_ACTIONS = 'actions';
export const LIST_KEY_CHECKBOX = 'checkbox';
export const LIST_LABEL_TAG = __('Tag');
export const LIST_LABEL_IMAGE_ID = __('Image ID');
export const LIST_LABEL_SIZE = __('Size');
export const LIST_LABEL_LAST_UPDATED = __('Last Updated');
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import RegistryExplorer from './pages/index.vue';
import { createStore } from './stores';
import createRouter from './router';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-container-registry');
if (!el) {
return null;
}
const { endpoint } = el.dataset;
const store = createStore();
const router = createRouter(endpoint, store);
store.dispatch('setInitialState', el.dataset);
return new Vue({
el,
store,
router,
components: {
RegistryExplorer,
},
render(createElement) {
return createElement('registry-explorer');
},
});
};
<script>
export default {};
</script>
<template>
<div></div>
</template>
<script>
export default {};
</script>
<template>
<div class="position-relative">
<transition name="slide">
<router-view />
</transition>
</div>
</template>
<script>
export default {};
</script>
<template>
<div></div>
</template>
import Vue from 'vue';
import VueRouter from 'vue-router';
import { __ } from '~/locale';
import List from './pages/list.vue';
import Details from './pages/details.vue';
Vue.use(VueRouter);
export default function createRouter(base, store) {
const router = new VueRouter({
base,
mode: 'history',
routes: [
{
name: 'list',
path: '/',
component: List,
meta: {
name: __('Container Registry'),
},
beforeEnter: (to, from, next) => {
store.dispatch('requestImagesList');
next();
},
},
{
name: 'details',
path: '/:id',
component: Details,
meta: {
name: __('Tags'),
},
beforeEnter: (to, from, next) => {
store.dispatch('requestTagsList', { id: to.params.id });
next();
},
},
],
});
return router;
}
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import * as types from './mutation_types';
import {
FETCH_IMAGES_LIST_ERROR_MESSAGE,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
FETCH_TAGS_LIST_ERROR_MESSAGE,
DELETE_TAG_SUCCESS_MESSAGE,
DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
DELETE_IMAGE_SUCCESS_MESSAGE,
} from '../constants';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
export const receiveImagesListSuccess = ({ commit }, { data, headers }) => {
commit(types.SET_IMAGES_LIST_SUCCESS, data);
commit(types.SET_PAGINATION, headers);
};
export const receiveTagsListSuccess = ({ commit }, { data, headers }) => {
commit(types.SET_TAGS_LIST_SUCCESS, data);
commit(types.SET_TAGS_PAGINATION, headers);
};
export const requestImagesList = ({ commit, dispatch, state }, pagination = {}) => {
commit(types.SET_MAIN_LOADING, true);
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
return axios
.get(state.config.endpoint, { params: { page, per_page: perPage } })
.then(({ data, headers }) => {
dispatch('receiveImagesListSuccess', { data, headers });
})
.catch(() => {
createFlash(FETCH_IMAGES_LIST_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestTagsList = ({ commit, dispatch }, { pagination = {}, id }) => {
commit(types.SET_MAIN_LOADING, true);
const url = window.atob(id);
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
return axios
.get(url, { params: { page, per_page: perPage } })
.then(({ data, headers }) => {
dispatch('receiveTagsListSuccess', { data, headers });
})
.catch(() => {
createFlash(FETCH_TAGS_LIST_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTag = ({ commit, dispatch, state }, { tag, imageId }) => {
commit(types.SET_MAIN_LOADING, true);
return axios
.delete(tag.destroy_path)
.then(() => {
createFlash(DELETE_TAG_SUCCESS_MESSAGE, 'success');
dispatch('requestTagsList', { pagination: state.tagsPagination, id: imageId });
})
.catch(() => {
createFlash(DELETE_TAG_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTags = ({ commit, dispatch, state }, { ids, imageId }) => {
commit(types.SET_MAIN_LOADING, true);
const url = `/${state.config.projectPath}/registry/repository/${imageId}/tags/bulk_destroy`;
return axios
.delete(url, { params: { ids } })
.then(() => {
createFlash(DELETE_TAGS_SUCCESS_MESSAGE, 'success');
dispatch('requestTagsList', { pagination: state.tagsPagination, id: imageId });
})
.catch(() => {
createFlash(DELETE_TAGS_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteImage = ({ commit, dispatch, state }, destroyPath) => {
commit(types.SET_MAIN_LOADING, true);
return axios
.delete(destroyPath)
.then(() => {
dispatch('requestImagesList', { pagination: state.pagination });
createFlash(DELETE_IMAGE_SUCCESS_MESSAGE, 'success');
})
.catch(() => {
createFlash(DELETE_IMAGE_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
state,
actions,
mutations,
});
export default createStore();
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const SET_IMAGES_LIST_SUCCESS = 'SET_PACKAGE_LIST_SUCCESS';
export const SET_PAGINATION = 'SET_PAGINATION';
export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION';
export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS';
import * as types from './mutation_types';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
export default {
[types.SET_INITIAL_STATE](state, config) {
state.config = {
...config,
};
},
[types.SET_IMAGES_LIST_SUCCESS](state, images) {
state.images = images;
},
[types.SET_TAGS_LIST_SUCCESS](state, tags) {
state.tags = tags;
},
[types.SET_MAIN_LOADING](state, isLoading) {
state.isLoading = isLoading;
},
[types.SET_PAGINATION](state, headers) {
const normalizedHeaders = normalizeHeaders(headers);
state.pagination = parseIntPagination(normalizedHeaders);
},
[types.SET_TAGS_PAGINATION](state, headers) {
const normalizedHeaders = normalizeHeaders(headers);
state.tagsPagination = parseIntPagination(normalizedHeaders);
},
};
export default () => ({
isLoading: false,
config: {},
images: [],
tags: [],
pagination: {},
tagsPagination: {},
});
......@@ -4,14 +4,20 @@ import Translate from '~/vue_shared/translate';
Vue.use(Translate);
export default () =>
new Vue({
el: '#js-vue-registry-images',
export default () => {
const el = document.getElementById('js-vue-registry-images');
if (!el) {
return null;
}
return new Vue({
el,
components: {
registryApp,
},
data() {
const { dataset } = document.querySelector(this.$options.el);
const { dataset } = el;
return {
registryData: {
endpoint: dataset.endpoint,
......@@ -35,3 +41,4 @@ export default () =>
});
},
});
};
import { joinPaths } from '~/lib/utils/url_utility';
export const updateElementsVisibility = (selector, isVisible) => {
document.querySelectorAll(selector).forEach(elem => elem.classList.toggle('hidden', !isVisible));
};
......@@ -6,6 +8,6 @@ export const updateFormAction = (selector, basePath, path) => {
const form = document.querySelector(selector);
if (form) {
form.action = `${basePath}${path}`;
form.action = joinPaths(basePath, path);
}
};
......@@ -24,7 +24,7 @@ def destroy
def mark_as_ham
spam_log = SpamLog.find(params[:id])
if Spam::HamService.new(spam_log).mark_as_ham!
if Spam::HamService.new(spam_log).execute
redirect_to admin_spam_logs_path, notice: _('Spam log successfully submitted as ham.')
else
redirect_to admin_spam_logs_path, alert: _('Error with Akismet. Please check the logs for more info.')
......
......@@ -23,6 +23,7 @@ class Build < Ci::Processable
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
belongs_to :resource_group, class_name: 'Ci::ResourceGroup', inverse_of: :builds
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
RUNNER_FEATURES = {
upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? },
......
......@@ -28,7 +28,7 @@ class JobArtifact < ApplicationRecord
license_scanning: 'gl-license-scanning-report.json',
performance: 'performance.json',
metrics: 'metrics.txt',
lsif: 'lsif.sqlite3'
lsif: 'lsif.json'
}.freeze
INTERNAL_TYPES = {
......@@ -74,6 +74,7 @@ class JobArtifact < ApplicationRecord
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) }
scope :for_sha, ->(sha) { joins(job: :pipeline).where(ci_pipelines: { sha: sha }) }
scope :with_file_types, -> (file_types) do
types = self.file_types.select { |file_type| file_types.include?(file_type) }.values
......@@ -117,7 +118,7 @@ class JobArtifact < ApplicationRecord
metrics: 12, ## EE-specific
metrics_referee: 13, ## runner referees
network_referee: 14, ## runner referees
lsif: 15 # LSIF dump for code navigation
lsif: 15 # LSIF data for code navigation
}
enum file_format: {
......
......@@ -6,23 +6,22 @@ module ReactiveCaching
extend ActiveSupport::Concern
InvalidateReactiveCache = Class.new(StandardError)
ExceededReactiveCacheLimit = Class.new(StandardError)
included do
class_attribute :reactive_cache_lease_timeout
class_attribute :reactive_cache_key
class_attribute :reactive_cache_lifetime
class_attribute :reactive_cache_lease_timeout
class_attribute :reactive_cache_refresh_interval
class_attribute :reactive_cache_lifetime
class_attribute :reactive_cache_hard_limit
class_attribute :reactive_cache_worker_finder
# defaults
self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
self.reactive_cache_lease_timeout = 2.minutes
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes
self.reactive_cache_hard_limit = 1.megabyte
self.reactive_cache_worker_finder = ->(id, *_args) do
find_by(primary_key => id)
end
......@@ -71,6 +70,8 @@ def exclusively_update_reactive_cache!(*args)
if within_reactive_cache_lifetime?(*args)
enqueuing_update(*args) do
new_value = calculate_reactive_cache(*args)
check_exceeded_reactive_cache_limit!(new_value)
old_value = Rails.cache.read(key)
Rails.cache.write(key, new_value)
reactive_cache_updated(*args) if new_value != old_value
......@@ -121,5 +122,13 @@ def enqueuing_update(*args)
ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args)
end
def check_exceeded_reactive_cache_limit!(data)
return unless Feature.enabled?(:reactive_cache_limit)
data_deep_size = Gitlab::Utils::DeepSize.new(data, max_size: self.class.reactive_cache_hard_limit)
raise ExceededReactiveCacheLimit.new unless data_deep_size.valid?
end
end
end
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