diff options
264 files changed, 1579 insertions, 396 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index afe9da08495..c7ef75d1c32 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -447,9 +447,8 @@ danger-review: - retry gem install danger --no-ri --no-rdoc cache: {} only: - refs: - - branches@gitlab-org/gitlab-ce - - branches@gitlab-org/gitlab-ee + variables: + - $DANGER_GITLAB_API_TOKEN except: refs: - master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 631f80c6bd9..ad8022e972f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -376,8 +376,14 @@ on those issues. Please select someone with relevant experience from the [GitLab team][team]. If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. +We also use [GitLab Triage] to automate some triaging policies. This is +currently setup as a [scheduled pipeline] running on the [`gl-triage`] branch. + [described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/ [issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815 +[GitLab Triage]: https://gitlab.com/gitlab-org/gitlab-triage +[scheduled pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipeline_schedules/3732/edit +[`gl-triage`]: https://gitlab.com/gitlab-org/gitlab-ce/tree/gl-triage ### Feature proposals diff --git a/Dangerfile b/Dangerfile index 84b72673c50..9217610da8b 100644 --- a/Dangerfile +++ b/Dangerfile @@ -4,3 +4,4 @@ danger.import_dangerfile(path: 'danger/changelog') danger.import_dangerfile(path: 'danger/specs') danger.import_dangerfile(path: 'danger/gemfile') danger.import_dangerfile(path: 'danger/database') +danger.import_dangerfile(path: 'danger/frozen_string') @@ -233,7 +233,7 @@ gem 'ruby-fogbugz', '~> 0.2.1' gem 'kubeclient', '~> 3.1.0' # Sanitize user input -gem 'sanitize', '~> 4.6.5' +gem 'sanitize', '~> 4.6' gem 'babosa', '~> 1.0.2' # Sanitizes SVG input diff --git a/Gemfile.lock b/Gemfile.lock index 0976169bb11..22626c0071b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -514,7 +514,7 @@ GEM net-ldap (0.16.0) net-ssh (5.0.1) netrc (0.11.0) - nokogiri (1.8.3) + nokogiri (1.8.4) mini_portile2 (~> 2.3.0) nokogumbo (1.5.0) nokogiri @@ -808,7 +808,7 @@ GEM et-orbi (~> 1.0) rugged (0.27.2) safe_yaml (1.0.4) - sanitize (4.6.5) + sanitize (4.6.6) crass (~> 1.0.2) nokogiri (>= 1.4.4) nokogumbo (~> 1.4) @@ -1157,7 +1157,7 @@ DEPENDENCIES ruby_parser (~> 3.8) rufus-scheduler (~> 3.4) rugged (~> 0.27) - sanitize (~> 4.6.5) + sanitize (~> 4.6) sass-rails (~> 5.0.6) scss_lint (~> 0.56.0) seed-fu (~> 2.3.7) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 1cf612fd4a6..5a0aaf05608 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -1168,7 +1168,7 @@ DEPENDENCIES ruby_parser (~> 3.8) rufus-scheduler (~> 3.4) rugged (~> 0.27) - sanitize (~> 4.6.5) + sanitize (~> 4.6) sass-rails (~> 5.0.6) scss_lint (~> 0.56.0) seed-fu (~> 2.3.7) diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js index 029fd6a67d4..efba6fc1aff 100644 --- a/app/assets/javascripts/gpg_badges.js +++ b/app/assets/javascripts/gpg_badges.js @@ -1,23 +1,36 @@ import $ from 'jquery'; import { parseQueryStringIntoObject } from '~/lib/utils/common_utils'; import axios from '~/lib/utils/axios_utils'; -import flash from '~/flash'; +import createFlash from '~/flash'; import { __ } from '~/locale'; export default class GpgBadges { static fetch() { - const badges = $('.js-loading-gpg-badge'); const tag = $('.js-signature-container'); + if (tag.length === 0) { + return Promise.resolve(); + } + + const badges = $('.js-loading-gpg-badge'); badges.html('<i class="fa fa-spinner fa-spin"></i>'); + const displayError = () => createFlash(__('An error occurred while loading commit signatures')); + + const endpoint = tag.data('signaturesPath'); + if (!endpoint) { + displayError(); + return Promise.reject(new Error('Missing commit signatures endpoint!')); + } + const params = parseQueryStringIntoObject(tag.serialize()); - return axios.get(tag.data('signaturesPath'), { params }) - .then(({ data }) => { - data.signatures.forEach((signature) => { - badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html); - }); - }) - .catch(() => flash(__('An error occurred while loading commits'))); + return axios + .get(endpoint, { params }) + .then(({ data }) => { + data.signatures.forEach(signature => { + badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html); + }); + }) + .catch(displayError); } } diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 6b7550efff8..2f3dd6f6cbc 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -541,6 +541,26 @@ export const addSelectOnFocusBehaviour = (selector = '.js-select-on-focus') => { }); }; +/** + * Method to round of values with decimal places + * with provided precision. + * + * Taken from https://stackoverflow.com/a/7343013/414749 + * + * Eg; roundOffFloat(3.141592, 3) = 3.142 + * + * Refer to spec/javascripts/lib/utils/common_utils_spec.js for + * more supported examples. + * + * @param {Float} number + * @param {Number} precision + */ +export const roundOffFloat = (number, precision = 0) => { + // eslint-disable-next-line no-restricted-properties + const multiplier = Math.pow(10, precision); + return Math.round(number * multiplier) / multiplier; +}; + window.gl = window.gl || {}; window.gl.utils = { ...(window.gl.utils || {}), diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index 7fca80c2fdb..91d8c30744f 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -38,7 +38,7 @@ import { normalizeHeaders } from './common_utils'; * } else { * poll.stop(); * } -* }); + * }); * * 1. Checks for response and headers before start polling * 2. Interval is provided by `Poll-Interval` header. @@ -51,8 +51,8 @@ export default class Poll { constructor(options = {}) { this.options = options; this.options.data = options.data || {}; - this.options.notificationCallback = options.notificationCallback || - function notificationCallback() {}; + this.options.notificationCallback = + options.notificationCallback || function notificationCallback() {}; this.intervalHeader = 'POLL-INTERVAL'; this.timeoutID = null; @@ -63,6 +63,7 @@ export default class Poll { const headers = normalizeHeaders(response.headers); const pollInterval = parseInt(headers[this.intervalHeader], 10); if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) { + clearTimeout(this.timeoutID); this.timeoutID = setTimeout(() => { this.makeRequest(); }, pollInterval); @@ -77,11 +78,11 @@ export default class Poll { notificationCallback(true); return resource[method](data) - .then((response) => { + .then(response => { this.checkConditions(response); notificationCallback(false); }) - .catch((error) => { + .catch(error => { notificationCallback(false); if (error.status === httpStatusCodes.ABORTED) { return; diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 6dd4c9d66ac..3aef30c608c 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -15,7 +15,7 @@ document.addEventListener('DOMContentLoaded', () => { const notesDataset = document.getElementById('js-vue-notes').dataset; const parsedUserData = JSON.parse(notesDataset.currentUserData); const noteableData = JSON.parse(notesDataset.noteableData); - const { markdownVersion } = notesDataset; + const markdownVersion = parseInt(notesDataset.markdownVersion, 10); let currentUserData = {}; noteableData.noteableType = notesDataset.noteableType; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index ab6a95e2601..e1b159142c9 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -174,27 +174,19 @@ export default { [types.UPDATE_NOTE](state, note) { const noteObj = utils.findNoteObjectById(state.discussions, note.discussion_id); - if (noteObj.individual_note) { noteObj.notes.splice(0, 1, note); } else { const comment = utils.findNoteObjectById(noteObj.notes, note.id); - noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note); + Object.assign(comment, note); } }, [types.UPDATE_DISCUSSION](state, noteData) { const note = noteData; - let index = 0; - - state.discussions.forEach((n, i) => { - if (n.id === note.id) { - index = i; - } - }); - + const selectedDiscussion = state.discussions.find(n => n.id === note.id); note.expanded = true; // override expand flag to prevent collapse - state.discussions.splice(index, 1, note); + Object.assign(selectedDiscussion, note); }, [types.CLOSE_ISSUE](state) { @@ -215,12 +207,9 @@ export default { [types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) { const discussion = utils.findNoteObjectById(state.discussions, discussionId); - const index = state.discussions.indexOf(discussion); - const discussionWithDiffLines = Object.assign({}, discussion, { + Object.assign(discussion, { truncated_diff_lines: diffLines, }); - - state.discussions.splice(index, 1, discussionWithDiffLines); }, }; diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index a0e096ebfaf..c4a812c5af4 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -2,13 +2,11 @@ import AjaxCache from '~/lib/utils/ajax_cache'; const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; -export const findNoteObjectById = (notes, id) => - notes.filter(n => n.id === id)[0]; +export const findNoteObjectById = (notes, id) => notes.find(n => n.id === id); export const getQuickActionText = note => { let text = 'Applying command'; - const quickActions = - AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || []; + const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || []; const executedCommands = quickActions.filter(command => { const commandRegex = new RegExp(`/${command.name}`); @@ -29,5 +27,4 @@ export const getQuickActionText = note => { export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note); -export const stripQuickActions = note => - note.replace(REGEX_QUICK_ACTIONS, '').trim(); +export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim(); diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index 85c6862d629..84e5bb3c46e 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import BlobViewer from '~/blob/viewer/index'; import initBlob from '~/pages/projects/init_blob'; +import GpgBadges from '~/gpg_badges'; document.addEventListener('DOMContentLoaded', () => { new BlobViewer(); // eslint-disable-line no-new @@ -26,4 +27,6 @@ document.addEventListener('DOMContentLoaded', () => { }, }); } + + GpgBadges.fetch(); }); diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 3b0f0f960b8..d2dc0c4570e 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -7,6 +7,7 @@ import TreeView from '~/tree'; import BlobViewer from '~/blob/viewer/index'; import Activities from '~/activities'; import { ajaxGet } from '~/lib/utils/common_utils'; +import GpgBadges from '~/gpg_badges'; import Star from '../../../star'; import notificationsDropdown from '../../../notifications_dropdown'; @@ -38,4 +39,6 @@ document.addEventListener('DOMContentLoaded', () => { $(treeSlider).waitForImages(() => { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); + + GpgBadges.fetch(); }); diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index 7ad082a5e61..33d69d891d8 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Vue from 'vue'; import initBlob from '~/blob_edit/blob_bundle'; import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; +import GpgBadges from '~/gpg_badges'; import TreeView from '../../../../tree'; import ShortcutsNavigation from '../../../../shortcuts_navigation'; import BlobViewer from '../../../../blob/viewer'; @@ -14,7 +15,8 @@ document.addEventListener('DOMContentLoaded', () => { new BlobViewer(); // eslint-disable-line no-new new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new $('#tree-slider').waitForImages(() => - ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath)); + ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath), + ); initBlob(); const commitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status'); @@ -36,4 +38,6 @@ document.addEventListener('DOMContentLoaded', () => { }, }); } + + GpgBadges.fetch(); }); diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index a4c7c143e56..1c1e17563a1 100644 --- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -1,27 +1,27 @@ <script> - import Visibility from 'visibilityjs'; - import ciIcon from '~/vue_shared/components/ci_icon.vue'; - import loadingIcon from '~/vue_shared/components/loading_icon.vue'; - import Poll from '~/lib/utils/poll'; - import Flash from '~/flash'; - import { s__, sprintf } from '~/locale'; - import tooltip from '~/vue_shared/directives/tooltip'; - import CommitPipelineService from '../services/commit_pipeline_service'; +import Visibility from 'visibilityjs'; +import ciIcon from '~/vue_shared/components/ci_icon.vue'; +import loadingIcon from '~/vue_shared/components/loading_icon.vue'; +import Poll from '~/lib/utils/poll'; +import Flash from '~/flash'; +import { s__, sprintf } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import CommitPipelineService from '../services/commit_pipeline_service'; - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + components: { + ciIcon, + loadingIcon, + }, + props: { + endpoint: { + type: String, + required: true, }, - components: { - ciIcon, - loadingIcon, - }, - props: { - endpoint: { - type: String, - required: true, - }, - /* This prop can be used to replace some of the `render_commit_status` + /* This prop can be used to replace some of the `render_commit_status` used across GitLab, this way we could use this vue component and add a realtime status where it makes sense realtime: { @@ -29,76 +29,77 @@ required: false, default: true, }, */ + }, + data() { + return { + ciStatus: {}, + isLoading: true, + }; + }, + computed: { + statusTitle() { + return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text }); }, - data() { - return { - ciStatus: {}, - isLoading: true, - }; - }, - computed: { - statusTitle() { - return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text }); - }, + }, + mounted() { + this.service = new CommitPipelineService(this.endpoint); + this.initPolling(); + }, + methods: { + successCallback(res) { + const { pipelines } = res.data; + if (pipelines.length > 0) { + // The pipeline entity always keeps the latest pipeline info on the `details.status` + this.ciStatus = pipelines[0].details.status; + } + this.isLoading = false; }, - mounted() { - this.service = new CommitPipelineService(this.endpoint); - this.initPolling(); + errorCallback() { + this.ciStatus = { + text: 'not found', + icon: 'status_notfound', + group: 'notfound', + }; + this.isLoading = false; + Flash(s__('Something went wrong on our end')); }, - methods: { - successCallback(res) { - const { pipelines } = res.data; - if (pipelines.length > 0) { - // The pipeline entity always keeps the latest pipeline info on the `details.status` - this.ciStatus = pipelines[0].details.status; - } - this.isLoading = false; - }, - errorCallback() { - this.ciStatus = { - text: 'not found', - icon: 'status_notfound', - group: 'notfound', - }; - this.isLoading = false; - Flash(s__('Something went wrong on our end')); - }, - initPolling() { - this.poll = new Poll({ - resource: this.service, - method: 'fetchData', - successCallback: response => this.successCallback(response), - errorCallback: this.errorCallback, - }); + initPolling() { + this.poll = new Poll({ + resource: this.service, + method: 'fetchData', + successCallback: response => this.successCallback(response), + errorCallback: this.errorCallback, + }); + + if (!Visibility.hidden()) { + this.isLoading = true; + this.poll.makeRequest(); + } else { + this.fetchPipelineCommitData(); + } + Visibility.change(() => { if (!Visibility.hidden()) { - this.isLoading = true; - this.poll.makeRequest(); + this.poll.restart(); } else { - this.fetchPipelineCommitData(); + this.poll.stop(); } - - Visibility.change(() => { - if (!Visibility.hidden()) { - this.poll.restart(); - } else { - this.poll.stop(); - } - }); - }, - fetchPipelineCommitData() { - this.service.fetchData() - .then(this.successCallback) - .catch(this.errorCallback); - }, + }); }, - destroy() { - this.poll.stop(); + fetchPipelineCommitData() { + this.service + .fetchData() + .then(this.successCallback) + .catch(this.errorCallback); }, - }; + }, + destroy() { + this.poll.stop(); + }, +}; </script> <template> - <div> + <div class="ci-status-link"> <loading-icon v-if="isLoading" label="Loading pipeline status" @@ -113,6 +114,7 @@ :title="statusTitle" :aria-label="statusTitle" :status="ciStatus" + :size="24" data-container="body" /> </a> diff --git a/app/assets/javascripts/reports/store/actions.js b/app/assets/javascripts/reports/store/actions.js new file mode 100644 index 00000000000..15c077b0fd8 --- /dev/null +++ b/app/assets/javascripts/reports/store/actions.js @@ -0,0 +1,67 @@ +import Visibility from 'visibilityjs'; +import axios from '../../lib/utils/axios_utils'; +import Poll from '../../lib/utils/poll'; +import * as types from './mutation_types'; + +export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint); + +export const requestReports = ({ commit }) => commit(types.REQUEST_REPORTS); + +let eTagPoll; + +export const clearEtagPoll = () => { + eTagPoll = null; +}; + +export const stopPolling = () => { + if (eTagPoll) eTagPoll.stop(); +}; + +export const restartPolling = () => { + if (eTagPoll) eTagPoll.restart(); +}; + +/** + * We need to poll the reports endpoint while they are being parsed in the Backend. + * This can take up to one minute. + * + * Poll.js will handle etag response. + * While http status code is 204, it means it's parsing, and we'll keep polling + * When http status code is 200, it means parsing is done, we can show the results & stop polling + * When http status code is 500, it means parsing went wrong and we stop polling + */ +export const fetchReports = ({ state, dispatch }) => { + dispatch('requestReports'); + + eTagPoll = new Poll({ + resource: { + getReports(endpoint) { + return axios.get(endpoint); + }, + }, + data: state.endpoint, + method: 'getReports', + successCallback: ({ data }) => dispatch('receiveReportsSuccess', data), + errorCallback: () => dispatch('receiveReportsError'), + }); + + if (!Visibility.hidden()) { + eTagPoll.makeRequest(); + } + + Visibility.change(() => { + if (!Visibility.hidden()) { + dispatch('restartPolling'); + } else { + dispatch('stopPolling'); + } + }); +}; + +export const receiveReportsSuccess = ({ commit }, response) => + commit(types.RECEIVE_REPORTS_SUCCESS, response); + +export const receiveReportsError = ({ commit }) => commit(types.RECEIVE_REPORTS_ERROR); + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/reports/store/index.js b/app/assets/javascripts/reports/store/index.js new file mode 100644 index 00000000000..af4f9688fb4 --- /dev/null +++ b/app/assets/javascripts/reports/store/index.js @@ -0,0 +1,13 @@ +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 default () => new Vuex.Store({ + actions, + mutations, + state: state(), +}); diff --git a/app/assets/javascripts/reports/store/mutation_types.js b/app/assets/javascripts/reports/store/mutation_types.js new file mode 100644 index 00000000000..77722974c45 --- /dev/null +++ b/app/assets/javascripts/reports/store/mutation_types.js @@ -0,0 +1,5 @@ +export const SET_ENDPOINT = 'SET_ENDPOINT'; + +export const REQUEST_REPORTS = 'REQUEST_REPORTS'; +export const RECEIVE_REPORTS_SUCCESS = 'RECEIVE_REPORTS_SUCCESS'; +export const RECEIVE_REPORTS_ERROR = 'RECEIVE_REPORTS_ERROR'; diff --git a/app/assets/javascripts/reports/store/mutations.js b/app/assets/javascripts/reports/store/mutations.js new file mode 100644 index 00000000000..d9d301826cf --- /dev/null +++ b/app/assets/javascripts/reports/store/mutations.js @@ -0,0 +1,26 @@ +/* eslint-disable no-param-reassign */ +import * as types from './mutation_types'; + +export default { + [types.SET_ENDPOINT](state, endpoint) { + state.endpoint = endpoint; + }, + [types.REQUEST_REPORTS](state) { + state.isLoading = true; + }, + [types.RECEIVE_REPORTS_SUCCESS](state, response) { + + state.isLoading = false; + + state.summary.total = response.summary.total; + state.summary.resolved = response.summary.resolved; + state.summary.failed = response.summary.failed; + + state.reports = response.suites; + + }, + [types.RECEIVE_REPORTS_ERROR](state) { + state.isLoading = false; + state.hasError = true; + }, +}; diff --git a/app/assets/javascripts/reports/store/state.js b/app/assets/javascripts/reports/store/state.js new file mode 100644 index 00000000000..97f9d0a6859 --- /dev/null +++ b/app/assets/javascripts/reports/store/state.js @@ -0,0 +1,28 @@ +export default () => ({ + endpoint: null, + + isLoading: false, + hasError: false, + + summary: { + total: 0, + resolved: 0, + failed: 0, + }, + + /** + * Each report will have the following format: + * { + * name: {String}, + * summary: { + * total: {Number}, + * resolved: {Number}, + * failed: {Number}, + * }, + * new_failures: {Array.<Object>}, + * resolved_failures: {Array.<Object>}, + * existing_failures: {Array.<Object>}, + * } + */ + reports: [], +}); diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index dc5760bce28..d272bf3f55f 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -13,12 +13,19 @@ * /> */ import tooltip from '../directives/tooltip'; +import Icon from '../components/icon.vue'; export default { name: 'ClipboardButton', + directives: { tooltip, }, + + components: { + Icon, + }, + props: { text: { type: String, @@ -58,10 +65,6 @@ export default { type="button" class="btn" > - <i - aria-hidden="true" - class="fa fa-clipboard" - > - </i> + <icon name="duplicate" /> </button> </template> diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue index b1c2df54ef6..f44d361c47e 100644 --- a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue +++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue @@ -1,4 +1,5 @@ <script> +import { roundOffFloat } from '~/lib/utils/common_utils'; import tooltip from '~/vue_shared/directives/tooltip'; export default { @@ -70,7 +71,7 @@ export default { }, methods: { getPercent(count) { - return Math.ceil((count / this.totalCount) * 100); + return roundOffFloat((count / this.totalCount) * 100, 1); }, barStyle(percent) { return `width: ${percent}%;`; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 523fcb05a87..646cedd79ed 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -294,6 +294,10 @@ .btn-clipboard { border: 0; padding: 0 5px; + + svg { + top: auto; + } } .input-group-prepend, diff --git a/app/assets/stylesheets/framework/stacked_progress_bar.scss b/app/assets/stylesheets/framework/stacked_progress_bar.scss index 528ba53a48b..29a2d5881f7 100644 --- a/app/assets/stylesheets/framework/stacked_progress_bar.scss +++ b/app/assets/stylesheets/framework/stacked_progress_bar.scss @@ -10,7 +10,7 @@ .status-neutral, .status-red, { height: 100%; - min-width: 30px; + min-width: 40px; padding: 0 5px; font-size: $tooltip-font-size; font-weight: normal; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index f75be4e01cd..63585e26022 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -205,7 +205,7 @@ > .ci-status-link, > .btn, > .commit-sha-group { - margin-left: $gl-padding-8; + margin-left: $gl-padding; } } @@ -235,10 +235,6 @@ fill: $gl-text-color-secondary; } - .fa-clipboard { - color: $gl-text-color-secondary; - } - :first-child { border-bottom-left-radius: $border-radius-default; border-top-left-radius: $border-radius-default; diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 074db361949..56a7b766b77 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -99,7 +99,8 @@ class ProfilesController < Profiles::ApplicationController :username, :website_url, :organization, - :preferred_language + :preferred_language, + :private_profile ) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 31f47a7aa7c..2f65f4a7403 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -13,6 +13,8 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! before_action :user, except: [:exists] + before_action :authorize_read_user_profile!, + only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets] def show respond_to do |format| @@ -148,4 +150,8 @@ class UsersController < ApplicationController def build_canonical_path(user) url_for(safe_params.merge(username: user.to_param)) end + + def authorize_read_user_profile! + access_denied! unless can?(current_user, :read_user_profile, user) + end end diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index 0754123a3cf..0eeba1d2428 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -8,6 +8,7 @@ # owned: boolean # parent: Group # all_available: boolean (defaults to true) +# min_access_level: integer # # Users with full private access can see all groups. The `owned` and `parent` # params can be used to restrict the groups that are returned. @@ -39,6 +40,7 @@ class GroupsFinder < UnionFinder def all_groups return [owned_groups] if params[:owned] + return [groups_with_min_access_level] if min_access_level? return [Group.all] if current_user&.full_private_access? && all_available? groups = [] @@ -56,6 +58,16 @@ class GroupsFinder < UnionFinder current_user.groups end + def groups_with_min_access_level + groups = current_user + .groups + .where('members.access_level >= ?', params[:min_access_level]) + + Gitlab::GroupHierarchy + .new(groups) + .base_and_descendants + end + def by_parent(groups) return groups unless params[:parent] @@ -73,4 +85,8 @@ class GroupsFinder < UnionFinder def all_available? params.fetch(:all_available, true) end + + def min_access_level? + current_user && params[:min_access_level].present? + end end diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb index 5aea0cb8192..a56a3a1e1a9 100644 --- a/app/finders/personal_projects_finder.rb +++ b/app/finders/personal_projects_finder.rb @@ -1,6 +1,9 @@ class PersonalProjectsFinder < UnionFinder - def initialize(user) + include Gitlab::Allowable + + def initialize(user, params = {}) @user = user + @params = params end # Finds the projects belonging to the user in "@user", limited to either @@ -8,9 +11,13 @@ class PersonalProjectsFinder < UnionFinder # # current_user - When given the list of projects is limited to those only # visible by this user. + # params - Optional query parameters + # min_access_level: integer # # Returns an ActiveRecord::Relation. def execute(current_user = nil) + return Project.none unless can?(current_user, :read_user_profile, @user) + segments = all_projects(current_user) find_union(segments, Project).includes(:namespace).order_updated_desc @@ -19,11 +26,21 @@ class PersonalProjectsFinder < UnionFinder private def all_projects(current_user) - projects = [] + return [projects_with_min_access_level(current_user)] if current_user && min_access_level? + projects = [] projects << @user.personal_projects.visible_to_user(current_user) if current_user projects << @user.personal_projects.public_to_user(current_user) - projects end + + def projects_with_min_access_level(current_user) + @user + .personal_projects + .visible_to_user_and_access_level(current_user, @params[:min_access_level]) + end + + def min_access_level? + @params[:min_access_level].present? + end end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index b06595081e7..cac6643eff3 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -17,6 +17,7 @@ # search: string # non_archived: boolean # archived: 'only' or boolean +# min_access_level: integer # class ProjectsFinder < UnionFinder include CustomAttributesFilter @@ -34,7 +35,7 @@ class ProjectsFinder < UnionFinder user = params.delete(:user) collection = if user - PersonalProjectsFinder.new(user).execute(current_user) + PersonalProjectsFinder.new(user, finder_params).execute(current_user) else init_collection end @@ -65,6 +66,8 @@ class ProjectsFinder < UnionFinder def collection_with_user if owned_projects? current_user.owned_projects + elsif min_access_level? + current_user.authorized_projects.where('project_authorizations.access_level >= ?', params[:min_access_level]) else if private_only? current_user.authorized_projects @@ -76,7 +79,7 @@ class ProjectsFinder < UnionFinder # Builds a collection for an anonymous user. def collection_without_user - if private_only? || owned_projects? + if private_only? || owned_projects? || min_access_level? Project.none else Project.public_to_user @@ -91,6 +94,10 @@ class ProjectsFinder < UnionFinder params[:non_public].present? end + def min_access_level? + params[:min_access_level].present? + end + def by_ids(items) project_ids_relation ? items.where(id: project_ids_relation) : items end @@ -143,4 +150,10 @@ class ProjectsFinder < UnionFinder projects end end + + def finder_params + return {} unless min_access_level? + + { min_access_level: params[:min_access_level] } + end end diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb index 74776b2ed1f..876f086a3ef 100644 --- a/app/finders/user_recent_events_finder.rb +++ b/app/finders/user_recent_events_finder.rb @@ -7,6 +7,7 @@ class UserRecentEventsFinder prepend FinderWithCrossProjectAccess include FinderMethods + include Gitlab::Allowable requires_cross_project_access @@ -21,6 +22,8 @@ class UserRecentEventsFinder end def execute + return Event.none unless can?(current_user, :read_user_profile, target_user) + recent_events(params[:offset] || 0) .joins(:project) .with_associations diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 3605d6a3c95..0171a880164 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -51,7 +51,7 @@ module ButtonHelper } content_tag :button, button_attributes do - concat(icon('clipboard', 'aria-hidden': 'true')) unless hide_button_icon + concat(sprite_icon('duplicate')) unless hide_button_icon concat(button_text) end end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index f49b5c7b51a..330959e536d 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -56,7 +56,7 @@ module CiStatusHelper status.humanize end - def ci_icon_for_status(status) + def ci_icon_for_status(status, size: 16) if detailed_status?(status) return sprite_icon(status.icon) end @@ -85,7 +85,7 @@ module CiStatusHelper 'status_canceled' end - sprite_icon(icon_name, size: 16) + sprite_icon(icon_name, size: size) end def pipeline_status_cache_key(pipeline_status) @@ -111,7 +111,8 @@ module CiStatusHelper 'commit', commit.status(ref), path, - tooltip_placement: tooltip_placement) + tooltip_placement: tooltip_placement, + icon_size: 24) end def render_pipeline_status(pipeline, tooltip_placement: 'left') @@ -125,16 +126,16 @@ module CiStatusHelper Ci::Runner.instance_type.blank? end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body') + def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16) klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" data = { toggle: 'tooltip', placement: tooltip_placement, container: container } if path - link_to ci_icon_for_status(status), path, + link_to ci_icon_for_status(status, size: icon_size), path, class: klass, title: title, data: data else - content_tag :span, ci_icon_for_status(status), + content_tag :span, ci_icon_for_status(status, size: icon_size), class: klass, title: title, data: data end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index e5c3be47801..89fe90fd801 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -145,15 +145,14 @@ module CommitsHelper person_name end - options = { - class: "commit-#{options[:source]}-link has-tooltip", - title: source_email + link_options = { + class: "commit-#{options[:source]}-link" } if user.nil? - mail_to(source_email, text, options) + mail_to(source_email, text, link_options) else - link_to(text, user_path(user), options) + link_to(text, user_path(user), link_options) end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 4d17b22a4a1..8ee4203b6f5 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -42,7 +42,13 @@ module UsersHelper private def get_profile_tabs - [:activity, :groups, :contributed, :projects, :snippets] + tabs = [] + + if can?(current_user, :read_user_profile, @user) + tabs += [:activity, :groups, :contributed, :projects, :snippets] + end + + tabs end def get_current_user_menu_items diff --git a/app/models/project.rb b/app/models/project.rb index 7d37c3b3893..f880d728839 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -327,6 +327,7 @@ class Project < ActiveRecord::Base scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) } scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) } + scope :visible_to_user_and_access_level, ->(user, access_level) { where(id: user.authorized_projects.where('project_authorizations.access_level >= ?', access_level).select(:id).reorder(nil)) } scope :archived, -> { where(archived: true) } scope :non_archived, -> { where(archived: false) } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index ee219f0a0d0..8499e45e846 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -5,6 +5,9 @@ class UserPolicy < BasePolicy desc "This is the ghost user" condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? } + desc "The profile is private" + condition(:private_profile, scope: :subject, score: 0) { @subject.private_profile? } + rule { ~restricted_public_level }.enable :read_user rule { ~anonymous }.enable :read_user @@ -12,4 +15,7 @@ class UserPolicy < BasePolicy enable :destroy_user enable :update_user end + + rule { default }.enable :read_user_profile + rule { private_profile & ~(user_is_self | admin) }.prevent :read_user_profile end diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index bdc22d71202..99663c8d5eb 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsBuildEntity < Grape::Entity include RequestAwareEntity include EntityDateHelper diff --git a/app/serializers/analytics_build_serializer.rb b/app/serializers/analytics_build_serializer.rb index f172d67d356..9c9f76a1c28 100644 --- a/app/serializers/analytics_build_serializer.rb +++ b/app/serializers/analytics_build_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsBuildSerializer < BaseSerializer entity AnalyticsBuildEntity end diff --git a/app/serializers/analytics_commit_entity.rb b/app/serializers/analytics_commit_entity.rb index 402cecbfd08..b25c603c9f0 100644 --- a/app/serializers/analytics_commit_entity.rb +++ b/app/serializers/analytics_commit_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsCommitEntity < CommitEntity include EntityDateHelper diff --git a/app/serializers/analytics_commit_serializer.rb b/app/serializers/analytics_commit_serializer.rb index cdbfecf2b70..0f65687a3c0 100644 --- a/app/serializers/analytics_commit_serializer.rb +++ b/app/serializers/analytics_commit_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsCommitSerializer < BaseSerializer entity AnalyticsCommitEntity end diff --git a/app/serializers/analytics_generic_serializer.rb b/app/serializers/analytics_generic_serializer.rb index 9f4859e8410..10391c13034 100644 --- a/app/serializers/analytics_generic_serializer.rb +++ b/app/serializers/analytics_generic_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsGenericSerializer < BaseSerializer def represent(resource, opts = {}) resource.symbolize_keys! diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb index b7d95ea020f..ab15bd0ac7a 100644 --- a/app/serializers/analytics_issue_entity.rb +++ b/app/serializers/analytics_issue_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsIssueEntity < Grape::Entity include RequestAwareEntity include EntityDateHelper diff --git a/app/serializers/analytics_issue_serializer.rb b/app/serializers/analytics_issue_serializer.rb index 4fb3e8f1bb4..4a1777276a4 100644 --- a/app/serializers/analytics_issue_serializer.rb +++ b/app/serializers/analytics_issue_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsIssueSerializer < AnalyticsGenericSerializer entity AnalyticsIssueEntity end diff --git a/app/serializers/analytics_merge_request_entity.rb b/app/serializers/analytics_merge_request_entity.rb index 888265eaa38..b7134da9461 100644 --- a/app/serializers/analytics_merge_request_entity.rb +++ b/app/serializers/analytics_merge_request_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsMergeRequestEntity < AnalyticsIssueEntity expose :state diff --git a/app/serializers/analytics_merge_request_serializer.rb b/app/serializers/analytics_merge_request_serializer.rb index 4622a1dd855..f0b9115d02c 100644 --- a/app/serializers/analytics_merge_request_serializer.rb +++ b/app/serializers/analytics_merge_request_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsMergeRequestSerializer < AnalyticsGenericSerializer entity AnalyticsMergeRequestEntity end diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb index 3e355a13e06..ae7c20c3bba 100644 --- a/app/serializers/analytics_stage_entity.rb +++ b/app/serializers/analytics_stage_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsStageEntity < Grape::Entity include EntityDateHelper diff --git a/app/serializers/analytics_stage_serializer.rb b/app/serializers/analytics_stage_serializer.rb index 613cf6874d8..86786273240 100644 --- a/app/serializers/analytics_stage_serializer.rb +++ b/app/serializers/analytics_stage_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsStageSerializer < BaseSerializer entity AnalyticsStageEntity end diff --git a/app/serializers/analytics_summary_entity.rb b/app/serializers/analytics_summary_entity.rb index 9c37afd53e1..39c6b4b06b2 100644 --- a/app/serializers/analytics_summary_entity.rb +++ b/app/serializers/analytics_summary_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsSummaryEntity < Grape::Entity expose :value, safe: true expose :title diff --git a/app/serializers/analytics_summary_serializer.rb b/app/serializers/analytics_summary_serializer.rb index c87a24aa47c..b22bd737f03 100644 --- a/app/serializers/analytics_summary_serializer.rb +++ b/app/serializers/analytics_summary_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AnalyticsSummarySerializer < BaseSerializer entity AnalyticsSummaryEntity end diff --git a/app/serializers/award_emoji_entity.rb b/app/serializers/award_emoji_entity.rb index 6e03cd02392..212931a2fa9 100644 --- a/app/serializers/award_emoji_entity.rb +++ b/app/serializers/award_emoji_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AwardEmojiEntity < Grape::Entity expose :name expose :user, using: API::Entities::UserSafe diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb index 8cade280b0c..7b65bd22f54 100644 --- a/app/serializers/base_serializer.rb +++ b/app/serializers/base_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseSerializer attr_reader :params diff --git a/app/serializers/blob_entity.rb b/app/serializers/blob_entity.rb index b501fd5e964..3ac61481dea 100644 --- a/app/serializers/blob_entity.rb +++ b/app/serializers/blob_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BlobEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb index f2d76a8ad81..f9da3f63911 100644 --- a/app/serializers/build_action_entity.rb +++ b/app/serializers/build_action_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BuildActionEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb index 6e0e33bc09b..414f436e76e 100644 --- a/app/serializers/build_artifact_entity.rb +++ b/app/serializers/build_artifact_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BuildArtifactEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 2de9624aed4..b887b99d31c 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BuildDetailsEntity < JobEntity expose :coverage, :erased_at, :duration expose :tag_list, as: :tags diff --git a/app/serializers/build_metadata_entity.rb b/app/serializers/build_metadata_entity.rb index f16f3badffa..6242ee8957d 100644 --- a/app/serializers/build_metadata_entity.rb +++ b/app/serializers/build_metadata_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BuildMetadataEntity < Grape::Entity expose :timeout_human_readable expose :timeout_source do |metadata| diff --git a/app/serializers/build_serializer.rb b/app/serializers/build_serializer.rb index bae9932847f..0649fdad6a8 100644 --- a/app/serializers/build_serializer.rb +++ b/app/serializers/build_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BuildSerializer < BaseSerializer entity JobEntity diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb index 77fc3336521..2bd17e58086 100644 --- a/app/serializers/cluster_application_entity.rb +++ b/app/serializers/cluster_application_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ClusterApplicationEntity < Grape::Entity expose :name expose :status_name, as: :status diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb index 7e5b0997878..c59f68bbc49 100644 --- a/app/serializers/cluster_entity.rb +++ b/app/serializers/cluster_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ClusterEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb index 2e13c1501e7..4bb4d4880d4 100644 --- a/app/serializers/cluster_serializer.rb +++ b/app/serializers/cluster_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ClusterSerializer < BaseSerializer entity ClusterEntity diff --git a/app/serializers/cohort_activity_month_entity.rb b/app/serializers/cohort_activity_month_entity.rb index e6788a8b596..cdbc89a2dcd 100644 --- a/app/serializers/cohort_activity_month_entity.rb +++ b/app/serializers/cohort_activity_month_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CohortActivityMonthEntity < Grape::Entity include ActionView::Helpers::NumberHelper diff --git a/app/serializers/cohort_entity.rb b/app/serializers/cohort_entity.rb index 7cdba5b0484..3d0213e1038 100644 --- a/app/serializers/cohort_entity.rb +++ b/app/serializers/cohort_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CohortEntity < Grape::Entity include ActionView::Helpers::NumberHelper diff --git a/app/serializers/cohorts_entity.rb b/app/serializers/cohorts_entity.rb index 98f5995ba6f..a84d568bf30 100644 --- a/app/serializers/cohorts_entity.rb +++ b/app/serializers/cohorts_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CohortsEntity < Grape::Entity expose :months_included expose :cohorts, using: CohortEntity diff --git a/app/serializers/cohorts_serializer.rb b/app/serializers/cohorts_serializer.rb index fe9367b13d8..ceca5e1e5a8 100644 --- a/app/serializers/cohorts_serializer.rb +++ b/app/serializers/cohorts_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CohortsSerializer < AnalyticsGenericSerializer entity CohortsEntity end diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index c8dd98cc04d..b3287c66554 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CommitEntity < API::Entities::Commit include RequestAwareEntity diff --git a/app/serializers/concerns/with_pagination.rb b/app/serializers/concerns/with_pagination.rb index 89631b73fcf..c8ffae355e8 100644 --- a/app/serializers/concerns/with_pagination.rb +++ b/app/serializers/concerns/with_pagination.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module WithPagination attr_accessor :paginator diff --git a/app/serializers/container_repositories_serializer.rb b/app/serializers/container_repositories_serializer.rb index 56dc70b5687..e1ce3c7b3ae 100644 --- a/app/serializers/container_repositories_serializer.rb +++ b/app/serializers/container_repositories_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ContainerRepositoriesSerializer < BaseSerializer entity ContainerRepositoryEntity end diff --git a/app/serializers/container_repository_entity.rb b/app/serializers/container_repository_entity.rb index 1103cf30a07..59bf35f5aff 100644 --- a/app/serializers/container_repository_entity.rb +++ b/app/serializers/container_repository_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ContainerRepositoryEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/container_tag_entity.rb b/app/serializers/container_tag_entity.rb index 8f1488e6cbb..637294877f8 100644 --- a/app/serializers/container_tag_entity.rb +++ b/app/serializers/container_tag_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ContainerTagEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/container_tags_serializer.rb b/app/serializers/container_tags_serializer.rb index 6ff3adff135..982ce33f6e3 100644 --- a/app/serializers/container_tags_serializer.rb +++ b/app/serializers/container_tags_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ContainerTagsSerializer < BaseSerializer entity ContainerTagEntity diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb index 2678f99510c..54bf030aba1 100644 --- a/app/serializers/deploy_key_entity.rb +++ b/app/serializers/deploy_key_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeployKeyEntity < Grape::Entity expose :id expose :user_id diff --git a/app/serializers/deploy_key_serializer.rb b/app/serializers/deploy_key_serializer.rb index 8f849eb88b7..a1cd98b631b 100644 --- a/app/serializers/deploy_key_serializer.rb +++ b/app/serializers/deploy_key_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeployKeySerializer < BaseSerializer entity DeployKeyEntity end diff --git a/app/serializers/deploy_keys_project_entity.rb b/app/serializers/deploy_keys_project_entity.rb index 568ef5ab75e..5775ad72d0d 100644 --- a/app/serializers/deploy_keys_project_entity.rb +++ b/app/serializers/deploy_keys_project_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeployKeysProjectEntity < Grape::Entity expose :can_push expose :project, using: ProjectEntity diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb index 241c689bccd..344148a1fb7 100644 --- a/app/serializers/deployment_entity.rb +++ b/app/serializers/deployment_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeploymentEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/deployment_serializer.rb b/app/serializers/deployment_serializer.rb index cba5c3f311f..04db6b88489 100644 --- a/app/serializers/deployment_serializer.rb +++ b/app/serializers/deployment_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeploymentSerializer < BaseSerializer entity DeploymentEntity diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb index 61135fba97b..79844c9210a 100644 --- a/app/serializers/diff_file_entity.rb +++ b/app/serializers/diff_file_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DiffFileEntity < Grape::Entity include RequestAwareEntity include BlobHelper diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb index bb804e5347a..f75ace14d9c 100644 --- a/app/serializers/diffs_entity.rb +++ b/app/serializers/diffs_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DiffsEntity < Grape::Entity include DiffHelper include RequestAwareEntity diff --git a/app/serializers/diffs_serializer.rb b/app/serializers/diffs_serializer.rb index 6771e10c5ac..9e5eb1699d4 100644 --- a/app/serializers/diffs_serializer.rb +++ b/app/serializers/diffs_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DiffsSerializer < BaseSerializer entity DiffsEntity end diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb index 7505bbdeb3d..6f95e6f9ca1 100644 --- a/app/serializers/discussion_entity.rb +++ b/app/serializers/discussion_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DiscussionEntity < Grape::Entity include RequestAwareEntity include NotesHelper diff --git a/app/serializers/discussion_serializer.rb b/app/serializers/discussion_serializer.rb index ed5e1224bb2..5be40e74175 100644 --- a/app/serializers/discussion_serializer.rb +++ b/app/serializers/discussion_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DiscussionSerializer < BaseSerializer entity DiscussionEntity end diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 464217123b4..cc0c2abf863 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module EntityDateHelper include ActionView::Helpers::DateHelper include ActionView::Helpers::TagHelper @@ -50,15 +52,20 @@ module EntityDateHelper elsif entity.due_date is_upcoming = (entity.due_date - Date.today).to_i > 0 time_ago = time_ago_in_words(entity.due_date) - content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" } - content.slice!("about ") - content << " " + (is_upcoming ? _("remaining") : _("ago")) - content.html_safe + + # https://gitlab.com/gitlab-org/gitlab-ce/issues/49440 + # + # Need to improve the i18n here and do a full translation + # of the string instead of piecewise translations. + content = time_ago + .gsub(/\d+/) { |match| "<strong>#{match}</strong>" } + .remove("about ") + remaining_or_ago = is_upcoming ? _("remaining") : _("ago") + + "#{content} #{remaining_or_ago}".html_safe elsif entity.start_date && entity.start_date.past? - days = entity.elapsed_days - content = content_tag(:strong, days) - content << " #{'day'.pluralize(days)} elapsed" - content.html_safe + days = entity.elapsed_days + "#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe end end end diff --git a/app/serializers/entity_request.rb b/app/serializers/entity_request.rb index 456ba1174c0..49e026e8c2a 100644 --- a/app/serializers/entity_request.rb +++ b/app/serializers/entity_request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EntityRequest # We use EntityRequest object to collect parameters and variables # from the controller. Because options that are being passed to the entity diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index 83558fc6659..b18e9706db6 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EnvironmentEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb index 84722f33f59..dc1686c30c4 100644 --- a/app/serializers/environment_serializer.rb +++ b/app/serializers/environment_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EnvironmentSerializer < BaseSerializer include WithPagination diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb index ee150eefd9e..f6804fe7f6a 100644 --- a/app/serializers/group_child_entity.rb +++ b/app/serializers/group_child_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupChildEntity < Grape::Entity include ActionView::Helpers::NumberHelper include RequestAwareEntity diff --git a/app/serializers/group_child_serializer.rb b/app/serializers/group_child_serializer.rb index 2baef0a5703..789707c2c9b 100644 --- a/app/serializers/group_child_serializer.rb +++ b/app/serializers/group_child_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupChildSerializer < BaseSerializer include WithPagination diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb index 6d8466da902..c46c342ee5d 100644 --- a/app/serializers/group_entity.rb +++ b/app/serializers/group_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupEntity < Grape::Entity include ActionView::Helpers::NumberHelper include RequestAwareEntity diff --git a/app/serializers/group_serializer.rb b/app/serializers/group_serializer.rb index 8cf7eb63bcf..38c5757a6c1 100644 --- a/app/serializers/group_serializer.rb +++ b/app/serializers/group_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupSerializer < BaseSerializer include WithPagination diff --git a/app/serializers/group_variable_entity.rb b/app/serializers/group_variable_entity.rb index 62cf0b21e1e..0edab4a3092 100644 --- a/app/serializers/group_variable_entity.rb +++ b/app/serializers/group_variable_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupVariableEntity < Grape::Entity expose :id expose :key diff --git a/app/serializers/group_variable_serializer.rb b/app/serializers/group_variable_serializer.rb index 8f8205924aa..ed20b240cce 100644 --- a/app/serializers/group_variable_serializer.rb +++ b/app/serializers/group_variable_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupVariableSerializer < BaseSerializer entity GroupVariableEntity end diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb index 6f31fbd6b7c..e71bd3313fb 100644 --- a/app/serializers/issuable_entity.rb +++ b/app/serializers/issuable_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssuableEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/issuable_sidebar_entity.rb b/app/serializers/issuable_sidebar_entity.rb index 29138c803df..773d78d324c 100644 --- a/app/serializers/issuable_sidebar_entity.rb +++ b/app/serializers/issuable_sidebar_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssuableSidebarEntity < Grape::Entity include TimeTrackableEntity include RequestAwareEntity diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 840fdbcbf14..16a477c92fa 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssueEntity < IssuableEntity include TimeTrackableEntity diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb index 2555595379b..37cf5e28396 100644 --- a/app/serializers/issue_serializer.rb +++ b/app/serializers/issue_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssueSerializer < BaseSerializer # This overrided method takes care of which entity should be used # to serialize the `issue` based on `basic` key in `opts` param. diff --git a/app/serializers/issue_sidebar_entity.rb b/app/serializers/issue_sidebar_entity.rb index 6c823dbfe95..349ad9d1fef 100644 --- a/app/serializers/issue_sidebar_entity.rb +++ b/app/serializers/issue_sidebar_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssueSidebarEntity < IssuableSidebarEntity expose :assignees, using: API::Entities::UserBasic end diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index 960e7291ae6..7bc1d87dea5 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class JobEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/job_group_entity.rb b/app/serializers/job_group_entity.rb index 8554de55517..0941a9d36be 100644 --- a/app/serializers/job_group_entity.rb +++ b/app/serializers/job_group_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class JobGroupEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/label_entity.rb b/app/serializers/label_entity.rb index 4452161051e..98743d62b50 100644 --- a/app/serializers/label_entity.rb +++ b/app/serializers/label_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class LabelEntity < Grape::Entity expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) } diff --git a/app/serializers/label_serializer.rb b/app/serializers/label_serializer.rb index ad6ba8c46c9..25b9f7de243 100644 --- a/app/serializers/label_serializer.rb +++ b/app/serializers/label_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class LabelSerializer < BaseSerializer entity LabelEntity diff --git a/app/serializers/lfs_file_lock_entity.rb b/app/serializers/lfs_file_lock_entity.rb index 264a77adc3f..7961c4e666b 100644 --- a/app/serializers/lfs_file_lock_entity.rb +++ b/app/serializers/lfs_file_lock_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class LfsFileLockEntity < Grape::Entity root 'locks', 'lock' diff --git a/app/serializers/lfs_file_lock_serializer.rb b/app/serializers/lfs_file_lock_serializer.rb index ba8fb1a461d..0367097e376 100644 --- a/app/serializers/lfs_file_lock_serializer.rb +++ b/app/serializers/lfs_file_lock_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class LfsFileLockSerializer < BaseSerializer entity LfsFileLockEntity end diff --git a/app/serializers/merge_request_basic_entity.rb b/app/serializers/merge_request_basic_entity.rb index 1c06691026d..f7eb74cf392 100644 --- a/app/serializers/merge_request_basic_entity.rb +++ b/app/serializers/merge_request_basic_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestBasicEntity < IssuableSidebarEntity expose :assignee_id expose :merge_status diff --git a/app/serializers/merge_request_basic_serializer.rb b/app/serializers/merge_request_basic_serializer.rb index cc5c664c8fa..a68b48b00db 100644 --- a/app/serializers/merge_request_basic_serializer.rb +++ b/app/serializers/merge_request_basic_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestBasicSerializer < BaseSerializer entity MergeRequestBasicEntity end diff --git a/app/serializers/merge_request_create_entity.rb b/app/serializers/merge_request_create_entity.rb index 11234313293..e7a93004dda 100644 --- a/app/serializers/merge_request_create_entity.rb +++ b/app/serializers/merge_request_create_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestCreateEntity < Grape::Entity expose :iid diff --git a/app/serializers/merge_request_create_serializer.rb b/app/serializers/merge_request_create_serializer.rb index 08daf473319..b6df9ee13fc 100644 --- a/app/serializers/merge_request_create_serializer.rb +++ b/app/serializers/merge_request_create_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestCreateSerializer < BaseSerializer entity MergeRequestCreateEntity end diff --git a/app/serializers/merge_request_diff_entity.rb b/app/serializers/merge_request_diff_entity.rb index 32c761b45ac..433bfe60474 100644 --- a/app/serializers/merge_request_diff_entity.rb +++ b/app/serializers/merge_request_diff_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestDiffEntity < Grape::Entity include Gitlab::Routing include GitHelper diff --git a/app/serializers/merge_request_metrics_entity.rb b/app/serializers/merge_request_metrics_entity.rb index 3548107ac16..1c9db08d103 100644 --- a/app/serializers/merge_request_metrics_entity.rb +++ b/app/serializers/merge_request_metrics_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestMetricsEntity < Grape::Entity expose :latest_closed_at, as: :closed_at expose :merged_at diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb index caf193bdae3..1f8c830e1aa 100644 --- a/app/serializers/merge_request_serializer.rb +++ b/app/serializers/merge_request_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestSerializer < BaseSerializer # This overrided method takes care of which entity should be used # to serialize the `merge_request` based on `serializer` key in `opts` param. diff --git a/app/serializers/merge_request_user_entity.rb b/app/serializers/merge_request_user_entity.rb index 33fc7b724d5..fd2d2897113 100644 --- a/app/serializers/merge_request_user_entity.rb +++ b/app/serializers/merge_request_user_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestUserEntity < UserEntity include RequestAwareEntity include BlobHelper diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index a78bd77cf7c..4fe04e4b206 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestWidgetEntity < IssuableEntity expose :state expose :in_progress_merge_commit_sha diff --git a/app/serializers/note_attachment_entity.rb b/app/serializers/note_attachment_entity.rb index 1ad50568ab9..dc801e2bf4e 100644 --- a/app/serializers/note_attachment_entity.rb +++ b/app/serializers/note_attachment_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NoteAttachmentEntity < Grape::Entity expose :url expose :filename diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb index 0e1f94a9f61..daa5c24d0f5 100644 --- a/app/serializers/note_entity.rb +++ b/app/serializers/note_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NoteEntity < API::Entities::Note include RequestAwareEntity include NotesHelper diff --git a/app/serializers/note_user_entity.rb b/app/serializers/note_user_entity.rb index 7289f3a0222..b00dfa7d353 100644 --- a/app/serializers/note_user_entity.rb +++ b/app/serializers/note_user_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NoteUserEntity < UserEntity unexpose :web_url end diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb index 8ba9cac53c4..3b56767f774 100644 --- a/app/serializers/pipeline_details_entity.rb +++ b/app/serializers/pipeline_details_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PipelineDetailsEntity < PipelineEntity expose :details do expose :ordered_stages, as: :stages, using: StageEntity diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index f782b411b84..6cf1925adda 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PipelineEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 17a022539bb..4a33160afa1 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PipelineSerializer < BaseSerializer include WithPagination entity PipelineDetailsEntity diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb index b3e5fd21e97..60c4ba135d6 100644 --- a/app/serializers/project_entity.rb +++ b/app/serializers/project_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProjectEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/project_mirror_entity.rb b/app/serializers/project_mirror_entity.rb index a9c08ac021a..8aba244cd11 100644 --- a/app/serializers/project_mirror_entity.rb +++ b/app/serializers/project_mirror_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProjectMirrorEntity < Grape::Entity expose :id diff --git a/app/serializers/project_note_entity.rb b/app/serializers/project_note_entity.rb index e541bfbee8d..d7c4d0aacc6 100644 --- a/app/serializers/project_note_entity.rb +++ b/app/serializers/project_note_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProjectNoteEntity < NoteEntity expose :human_access do |note| note.project.team.human_max_access(note.author_id) diff --git a/app/serializers/project_note_serializer.rb b/app/serializers/project_note_serializer.rb index 763ad0bdb3f..2182904e815 100644 --- a/app/serializers/project_note_serializer.rb +++ b/app/serializers/project_note_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProjectNoteSerializer < BaseSerializer entity ProjectNoteEntity end diff --git a/app/serializers/project_serializer.rb b/app/serializers/project_serializer.rb index 74de1e79a8f..23b96c2fc9e 100644 --- a/app/serializers/project_serializer.rb +++ b/app/serializers/project_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProjectSerializer < BaseSerializer entity ProjectEntity end diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb index d53fcfb8c1b..1524c1291d8 100644 --- a/app/serializers/request_aware_entity.rb +++ b/app/serializers/request_aware_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RequestAwareEntity extend ActiveSupport::Concern diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb index db26eadab2d..04ec80e0efa 100644 --- a/app/serializers/runner_entity.rb +++ b/app/serializers/runner_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RunnerEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb index 2516df70ad9..00e6d32ee3a 100644 --- a/app/serializers/stage_entity.rb +++ b/app/serializers/stage_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class StageEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/stage_serializer.rb b/app/serializers/stage_serializer.rb index 091d8e91e43..11fb0d3f852 100644 --- a/app/serializers/stage_serializer.rb +++ b/app/serializers/stage_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class StageSerializer < BaseSerializer include WithPagination diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index 47df7f9dcf9..306c30f0323 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class StatusEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/submodule_entity.rb b/app/serializers/submodule_entity.rb index ed1f1ae0ef0..e475a4f301f 100644 --- a/app/serializers/submodule_entity.rb +++ b/app/serializers/submodule_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SubmoduleEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/time_trackable_entity.rb b/app/serializers/time_trackable_entity.rb index e81cd7bec72..613d19703a4 100644 --- a/app/serializers/time_trackable_entity.rb +++ b/app/serializers/time_trackable_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module TimeTrackableEntity extend ActiveSupport::Concern extend Grape diff --git a/app/serializers/tree_entity.rb b/app/serializers/tree_entity.rb index 9f1b485347f..9b7dc80e1d9 100644 --- a/app/serializers/tree_entity.rb +++ b/app/serializers/tree_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class TreeEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb index 496f070ddbd..f1cfcd943d8 100644 --- a/app/serializers/tree_root_entity.rb +++ b/app/serializers/tree_root_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`. class TreeRootEntity < Grape::Entity include RequestAwareEntity diff --git a/app/serializers/tree_serializer.rb b/app/serializers/tree_serializer.rb index 713ade23bc9..536b8ab1ae2 100644 --- a/app/serializers/tree_serializer.rb +++ b/app/serializers/tree_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class TreeSerializer < BaseSerializer entity TreeRootEntity end diff --git a/app/serializers/user_entity.rb b/app/serializers/user_entity.rb index 876512b12dc..6236d66ff4a 100644 --- a/app/serializers/user_entity.rb +++ b/app/serializers/user_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UserEntity < API::Entities::UserBasic include RequestAwareEntity diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 49a71ebac61..2111e1b5667 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UserSerializer < BaseSerializer entity UserEntity end diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb index d576745c073..85cf367fe51 100644 --- a/app/serializers/variable_entity.rb +++ b/app/serializers/variable_entity.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class VariableEntity < Grape::Entity expose :id expose :key diff --git a/app/serializers/variable_serializer.rb b/app/serializers/variable_serializer.rb index 32ae82ab51c..586666cad8e 100644 --- a/app/serializers/variable_serializer.rb +++ b/app/serializers/variable_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class VariableSerializer < BaseSerializer entity VariableEntity end diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb index c69b46cab5a..acc2fa153ae 100644 --- a/app/services/users/build_service.rb +++ b/app/services/users/build_service.rb @@ -64,7 +64,8 @@ module Users :theme_id, :twitter, :username, - :website_url + :website_url, + :private_profile ] end diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 507cd5dcc12..a4835584b50 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -69,6 +69,12 @@ = f.text_field :location = f.text_field :organization = f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.' + %hr + %h5 Private profile + - private_profile_label = capture do + Don't display activity-related personal information on your profile + = link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile') + = f.check_box :private_profile, label: private_profile_label .prepend-top-default.append-bottom-default = f.submit 'Update profile settings', class: 'btn btn-success' = link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel' diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index e90916e340d..ef6f5c76de6 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -18,7 +18,7 @@ - commit = blame_group[:commit] %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) } .commit - = author_avatar(commit, size: 36) + = author_avatar(commit, size: 36, has_tooltip: false) .commit-row-title %span.item-title.str-truncated-100 = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index efb8175398b..5edab38bd64 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -3,6 +3,8 @@ - page_title @blob.path, @ref +.js-signature-container{ data: { 'signatures-path': namespace_project_signatures_path } } + %div{ class: container_class } = render 'projects/last_push' diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 886dd73c33b..78522393d4b 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -10,7 +10,7 @@ %span.d-none.d-sm-inline authored #{time_ago_with_tooltip(@commit.authored_date)} %span= s_('ByAuthor|by') - = author_avatar(@commit, size: 24) + = author_avatar(@commit, size: 24, has_tooltip: false) %strong = commit_author_link(@commit, avatar: true, size: 24) - if @commit.different_committer? diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 90e55fd0fb0..feaf44e8c0a 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -19,7 +19,7 @@ %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" } .avatar-cell.d-none.d-sm-block - = author_avatar(commit, size: 36) + = author_avatar(commit, size: 36, has_tooltip: false) .commit-detail.flex-list .commit-content.qa-commit-content diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 9741b783db3..1d0b0265bb7 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -7,4 +7,4 @@ = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/metrics'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 - = render_if_exists 'projects/services/prometheus/metrics', project: @project + = render 'projects/services/prometheus/metrics', project: @project diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index e28accd5b43..803ecca48f7 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -8,6 +8,10 @@ = render partial: 'flash_messages', locals: { project: @project } +- if @project.repository_exists? && !@project.empty_repo? + - signatures_path = namespace_project_signatures_path(project_id: @project.path, id: @project.default_branch) + .js-signature-container{ data: { 'signatures-path': signatures_path } } + %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } = render "projects/last_push" diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 3b4057e56d0..ace8120eeff 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,11 +1,14 @@ - @no_container = true - breadcrumb_title _("Repository") - @content_class = "limit-container-width" unless fluid_layout +- signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.path, project_id: @project.path, id: @ref) - page_title @path.presence || _("Files"), @ref = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") +.js-signature-container{ data: { 'signatures-path': signatures_path } } + %div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] } = render 'projects/last_push' = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml index 37747faed62..ddc089b0bd7 100644 --- a/app/views/sherlock/queries/_general.html.haml +++ b/app/views/sherlock/queries/_general.html.haml @@ -27,7 +27,7 @@ .card-header .float-right %button.js-clipboard-trigger.btn.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button } - %i.fa.fa-clipboard + = sprite_icon('duplicate') %pre.hidden = @query.formatted_query %strong @@ -42,7 +42,7 @@ .card-header .float-right %button.js-clipboard-trigger.btn.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button } - %i.fa.fa-clipboard + = sprite_icon('duplicate') %pre.hidden = @query.explain %strong diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index b2ec7166832..8d9e86d02c4 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -23,8 +23,9 @@ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn', title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('exclamation-circle') - = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do - = icon('rss') + - if can?(current_user, :read_user_profile, @user) + = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do + = icon('rss') - if current_user && current_user.admin? = link_to [:admin, @user], class: 'btn btn-default', title: 'View user in admin area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do @@ -40,10 +41,12 @@ = @user.name .cover-desc.member-date - %span.middle-dot-divider - @#{@user.username} - %span.middle-dot-divider - Member since #{@user.created_at.to_date.to_s(:long)} + %p + %span.middle-dot-divider + @#{@user.username} + - if can?(current_user, :read_user_profile, @user) + %span.middle-dot-divider + Member since #{@user.created_at.to_date.to_s(:long)} .cover-desc - unless @user.public_email.blank? @@ -78,30 +81,31 @@ %p.profile-user-bio = @user.bio - .scrolling-tabs-container - .fade-left= icon('angle-left') - .fade-right= icon('angle-right') - %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs - - if profile_tab?(:activity) - %li.js-activity-tab - = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do - Activity - - if profile_tab?(:groups) - %li.js-groups-tab - = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do - Groups - - if profile_tab?(:contributed) - %li.js-contributed-tab - = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do - Contributed projects - - if profile_tab?(:projects) - %li.js-projects-tab - = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do - Personal projects - - if profile_tab?(:snippets) - %li.js-snippets-tab - = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do - Snippets + - unless profile_tabs.empty? + .scrolling-tabs-container + .fade-left= icon('angle-left') + .fade-right= icon('angle-right') + %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs + - if profile_tab?(:activity) + %li.js-activity-tab + = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do + Activity + - if profile_tab?(:groups) + %li.js-groups-tab + = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do + Groups + - if profile_tab?(:contributed) + %li.js-contributed-tab + = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do + Contributed projects + - if profile_tab?(:projects) + %li.js-projects-tab + = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do + Personal projects + - if profile_tab?(:snippets) + %li.js-snippets-tab + = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do + Snippets %div{ class: container_class } .tab-content @@ -137,3 +141,13 @@ .loading-status = spinner + + - if profile_tabs.empty? + .row + .col-12 + .svg-content + = image_tag 'illustrations/profile_private_mode.svg' + .col-12.text-center + .text-content + %h4 + This user has a private profile diff --git a/changelogs/unreleased/29278-commits-page-tooltips.yml b/changelogs/unreleased/29278-commits-page-tooltips.yml new file mode 100644 index 00000000000..d54301a1cf0 --- /dev/null +++ b/changelogs/unreleased/29278-commits-page-tooltips.yml @@ -0,0 +1,5 @@ +--- +title: Remove tooltips from commit author avatar and name in commit lists +merge_request: 20674 +author: +type: other diff --git a/changelogs/unreleased/38604-add-private-profile.yml b/changelogs/unreleased/38604-add-private-profile.yml new file mode 100644 index 00000000000..e40e7d9321e --- /dev/null +++ b/changelogs/unreleased/38604-add-private-profile.yml @@ -0,0 +1,5 @@ +--- +title: Add an option to have a private profile on GitLab. +merge_request: 20387 +author: jxterry +type: added diff --git a/changelogs/unreleased/43011-typecast-markdownversion-prop-notesapp.yml b/changelogs/unreleased/43011-typecast-markdownversion-prop-notesapp.yml new file mode 100644 index 00000000000..b60aeba860a --- /dev/null +++ b/changelogs/unreleased/43011-typecast-markdownversion-prop-notesapp.yml @@ -0,0 +1,5 @@ +--- +title: Fix Vue datatype errors for markdownVersion parsing +merge_request: 20800 +author: +type: fixed diff --git a/changelogs/unreleased/45318-vuex-store.yml b/changelogs/unreleased/45318-vuex-store.yml new file mode 100644 index 00000000000..5ea89034bce --- /dev/null +++ b/changelogs/unreleased/45318-vuex-store.yml @@ -0,0 +1,5 @@ +--- +title: Adds Vuex store for reports section in MR widget +merge_request: 20709 +author: +type: added diff --git a/changelogs/unreleased/api-minimal-access-level.yml b/changelogs/unreleased/api-minimal-access-level.yml new file mode 100644 index 00000000000..43cab246d69 --- /dev/null +++ b/changelogs/unreleased/api-minimal-access-level.yml @@ -0,0 +1,5 @@ +--- +title: Add filter for minimal access level in groups and projects API +merge_request: 20478 +author: Marko, Peter +type: added diff --git a/changelogs/unreleased/blackst0ne-replace-sidekiq-inline-with-perform-enqueued-jobs.yml b/changelogs/unreleased/blackst0ne-replace-sidekiq-inline-with-perform-enqueued-jobs.yml new file mode 100644 index 00000000000..69e6b7d815a --- /dev/null +++ b/changelogs/unreleased/blackst0ne-replace-sidekiq-inline-with-perform-enqueued-jobs.yml @@ -0,0 +1,5 @@ +--- +title: Replace 'Sidekiq::Testing.inline!' with 'perform_enqueued_jobs' +merge_request: 20768 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/frozen-string-danger.yml b/changelogs/unreleased/frozen-string-danger.yml new file mode 100644 index 00000000000..9910139b8a9 --- /dev/null +++ b/changelogs/unreleased/frozen-string-danger.yml @@ -0,0 +1,5 @@ +--- +title: Add Dangerfile for frozen_string_literal +merge_request: 20767 +author: gfyoung +type: performance diff --git a/changelogs/unreleased/frozen-string-enable-app-serializers.yml b/changelogs/unreleased/frozen-string-enable-app-serializers.yml new file mode 100644 index 00000000000..40c7b695d39 --- /dev/null +++ b/changelogs/unreleased/frozen-string-enable-app-serializers.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen string in app/serializers/**/*.rb +merge_request: 20726 +author: gfyoung +type: performance diff --git a/changelogs/unreleased/kp-stacked-progress-bar-decimal-places.yml b/changelogs/unreleased/kp-stacked-progress-bar-decimal-places.yml new file mode 100644 index 00000000000..a2fca4c5b91 --- /dev/null +++ b/changelogs/unreleased/kp-stacked-progress-bar-decimal-places.yml @@ -0,0 +1,5 @@ +--- +title: Show decimal place up to single digit in Stacked Progress Bar +merge_request: 20776 +author: +type: changed diff --git a/changelogs/unreleased/sh-bump-sanitize-4-6-6.yml b/changelogs/unreleased/sh-bump-sanitize-4-6-6.yml new file mode 100644 index 00000000000..b9444440cb9 --- /dev/null +++ b/changelogs/unreleased/sh-bump-sanitize-4-6-6.yml @@ -0,0 +1,5 @@ +--- +title: Bump nokogiri to 1.8.4 and sanitize to 4.6.6 for performance +merge_request: 20795 +author: +type: performance diff --git a/changelogs/unreleased/sh-enable-frozen-literals-banzi-filters.yml b/changelogs/unreleased/sh-enable-frozen-literals-banzi-filters.yml new file mode 100644 index 00000000000..897d673e97d --- /dev/null +++ b/changelogs/unreleased/sh-enable-frozen-literals-banzi-filters.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen strings in remaining lib/banzai/filter/*.rb files +merge_request: 20777 +author: +type: performance diff --git a/changelogs/unreleased/sh-freeze-banzai-filter-strings.yml b/changelogs/unreleased/sh-freeze-banzai-filter-strings.yml new file mode 100644 index 00000000000..37b397ea49f --- /dev/null +++ b/changelogs/unreleased/sh-freeze-banzai-filter-strings.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen strings in lib/banzai/filter/*.rb +merge_request: 20775 +author: +type: performance diff --git a/changelogs/unreleased/tz-mr-refactor-memory-reduction.yml b/changelogs/unreleased/tz-mr-refactor-memory-reduction.yml index 20b72c98bc1..16003fa9cad 100644 --- a/changelogs/unreleased/tz-mr-refactor-memory-reduction.yml +++ b/changelogs/unreleased/tz-mr-refactor-memory-reduction.yml @@ -1,5 +1,5 @@ ---- -title: Reduces the client side memory footprint on merge requests -merge_request: 20744 -author: -type: performance +---
+title: Reduces the client side memory footprint on merge requests
+merge_request: 20744
+author:
+type: performance
diff --git a/changelogs/unreleased/winh-tree-view-gpg.yml b/changelogs/unreleased/winh-tree-view-gpg.yml new file mode 100644 index 00000000000..84d63814a47 --- /dev/null +++ b/changelogs/unreleased/winh-tree-view-gpg.yml @@ -0,0 +1,5 @@ +--- +title: Display GPG status on repository and blob pages +merge_request: 20524 +author: +type: changed diff --git a/changelogs/unreleased/zj-backup-timeout.yml b/changelogs/unreleased/zj-backup-timeout.yml new file mode 100644 index 00000000000..b2ad2ed8c63 --- /dev/null +++ b/changelogs/unreleased/zj-backup-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Disable Gitaly timeouts when creating or restoring backups +merge_request: 20810 +author: +type: fixed diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb index fe37b7710aa..4d8d35bf6cf 100644 --- a/config/initializers/8_metrics.rb +++ b/config/initializers/8_metrics.rb @@ -80,8 +80,6 @@ def instrument_classes(instrumentation) instrumentation.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker) - instrumentation.instrument_instance_methods(Rouge::Plugins::CommonMark) - instrumentation.instrument_instance_methods(Rouge::Plugins::Redcarpet) instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) [:XML, :HTML].each do |namespace| diff --git a/danger/frozen_string/Dangerfile b/danger/frozen_string/Dangerfile new file mode 100644 index 00000000000..595176d597d --- /dev/null +++ b/danger/frozen_string/Dangerfile @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +FILE_EXTENSION = ".rb" +MAGIC_COMMENT = "# frozen_string_literal: true" + +def get_files_with_no_magic_comment(files) + files.select do |file| + file.end_with?(FILE_EXTENSION) && + !File.open(file, &:gets).start_with?(MAGIC_COMMENT) + end +end + +files_to_check = git.added_files +files_to_fix = get_files_with_no_magic_comment(files_to_check) + +if files_to_fix.any? + warn 'This merge request adds files that do not enforce frozen string literal. ' \ + 'See https://gitlab.com/gitlab-org/gitlab-ce/issues/47424 for more information.' + + markdown(<<~MARKDOWN) +## Enable Frozen String Literal + +The following files should have `#{MAGIC_COMMENT}` in the first line: + +* #{files_to_fix.map { |path| "`#{path}`" }.join("\n* ")} + MARKDOWN +end diff --git a/db/migrate/20180722103201_add_private_profile_to_users.rb b/db/migrate/20180722103201_add_private_profile_to_users.rb new file mode 100644 index 00000000000..4f7ef1322d8 --- /dev/null +++ b/db/migrate/20180722103201_add_private_profile_to_users.rb @@ -0,0 +1,10 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddPrivateProfileToUsers < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :users, :private_profile, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 1a5555fb3a4..3db11d8447e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180704204006) do +ActiveRecord::Schema.define(version: 20180722103201) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -2124,6 +2124,7 @@ ActiveRecord::Schema.define(version: 20180704204006) do t.integer "theme_id", limit: 2 t.integer "accepted_term_id" t.string "feed_token" + t.boolean "private_profile" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 056cca17d62..eefa86f8e42 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -124,11 +124,6 @@ The Pages daemon doesn't listen to the outside world. ``` 1. [Reconfigure GitLab][reconfigure] -1. Restart gitlab-pages by running the following command: - - ```shell - sudo gitlab-ctl restart gitlab-pages - ``` Watch the [video tutorial][video-admin] for this configuration. @@ -161,11 +156,6 @@ outside world. respectively. 1. [Reconfigure GitLab][reconfigure] -1. Restart gitlab-pages by running the following command: - - ```shell - sudo gitlab-ctl restart gitlab-pages - ``` ## Advanced configuration @@ -203,11 +193,6 @@ world. Custom domains are supported, but no TLS. listens on. If you don't have IPv6, you can omit the IPv6 address. 1. [Reconfigure GitLab][reconfigure] -1. Restart gitlab-pages by running the following command: - - ```shell - sudo gitlab-ctl restart gitlab-pages - ``` ### Custom domains with TLS support @@ -241,11 +226,6 @@ world. Custom domains and TLS are supported. listens on. If you don't have IPv6, you can omit the IPv6 address. 1. [Reconfigure GitLab][reconfigure] -1. Restart gitlab-pages by running the following command: - - ```shell - sudo gitlab-ctl restart gitlab-pages - ``` ### Custom domain verification @@ -290,11 +270,29 @@ are stored. ``` 1. [Reconfigure GitLab][reconfigure] -1. Restart gitlab-pages by running the following command: + +## Configure listener for reverse proxy requests + +Follow the steps below to configure the proxy listener of GitLab Pages. [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2533) in +Omnibus GitLab 11.1. + +1. By default the listener is configured to listen for requests on `localhost:8090`. + + If you wish to disable it you must configure this in + `/etc/gitlab/gitlab.rb`: ```shell - sudo gitlab-ctl restart gitlab-pages - ``` + gitlab_pages['listen_proxy'] = nil + ``` + + If you wish to make it listen on a different port you must configure this also in + `/etc/gitlab/gitlab.rb`: + + ```shell + gitlab_pages['listen_proxy'] = "localhost:10080" + ``` + +1. [Reconfigure GitLab][reconfigure] ## Set maximum pages size diff --git a/doc/api/groups.md b/doc/api/groups.md index 11de75039ee..87be36cc815 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -10,13 +10,14 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin); Attributes `owned` and `min_access_level` have precedence | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name`, `path` or `id`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | -| `owned` | boolean | no | Limit to groups owned by the current user | +| `owned` | boolean | no | Limit to groups explicitly owned by the current user | +| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) | ``` GET /groups @@ -94,13 +95,14 @@ Parameters: | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) of the parent group | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin); Attributes `owned` and `min_access_level` have precedence | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name`, `path` or `id`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | -| `owned` | boolean | no | Limit to groups owned by the current user | +| `owned` | boolean | no | Limit to groups explicitly owned by the current user | +| `min_access_level` | integer | no | Limit to groups where current user has at least this [access level](members.md) | ``` GET /groups/:id/subgroups diff --git a/doc/api/issues.md b/doc/api/issues.md index 5613cb6d915..92fb3e9c307 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | -| `milestone` | string | no | The milestone title | +| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | @@ -151,7 +151,7 @@ GET /groups/:id/issues?my_reaction_emoji=star | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | -| `milestone` | string | no | The milestone title | +| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | @@ -265,7 +265,7 @@ GET /projects/:id/issues?my_reaction_emoji=star | `iids[]` | Array[integer] | no | Return only the milestone having the given `iid` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | -| `milestone` | string | no | The milestone title | +| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | diff --git a/doc/api/projects.md b/doc/api/projects.md index 9409afc88a8..f360b49c293 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -48,7 +48,7 @@ GET /projects | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of projects matching the search criteria | | `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. | -| `owned` | boolean | no | Limit by projects owned by the current user | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user | | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | @@ -57,6 +57,7 @@ GET /projects | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | | `wiki_checksum_failed` | boolean | no | Limit projects where the wiki checksum calculation has failed _([Introduced][ee-6137] in [GitLab Premium][eep] 11.2)_ | | `repository_checksum_failed` | boolean | no | Limit projects where the repository checksum calculation has failed _([Introduced][ee-6137] in [GitLab Premium][eep] 11.2)_ | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) | When `simple=true` or the user is unauthenticated this returns something like: @@ -273,13 +274,14 @@ GET /users/:user_id/projects | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of projects matching the search criteria | | `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. | -| `owned` | boolean | no | Limit by projects owned by the current user | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user | | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) | ```json [ @@ -769,13 +771,14 @@ GET /projects/:id/forks | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of projects matching the search criteria | | `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned. | -| `owned` | boolean | no | Limit by projects owned by the current user | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user | | `membership` | boolean | no | Limit by projects that the current user is a member of | | `starred` | boolean | no | Limit by projects starred by the current user | | `statistics` | boolean | no | Include project statistics | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_issues_enabled` | boolean | no | Limit by enabled issues feature | | `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/forks" diff --git a/doc/api/users.md b/doc/api/users.md index 72fdaaa2c74..1bcc7b7f346 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -105,7 +105,8 @@ GET /users "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, - "external": false + "external": false, + "private_profile": false }, { "id": 2, @@ -135,7 +136,8 @@ GET /users "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, - "external": false + "external": false, + "private_profile": false } ] ``` @@ -248,7 +250,8 @@ Parameters: "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, - "external": false + "external": false, + "private_profile": false } ``` @@ -288,6 +291,7 @@ Parameters: - `skip_confirmation` (optional) - Skip confirmation - true or false (default) - `external` (optional) - Flags the user as external - true or false(default) - `avatar` (optional) - Image file for user's avatar +- `private_profile (optional) - User's profile is private - true or false ## User modification @@ -318,6 +322,7 @@ Parameters: - `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default) - `external` (optional) - Flags the user as external - true or false(default) - `avatar` (optional) - Image file for user's avatar +- `private_profile (optional) - User's profile is private - true or false On password update, user will be forced to change it upon next login. Note, at the moment this method does only return a `404` error, @@ -382,7 +387,8 @@ GET /user "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, - "external": false + "external": false, + "private_profile": false } ``` @@ -429,7 +435,8 @@ GET /user "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, - "external": false + "external": false, + "private_profile": false } ``` diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 096b64eb881..d95f8c7c8cc 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1411,43 +1411,6 @@ variables: You can set it globally or per-job in the [`variables`](#variables) section. -### Custom build directories - -> [Introduced][gitlab-runner-876] in Gitlab Runner 11.1 - -NOTE: **Note:** -This can only be used when `custom_build_dir` is set to true in the [Runner's -configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html). - -By default, GitLab Runner clones the repository in the `/builds` directory, -but sometimes your project might require to have the code in a specific -directory, like the GO projects for example. In that case, you can specify -the `CI_PROJECT_DIR` variable to tell the Runner in which directory to clone -the repository: - -```yml -image: golang:1.10-alpine3.7 - -variables: - CI_PROJECT_DIR: /go/src/gitlab.com/namespace/project-name - -stages: - - test - -dir: - stage: test - script: - - pwd # /go/src/gitlab.com/namespace/project-name -``` - -The following executors may use this feature only when -[concurrent](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section) -is set to `1`: - -- `shell` -- `ssh` -- `docker`, `docker+machine` when the job's working directory is mounted as a host volume. - ## Special YAML features It's possible to use special YAML features like anchors (`&`), aliases (`*`) @@ -1641,6 +1604,5 @@ CI with various languages. [ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983 [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447 [ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909 -[gitlab-runner-876]: https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/876 [schedules]: ../../user/project/pipelines/schedules.md [variables-expressions]: ../variables/README.md#variables-expressions diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index 91cdef8d1dd..96a08c04905 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -68,6 +68,28 @@ Alternatively, you can follow [this detailed procedure from the GitLab Team Hand which also covers the case where you have projects hosted with [GitLab Pages](../project/pages/index.md). +## Private profile + +The following information will be hidden from the user profile page (https://gitlab.example.com/username) if this feature is enabled: + +- Atom feed +- Date when account is created +- Activity tab +- Groups tab +- Contributed projects tab +- Personal projects tab +- Snippets tab + +To enable private profile: + +1. Navigate to your personal [profile settings](#profile-settings). +1. Check the "Private profile" option. +1. Hit **Update profile settings**. + + +NOTE: **Note:** +You and GitLab admins can see your the abovementioned information on your profile even if it is private. + ## Troubleshooting ### Why do I keep getting signed out? diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 9b4fdd65e2f..601db5f424d 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -48,6 +48,7 @@ the following table. | `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. | | `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). | | `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). | +| `read_repository` | Allows read-access to the repository through git clone). | [2fa]: ../account/two_factor_authentication.md [api]: ../../api/README.md diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 860edb8e6f7..6dfdbe6c0d5 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -27,7 +27,7 @@ You create issues, host code, perform reviews, build, test, and deploy from one single platform. Issue Boards help you to visualize and manage the entire process _in_ GitLab. -With [Multiple Issue Boards](#multiple-issue-boards), available +With [Multiple Issue Boards](#use-cases-for-multiple-issue-boards), available only in [GitLab Enterprise Edition](#features-per-tier), you go even further, as you can not only keep yourself and your project organized from a broader perspective with one Issue Board per project, @@ -75,7 +75,7 @@ each team can have their own board to organize their workflow individually. #### Scrum team -With multiple Issue Boards, each team has one board. Now you can move issues through each +With Multiple Issue Boards, each team has one board. Now you can move issues through each part of the process. For instance: **To Do**, **Doing**, and **Done**. #### Organization of topics diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 464a31ee819..e883687f2db 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -30,7 +30,7 @@ module API end class User < UserBasic - expose :created_at + expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization end @@ -55,6 +55,7 @@ module API expose :can_create_project?, as: :can_create_project expose :two_factor_enabled?, as: :two_factor_enabled expose :external + expose :private_profile end class UserWithAdmin < UserPublic diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 797b04df059..b4f441f6a4f 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -34,11 +34,12 @@ module API optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user' optional :order_by, type: String, values: %w[name path id], default: 'name', desc: 'Order by name, path or id' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user' use :pagination end def find_groups(params, parent_id = nil) - find_params = params.slice(:all_available, :custom_attributes, :owned) + find_params = params.slice(:all_available, :custom_attributes, :owned, :min_access_level) find_params[:parent] = find_group!(parent_id) if parent_id find_params[:all_available] = find_params.fetch(:all_available, current_user&.full_private_access?) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index f7737468148..be17653dbb2 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -389,6 +389,7 @@ module API finder_params[:search] = params[:search] if params[:search] finder_params[:user] = params.delete(:user) if params[:user] finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes] + finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level] finder_params end diff --git a/lib/api/keys.rb b/lib/api/keys.rb index 767f27ef334..fd93f797f72 100644 --- a/lib/api/keys.rb +++ b/lib/api/keys.rb @@ -12,7 +12,7 @@ module API key = Key.find(params[:id]) - present key, with: Entities::SSHKeyWithUser + present key, with: Entities::SSHKeyWithUser, current_user: current_user end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 889e3d4f819..eadde7b17bb 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -54,6 +54,7 @@ module API optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of' optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' use :optional_filter_params_ee end diff --git a/lib/api/users.rb b/lib/api/users.rb index 5aaaf104dff..e83887b3e9e 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -42,6 +42,8 @@ module API optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' optional :avatar, type: File, desc: 'Avatar image for user' + optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' all_or_none_of :extern_uid, :provider end @@ -96,7 +98,7 @@ module API entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin - users, options = with_custom_attributes(users, with: entity) + users, options = with_custom_attributes(users, { with: entity, current_user: current_user }) present paginate(users), options end @@ -113,7 +115,7 @@ module API user = User.find_by(id: params[:id]) not_found!('User') unless user && can?(current_user, :read_user, user) - opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : { with: Entities::User } + opts = { with: current_user&.admin? ? Entities::UserWithAdmin : Entities::User, current_user: current_user } user, opts = with_custom_attributes(user, opts) present user, opts @@ -139,7 +141,7 @@ module API user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true) if user.persisted? - present user, with: Entities::UserPublic + present user, with: Entities::UserPublic, current_user: current_user else conflict!('Email has already been taken') if User .where(email: user.email) @@ -198,7 +200,7 @@ module API result = ::Users::UpdateService.new(current_user, user_params.except(:extern_uid, :provider).merge(user: user)).execute if result[:status] == :success - present user, with: Entities::UserPublic + present user, with: Entities::UserPublic, current_user: current_user else render_validation_error!(user) end @@ -545,7 +547,7 @@ module API Entities::UserPublic end - present current_user, with: entity + present current_user, with: entity, current_user: current_user end end diff --git a/lib/banzai/filter/absolute_link_filter.rb b/lib/banzai/filter/absolute_link_filter.rb index 1ec6201523f..04ec568eee3 100644 --- a/lib/banzai/filter/absolute_link_filter.rb +++ b/lib/banzai/filter/absolute_link_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'uri' module Banzai diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index b39b11009b3..ad0806df8e6 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # Issues, Merge Requests, Snippets, Commits and Commit Ranges share diff --git a/lib/banzai/filter/ascii_doc_post_processing_filter.rb b/lib/banzai/filter/ascii_doc_post_processing_filter.rb index c9fcf057c5f..88439f06b5f 100644 --- a/lib/banzai/filter/ascii_doc_post_processing_filter.rb +++ b/lib/banzai/filter/ascii_doc_post_processing_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class AsciiDocPostProcessingFilter < HTML::Pipeline::Filter diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb index 4a143baeef6..deda4b1872e 100644 --- a/lib/banzai/filter/autolink_filter.rb +++ b/lib/banzai/filter/autolink_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'uri' module Banzai diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb index 7108e828c6d..ad367cc5efe 100644 --- a/lib/banzai/filter/blockquote_fence_filter.rb +++ b/lib/banzai/filter/blockquote_fence_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class BlockquoteFenceFilter < HTML::Pipeline::TextFilter diff --git a/lib/banzai/filter/color_filter.rb b/lib/banzai/filter/color_filter.rb index 6ab29ac281f..6d9bdb9cbd3 100644 --- a/lib/banzai/filter/color_filter.rb +++ b/lib/banzai/filter/color_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that renders `color` followed by a color "chip". diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index 01b3b0dafb9..d6b46236a49 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces commit range references with links. diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index 8cd92a1adba..c3e5ac41cb8 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces commit references with links. diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb index 7b55e8b36f6..f49c4b403db 100644 --- a/lib/banzai/filter/commit_trailers_filter.rb +++ b/lib/banzai/filter/commit_trailers_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces users' names and emails in commit trailers diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 4eccd9d5ed5..c87948a30bf 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces :emoji: and unicode with images. diff --git a/lib/banzai/filter/epic_reference_filter.rb b/lib/banzai/filter/epic_reference_filter.rb index 265924abe24..e06e2fb3870 100644 --- a/lib/banzai/filter/epic_reference_filter.rb +++ b/lib/banzai/filter/epic_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # The actual filter is implemented in the EE mixin diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index ed01a72ff9f..b4a7a44e109 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces external issue tracker references with links. diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index d6327ef31cb..2e6d742de27 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML Filter to modify the attributes of external links diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index bb9f488cd87..0c1bbd2d250 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML Filter for parsing Gollum's tags in HTML. It's only parses the diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index e008fd428b0..406c2d3c96b 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'erb' module Banzai diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb index 4cd9b02b76c..afaee70f351 100644 --- a/lib/banzai/filter/image_lazy_load_filter.rb +++ b/lib/banzai/filter/image_lazy_load_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that moves the value of image `src` attributes to `data-src` @@ -5,7 +7,7 @@ module Banzai class ImageLazyLoadFilter < HTML::Pipeline::Filter def call doc.xpath('descendant-or-self::img').each do |img| - img['class'] ||= '' << 'lazy' + img.add_class('lazy') img['data-src'] = img['src'] img['src'] = LazyImageTagHelper.placeholder_image end diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index f318c425962..884a94fb761 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that wraps links around inline images. diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb index 73e82a4d7e3..e9ddc6e0e3d 100644 --- a/lib/banzai/filter/inline_diff_filter.rb +++ b/lib/banzai/filter/inline_diff_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class InlineDiffFilter < HTML::Pipeline::Filter diff --git a/lib/banzai/filter/issuable_reference_filter.rb b/lib/banzai/filter/issuable_reference_filter.rb index 7addf09be73..2963cba91e8 100644 --- a/lib/banzai/filter/issuable_reference_filter.rb +++ b/lib/banzai/filter/issuable_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class IssuableReferenceFilter < AbstractReferenceFilter diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb index 1a415232545..d7fe012883d 100644 --- a/lib/banzai/filter/issuable_state_filter.rb +++ b/lib/banzai/filter/issuable_state_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that appends state information to issuable links. diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 6877cae8c55..f85be042999 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces issue references with links. References to diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index a5f38046a43..b92e9e55bb9 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces label references with links. diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb index 944363f17d3..cdf758472c1 100644 --- a/lib/banzai/filter/markdown_filter.rb +++ b/lib/banzai/filter/markdown_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class MarkdownFilter < HTML::Pipeline::TextFilter diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb index b6e784c886b..9d1bc3cf60c 100644 --- a/lib/banzai/filter/math_filter.rb +++ b/lib/banzai/filter/math_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'uri' module Banzai diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb index 10c40568006..7098767b583 100644 --- a/lib/banzai/filter/merge_request_reference_filter.rb +++ b/lib/banzai/filter/merge_request_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces merge request references with links. References diff --git a/lib/banzai/filter/mermaid_filter.rb b/lib/banzai/filter/mermaid_filter.rb index 65c131e08d9..7c8b165a330 100644 --- a/lib/banzai/filter/mermaid_filter.rb +++ b/lib/banzai/filter/mermaid_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class MermaidFilter < HTML::Pipeline::Filter diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index af8448937b3..328c8c1803b 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces milestone references with links. diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb index 28933c78966..caba8955bac 100644 --- a/lib/banzai/filter/plantuml_filter.rb +++ b/lib/banzai/filter/plantuml_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "nokogiri" require "asciidoctor-plantuml/plantuml" diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb index caf11fe94c4..1f091f594f8 100644 --- a/lib/banzai/filter/redactor_filter.rb +++ b/lib/banzai/filter/redactor_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that removes references to records that the current user does diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 2411dd2cdfc..e5164e7f72a 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # Base class for GitLab Flavored Markdown reference filters. diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 262458a872a..8e838d04bad 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'uri' module Banzai diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 8275bb9e149..80b9d3d045f 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # Sanitize HTML diff --git a/lib/banzai/filter/set_direction_filter.rb b/lib/banzai/filter/set_direction_filter.rb index c2976aeb7c6..45b259a2faf 100644 --- a/lib/banzai/filter/set_direction_filter.rb +++ b/lib/banzai/filter/set_direction_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that sets dir="auto" for RTL languages support diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb index 881e10afb9f..f4b6edb6174 100644 --- a/lib/banzai/filter/snippet_reference_filter.rb +++ b/lib/banzai/filter/snippet_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces snippet references with links. References to diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 6dbf0d68fe8..8a7f9045c24 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rouge/plugins/common_mark' require 'rouge/plugins/redcarpet' @@ -15,7 +17,7 @@ module Banzai end def highlight_node(node) - css_classes = 'code highlight js-syntax-highlight' + css_classes = +'code highlight js-syntax-highlight' lang = node.attr('lang') retried = false diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb index b32660a8341..c6d1e028eaa 100644 --- a/lib/banzai/filter/table_of_contents_filter.rb +++ b/lib/banzai/filter/table_of_contents_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that adds an anchor child element to all Headers in a @@ -19,7 +21,7 @@ module Banzai def call return doc if context[:no_header_anchors] - result[:toc] = "" + result[:toc] = +"" headers = Hash.new(0) header_root = current_header = HeaderNode.new diff --git a/lib/banzai/filter/task_list_filter.rb b/lib/banzai/filter/task_list_filter.rb index 9fa5f589f3e..ef35a49edcb 100644 --- a/lib/banzai/filter/task_list_filter.rb +++ b/lib/banzai/filter/task_list_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'task_list/filter' module Banzai diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index c7fa8a8119f..11960047e5b 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # HTML filter that replaces user or group references with links. diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb index 35cb10eae5d..0fb59c914c3 100644 --- a/lib/banzai/filter/video_link_filter.rb +++ b/lib/banzai/filter/video_link_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter # Find every image that isn't already wrapped in an `a` tag, and that has diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb index 269d5bf74fa..870721f895d 100644 --- a/lib/banzai/filter/wiki_link_filter.rb +++ b/lib/banzai/filter/wiki_link_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'uri' module Banzai diff --git a/lib/banzai/filter/yaml_front_matter_filter.rb b/lib/banzai/filter/yaml_front_matter_filter.rb index 58e3e81209e..295964dd75d 100644 --- a/lib/banzai/filter/yaml_front_matter_filter.rb +++ b/lib/banzai/filter/yaml_front_matter_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class YamlFrontMatterFilter < HTML::Pipeline::Filter diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 21ac43f80fd..0356e8efc5c 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -353,8 +353,6 @@ module Gitlab # offset: 5, # after: Time.new(2016, 4, 21, 14, 32, 10) # ) - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/446 def log(options) default_options = { limit: 10, @@ -826,6 +824,10 @@ module Gitlab end end + # This method, fetch_ref, is used from within + # Gitlab::Git::OperationService. OperationService will eventually only + # exist in gitaly-ruby. When we delete OperationService from gitlab-ce + # we can also remove fetch_ref. def fetch_ref(source_repository, source_ref:, target_ref:) Gitlab::Git.check_namespace!(source_repository) source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository) @@ -1025,8 +1027,8 @@ module Gitlab end def clean_stale_repository_files - gitaly_migrate(:repository_cleanup, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - gitaly_repository_client.cleanup if is_enabled && exists? + wrapped_gitaly_errors do + gitaly_repository_client.cleanup if exists? end rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup Rails.logger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}") diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 58a4060cc96..c27972a84a4 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -407,7 +407,7 @@ module Gitlab # The default timeout on all Gitaly calls def self.default_timeout - return 0 if Sidekiq.server? + return no_timeout if Sidekiq.server? timeout(:gitaly_timeout_default) end @@ -420,6 +420,10 @@ module Gitlab timeout(:gitaly_timeout_medium) end + def self.no_timeout + 0 + end + def self.timeout(timeout_name) Gitlab::CurrentSettings.current_application_settings[timeout_name] end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 64b9af4d70c..2956ed4b911 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -202,7 +202,7 @@ module Gitlab save_path, :create_bundle, Gitaly::CreateBundleRequest, - GitalyClient.default_timeout + GitalyClient.no_timeout ) end @@ -220,7 +220,7 @@ module Gitlab bundle_path, :create_repository_from_bundle, Gitaly::CreateRepositoryFromBundleRequest, - GitalyClient.default_timeout + GitalyClient.no_timeout ) end @@ -245,7 +245,7 @@ module Gitlab :repository_service, :create_repository_from_snapshot, request, - timeout: GitalyClient.default_timeout + timeout: GitalyClient.no_timeout ) end diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb index f3d7407383c..d0527f014a7 100644 --- a/lib/gitlab/import_export/merge_request_parser.rb +++ b/lib/gitlab/import_export/merge_request_parser.rb @@ -25,6 +25,7 @@ module Gitlab @project.repository.create_branch(@merge_request.target_branch, @merge_request.target_branch_sha) end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1295 def fetch_ref @project.repository.fetch_ref(@project.repository, source_ref: @diff_head_sha, target_ref: @merge_request.source_branch) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 75b88a2cb2f..09a35b5da07 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -465,7 +465,7 @@ msgstr "" msgid "An error occurred while importing project: ${details}" msgstr "" -msgid "An error occurred while loading commits" +msgid "An error occurred while loading commit signatures" msgstr "" msgid "An error occurred while loading diff" diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index b0acf4a49ac..071f96a729e 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe UsersController do let(:user) { create(:user) } + let(:private_user) { create(:user, private_profile: true) } + let(:public_user) { create(:user) } describe 'GET #show' do context 'with rendered views' do @@ -98,16 +100,47 @@ describe UsersController do expect(assigns(:events)).to be_empty end + + it 'hides events if the user has a private profile' do + Gitlab::DataBuilder::Push.build_sample(project, private_user) + + get :show, username: private_user.username, format: :json + + expect(assigns(:events)).to be_empty + end end end describe 'GET #calendar' do - it 'renders calendar' do - sign_in(user) + context 'for user' do + let(:project) { create(:project) } + + before do + sign_in(user) + project.add_developer(user) + end + + context 'with public profile' do + it 'renders calendar' do + push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) + EventCreateService.new.push(project, public_user, push_data) + + get :calendar, username: public_user.username, format: :json - get :calendar, username: user.username, format: :json + expect(response).to have_gitlab_http_status(200) + end + end + + context 'with private profile' do + it 'does not render calendar' do + push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user) + EventCreateService.new.push(project, private_user, push_data) - expect(response).to have_gitlab_http_status(200) + get :calendar, username: private_user.username, format: :json + + expect(response).to have_gitlab_http_status(:not_found) + end + end end context 'forked project' do @@ -150,9 +183,26 @@ describe UsersController do expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) end - it 'renders calendar_activities' do - get :calendar_activities, username: user.username - expect(response).to render_template('calendar_activities') + context 'for user' do + context 'with public profile' do + it 'renders calendar_activities' do + push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) + EventCreateService.new.push(project, public_user, push_data) + + get :calendar_activities, username: public_user.username + expect(assigns[:events]).not_to be_empty + end + end + + context 'with private profile' do + it 'does not render calendar_activities' do + push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user) + EventCreateService.new.push(project, private_user, push_data) + + get :calendar_activities, username: private_user.username + expect(response).to have_gitlab_http_status(:not_found) + end + end end end diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 23d8d606790..534cfe1eb12 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -238,6 +238,5 @@ def check_author_link(email, author) author_link = find('.commit-author-link') expect(author_link['href']).to eq(user_path(author)) - expect(author_link['title']).to eq(email) expect(find('.commit-author-name').text).to eq(author.name) end diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb index 3d05474dca2..5003eb508c2 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -7,7 +7,7 @@ describe 'GPG signed commits', :js do user = create :user, email: 'unrelated.user@example.org' project.add_maintainer(user) - Sidekiq::Testing.inline! do + perform_enqueued_jobs do create :gpg_key, key: GpgHelpers::User1.public_key, user: user end @@ -21,7 +21,7 @@ describe 'GPG signed commits', :js do end # user changes his email which makes the gpg key verified - Sidekiq::Testing.inline! do + perform_enqueued_jobs do user.skip_reconfirmation! user.update!(email: GpgHelpers::User1.emails.first) end @@ -48,7 +48,7 @@ describe 'GPG signed commits', :js do end # user adds the gpg key which makes the signature valid - Sidekiq::Testing.inline! do + perform_enqueued_jobs do create :gpg_key, key: GpgHelpers::User1.public_key, user: user end @@ -66,7 +66,7 @@ describe 'GPG signed commits', :js do end let(:user_1_key) do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do create :gpg_key, key: GpgHelpers::User1.public_key, user: user_1 end end @@ -79,7 +79,7 @@ describe 'GPG signed commits', :js do end let(:user_2_key) do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do create :gpg_key, key: GpgHelpers::User2.public_key, user: user_2 end end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 3e2fb704bc6..207c333c636 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -3,15 +3,53 @@ require 'spec_helper' describe 'User page' do let(:user) { create(:user) } - it 'shows all the tabs' do - visit(user_path(user)) - - page.within '.nav-links' do - expect(page).to have_link('Activity') - expect(page).to have_link('Groups') - expect(page).to have_link('Contributed projects') - expect(page).to have_link('Personal projects') - expect(page).to have_link('Snippets') + context 'with public profile' do + it 'shows all the tabs' do + visit(user_path(user)) + + page.within '.nav-links' do + expect(page).to have_link('Activity') + expect(page).to have_link('Groups') + expect(page).to have_link('Contributed projects') + expect(page).to have_link('Personal projects') + expect(page).to have_link('Snippets') + end + end + + it 'does not show private profile message' do + visit(user_path(user)) + + expect(page).not_to have_content("This user has a private profile") + end + end + + context 'with private profile' do + let(:user) { create(:user, private_profile: true) } + + it 'shows no tab' do + visit(user_path(user)) + + expect(page).to have_css("div.profile-header") + expect(page).not_to have_css("ul.nav-links") + end + + it 'shows private profile message' do + visit(user_path(user)) + + expect(page).to have_content("This user has a private profile") + end + + it 'shows own tabs' do + sign_in(user) + visit(user_path(user)) + + page.within '.nav-links' do + expect(page).to have_link('Activity') + expect(page).to have_link('Groups') + expect(page).to have_link('Contributed projects') + expect(page).to have_link('Personal projects') + expect(page).to have_link('Snippets') + end end end diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index da043f94021..58470f4c84d 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -29,11 +29,22 @@ describe UserRecentEventsFinder do public_project.add_developer(current_user) end - it 'returns all the events' do - expect(finder.execute).to include(private_event, internal_event, public_event) + context 'when profile is public' do + it 'returns all the events' do + expect(finder.execute).to include(private_event, internal_event, public_event) + end + end + + context 'when profile is private' do + it 'returns no event' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false) + expect(finder.execute).to be_empty + end end it 'does not include the events if the user cannot read cross project' do + expect(Ability).to receive(:allowed?).and_call_original expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } expect(finder.execute).to be_empty end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index fee8df10129..630f3eff258 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -121,6 +121,8 @@ describe ButtonHelper do end describe 'clipboard_button' do + include IconsHelper + let(:user) { create(:user) } let(:project) { build_stubbed(:project) } @@ -145,7 +147,7 @@ describe ButtonHelper do expect(element.attr('data-clipboard-text')).to eq(nil) expect(element.inner_text).to eq("") - expect(element).to have_selector('.fa.fa-clipboard') + expect(element.to_html).to include sprite_icon('duplicate') end end @@ -178,7 +180,7 @@ describe ButtonHelper do context 'with `hide_button_icon` attribute provided' do it 'shows copy to clipboard button without tooltip support' do - expect(element(hide_button_icon: true)).not_to have_selector('.fa.fa-clipboard') + expect(element(hide_button_icon: true).to_html).not_to include sprite_icon('duplicate') end end end diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index b18c045848f..b079802cb81 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -25,8 +25,20 @@ describe UsersHelper do allow(helper).to receive(:can?).and_return(true) end - it 'includes all the expected tabs' do - expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets) + context 'with public profile' do + it 'includes all the expected tabs' do + expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets) + end + end + + context 'with private profile' do + before do + allow(helper).to receive(:can?).with(user, :read_user_profile, nil).and_return(false) + end + + it 'is empty' do + expect(tabs).to be_empty + end end end diff --git a/spec/javascripts/gpg_badges_spec.js b/spec/javascripts/gpg_badges_spec.js index 97c771dcfd3..78330dd9633 100644 --- a/spec/javascripts/gpg_badges_spec.js +++ b/spec/javascripts/gpg_badges_spec.js @@ -1,23 +1,27 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import GpgBadges from '~/gpg_badges'; +import { TEST_HOST } from 'spec/test_constants'; describe('GpgBadges', () => { let mock; const dummyCommitSha = 'n0m0rec0ffee'; const dummyBadgeHtml = 'dummy html'; const dummyResponse = { - signatures: [{ - commit_sha: dummyCommitSha, - html: dummyBadgeHtml, - }], + signatures: [ + { + commit_sha: dummyCommitSha, + html: dummyBadgeHtml, + }, + ], }; + const dummyUrl = `${TEST_HOST}/dummy/signatures`; beforeEach(() => { mock = new MockAdapter(axios); setFixtures(` <form - class="commits-search-form js-signature-container" data-signatures-path="/hello" action="/hello" + class="commits-search-form js-signature-container" data-signatures-path="${dummyUrl}" action="${dummyUrl}" method="get"> <input name="utf8" type="hidden" value="✓"> <input type="search" name="search" id="commits-search"class="form-control search-text-input input-short"> @@ -32,25 +36,55 @@ describe('GpgBadges', () => { mock.restore(); }); - it('displays a loading spinner', (done) => { - mock.onGet('/hello').reply(200); + it('does not make a request if there is no container element', done => { + setFixtures(''); + spyOn(axios, 'get'); - GpgBadges.fetch().then(() => { - expect(document.querySelector('.js-loading-gpg-badge:empty')).toBe(null); - const spinners = document.querySelectorAll('.js-loading-gpg-badge i.fa.fa-spinner.fa-spin'); - expect(spinners.length).toBe(1); - done(); - }).catch(done.fail); + GpgBadges.fetch() + .then(() => { + expect(axios.get).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); - it('replaces the loading spinner', (done) => { - mock.onGet('/hello').reply(200, dummyResponse); + it('throws an error if the endpoint is missing', done => { + setFixtures('<div class="js-signature-container"></div>'); + spyOn(axios, 'get'); - GpgBadges.fetch().then(() => { - expect(document.querySelector('.js-loading-gpg-badge')).toBe(null); - const parentContainer = document.querySelector('.parent-container'); - expect(parentContainer.innerHTML.trim()).toEqual(dummyBadgeHtml); - done(); - }).catch(done.fail); + GpgBadges.fetch() + .then(() => done.fail('Expected error to be thrown')) + .catch(error => { + expect(error.message).toBe('Missing commit signatures endpoint!'); + expect(axios.get).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('displays a loading spinner', done => { + mock.onGet(dummyUrl).replyOnce(200); + + GpgBadges.fetch() + .then(() => { + expect(document.querySelector('.js-loading-gpg-badge:empty')).toBe(null); + const spinners = document.querySelectorAll('.js-loading-gpg-badge i.fa.fa-spinner.fa-spin'); + expect(spinners.length).toBe(1); + done(); + }) + .catch(done.fail); + }); + + it('replaces the loading spinner', done => { + mock.onGet(dummyUrl).replyOnce(200, dummyResponse); + + GpgBadges.fetch() + .then(() => { + expect(document.querySelector('.js-loading-gpg-badge')).toBe(null); + const parentContainer = document.querySelector('.parent-container'); + expect(parentContainer.innerHTML.trim()).toEqual(dummyBadgeHtml); + done(); + }) + .catch(done.fail); }); }); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 41ff59949e5..71b26a315af 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -627,4 +627,23 @@ describe('common_utils', () => { }); }); }); + + describe('roundOffFloat', () => { + it('Rounds off decimal places of a float number with provided precision', () => { + expect(commonUtils.roundOffFloat(3.141592, 3)).toBe(3.142); + }); + + it('Rounds off a float number to a whole number when provided precision is zero', () => { + expect(commonUtils.roundOffFloat(3.141592, 0)).toBe(3); + expect(commonUtils.roundOffFloat(3.5, 0)).toBe(4); + }); + + it('Rounds off float number to nearest 0, 10, 100, 1000 and so on when provided precision is below 0', () => { + expect(commonUtils.roundOffFloat(34567.14159, -1)).toBe(34570); + expect(commonUtils.roundOffFloat(34567.14159, -2)).toBe(34600); + expect(commonUtils.roundOffFloat(34567.14159, -3)).toBe(35000); + expect(commonUtils.roundOffFloat(34567.14159, -4)).toBe(30000); + expect(commonUtils.roundOffFloat(34567.14159, -5)).toBe(0); + }); + }); }); diff --git a/spec/javascripts/reports/store/actions_spec.js b/spec/javascripts/reports/store/actions_spec.js new file mode 100644 index 00000000000..c714c5af156 --- /dev/null +++ b/spec/javascripts/reports/store/actions_spec.js @@ -0,0 +1,130 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { + setEndpoint, + requestReports, + fetchReports, + stopPolling, + clearEtagPoll, + receiveReportsSuccess, + receiveReportsError, +} from '~/reports/store/actions'; +import state from '~/reports/store/state'; +import * as types from '~/reports/store/mutation_types'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { TEST_HOST } from 'spec/test_constants'; + +describe('Reports Store Actions', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('setEndpoint', () => { + it('should commit SET_ENDPOINT mutation', done => { + testAction( + setEndpoint, + 'endpoint.json', + mockedState, + [{ type: types.SET_ENDPOINT, payload: 'endpoint.json' }], + [], + done, + ); + }); + }); + + describe('requestReports', () => { + it('should commit REQUEST_REPORTS mutation', done => { + testAction(requestReports, null, mockedState, [{ type: types.REQUEST_REPORTS }], [], done); + }); + }); + + describe('fetchReports', () => { + let mock; + + beforeEach(() => { + mockedState.endpoint = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + stopPolling(); + clearEtagPoll(); + }); + + describe('success', () => { + it('dispatches requestReports and receiveReportsSuccess ', done => { + mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { summary: {}, suites: [{ name: 'rspec' }] }); + + testAction( + fetchReports, + null, + mockedState, + [], + [ + { + type: 'requestReports', + }, + { + payload: { summary: {}, suites: [{ name: 'rspec' }] }, + type: 'receiveReportsSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + }); + + it('dispatches requestReports and receiveReportsError ', done => { + testAction( + fetchReports, + null, + mockedState, + [], + [ + { + type: 'requestReports', + }, + { + type: 'receiveReportsError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveReportsSuccess', () => { + it('should commit RECEIVE_REPORTS_SUCCESS mutation', done => { + testAction( + receiveReportsSuccess, + { summary: {} }, + mockedState, + [{ type: types.RECEIVE_REPORTS_SUCCESS, payload: { summary: {} } }], + [], + done, + ); + }); + }); + + describe('receiveReportsError', () => { + it('should commit RECEIVE_REPORTS_ERROR mutation', done => { + testAction( + receiveReportsError, + null, + mockedState, + [{ type: types.RECEIVE_REPORTS_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/reports/store/mutations_spec.js b/spec/javascripts/reports/store/mutations_spec.js new file mode 100644 index 00000000000..3e0b15438c3 --- /dev/null +++ b/spec/javascripts/reports/store/mutations_spec.js @@ -0,0 +1,101 @@ +import state from '~/reports/store/state'; +import mutations from '~/reports/store/mutations'; +import * as types from '~/reports/store/mutation_types'; + +describe('Reports Store Mutations', () => { + let stateCopy; + + beforeEach(() => { + stateCopy = state(); + }); + + describe('SET_ENDPOINT', () => { + it('should set endpoint', () => { + mutations[types.SET_ENDPOINT](stateCopy, 'endpoint.json'); + expect(stateCopy.endpoint).toEqual('endpoint.json'); + }); + }); + + describe('REQUEST_REPORTS', () => { + it('should set isLoading to true', () => { + mutations[types.REQUEST_REPORTS](stateCopy); + expect(stateCopy.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_REPORTS_SUCCESS', () => { + const mockedResponse = { + summary: { + total: 14, + resolved: 0, + failed: 7, + }, + suites: [ + { + name: 'build:linux', + summary: { + total: 2, + resolved: 0, + failed: 1, + }, + new_failures: [ + { + name: 'StringHelper#concatenate when a is git and b is lab returns summary', + execution_time: 0.0092435, + system_output: + 'Failure/Error: is_expected.to eq(\'gitlab\')', + }, + ], + resolved_failures: [ + { + name: 'StringHelper#concatenate when a is git and b is lab returns summary', + execution_time: 0.009235, + system_output: + 'Failure/Error: is_expected.to eq(\'gitlab\')', + }, + ], + existing_failures: [ + { + name: 'StringHelper#concatenate when a is git and b is lab returns summary', + execution_time: 1232.08, + system_output: + 'Failure/Error: is_expected.to eq(\'gitlab\')', + }, + ], + }, + ], + }; + + beforeEach(() => { + mutations[types.RECEIVE_REPORTS_SUCCESS](stateCopy, mockedResponse); + }); + + it('should reset isLoading', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('should set summary counts', () => { + expect(stateCopy.summary.total).toEqual(mockedResponse.summary.total); + expect(stateCopy.summary.resolved).toEqual(mockedResponse.summary.resolved); + expect(stateCopy.summary.failed).toEqual(mockedResponse.summary.failed); + }); + + it('should set reports', () => { + expect(stateCopy.reports).toEqual(mockedResponse.suites); + }); + }); + + describe('RECEIVE_REPORTS_ERROR', () => { + beforeEach(() => { + mutations[types.RECEIVE_REPORTS_ERROR](stateCopy); + }); + it('should reset isLoading', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('should set hasError to true', () => { + expect(stateCopy.hasError).toEqual(true); + }); + + }); +}); diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js index 97f0fbb04db..e135690349e 100644 --- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js +++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js @@ -21,7 +21,7 @@ describe('clipboard button', () => { it('renders a button for clipboard', () => { expect(vm.$el.tagName).toEqual('BUTTON'); expect(vm.$el.getAttribute('data-clipboard-text')).toEqual('copy me'); - expect(vm.$el.querySelector('i').className).toEqual('fa fa-clipboard'); + expect(vm.$el).toHaveSpriteIcon('duplicate'); }); it('should have a tooltip with default values', () => { diff --git a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js index de3bf667fb3..076d940961d 100644 --- a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js +++ b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js @@ -10,9 +10,9 @@ const createComponent = (config) => { successLabel: 'Synced', failureLabel: 'Failed', neutralLabel: 'Out of sync', - successCount: 10, - failureCount: 5, - totalCount: 20, + successCount: 25, + failureCount: 10, + totalCount: 5000, }, config); return mountComponent(Component, defaultConfig); @@ -32,7 +32,7 @@ describe('StackedProgressBarComponent', () => { describe('computed', () => { describe('neutralCount', () => { it('returns neutralCount based on totalCount, successCount and failureCount', () => { - expect(vm.neutralCount).toBe(5); // 20 - 10 - 5 + expect(vm.neutralCount).toBe(4965); // 5000 - 25 - 10 }); }); }); @@ -40,7 +40,11 @@ describe('StackedProgressBarComponent', () => { describe('methods', () => { describe('getPercent', () => { it('returns percentage from provided count based on `totalCount`', () => { - expect(vm.getPercent(10)).toBe(50); + expect(vm.getPercent(500)).toBe(10); + }); + + it('returns percentage with decimal place from provided count based on `totalCount`', () => { + expect(vm.getPercent(10)).toBe(0.2); }); }); diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb index 41f957c4e00..d06c5535309 100644 --- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb +++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb @@ -7,6 +7,20 @@ describe Banzai::Filter::ImageLazyLoadFilter do %(<img src="#{path}" />) end + def image_with_class(path, class_attr = nil) + %(<img src="#{path}" class="#{class_attr}"/>) + end + + it 'adds a class attribute' do + doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('img')['class']).to eq 'lazy' + end + + it 'appends to the current class attribute' do + doc = filter(image_with_class('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg', 'test')) + expect(doc.at_css('img')['class']).to eq 'test lazy' + end + it 'transforms the image src to a data-src' do doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) expect(doc.at_css('img')['data-src']).to eq '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb index 813ae43b4d3..7eac2cacb90 100644 --- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb +++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb @@ -65,7 +65,7 @@ describe Gitlab::HashedStorage::Migrator do end it 'migrate project' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do subject.migrate(project) end diff --git a/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb b/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb index 4395e2f8264..5c6f213e15b 100644 --- a/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb +++ b/spec/migrations/active_record/schedule_set_confidential_note_events_on_services_spec.rb @@ -31,7 +31,7 @@ describe ScheduleSetConfidentialNoteEventsOnServices, :migration, :sidekiq do end it 'correctly processes services' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect(services_table.where(confidential_note_events: nil).count).to eq 4 expect(services_table.where(confidential_note_events: true).count).to eq 1 diff --git a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb index a837498e1b1..dd6f5325750 100644 --- a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb +++ b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb @@ -44,7 +44,7 @@ describe MigrateStageIdReferenceInBackground, :migration, :sidekiq do end it 'schedules background migrations' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect(jobs.where(stage_id: nil).count).to eq 5 migrate! diff --git a/spec/migrations/migrate_stages_statuses_spec.rb b/spec/migrations/migrate_stages_statuses_spec.rb index ce35276cbf5..5483e24fce7 100644 --- a/spec/migrations/migrate_stages_statuses_spec.rb +++ b/spec/migrations/migrate_stages_statuses_spec.rb @@ -34,7 +34,7 @@ describe MigrateStagesStatuses, :sidekiq, :migration do end it 'correctly migrates stages statuses' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect(stages.where(status: nil).count).to eq 3 migrate! diff --git a/spec/migrations/normalize_ldap_extern_uids_spec.rb b/spec/migrations/normalize_ldap_extern_uids_spec.rb index 56a78f52802..c6ea1e3e49e 100644 --- a/spec/migrations/normalize_ldap_extern_uids_spec.rb +++ b/spec/migrations/normalize_ldap_extern_uids_spec.rb @@ -38,7 +38,7 @@ describe NormalizeLdapExternUids, :migration, :sidekiq do end it 'migrates the LDAP identities' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do migrate! identities.where(id: 1..4).each do |identity| expect(identity.extern_uid).to eq("uid=foo #{identity.id},ou=people,dc=example,dc=com") @@ -47,7 +47,7 @@ describe NormalizeLdapExternUids, :migration, :sidekiq do end it 'does not modify non-LDAP identities' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do migrate! identity = identities.last expect(identity.extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") diff --git a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb index ed306fb3d62..96bef107599 100644 --- a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb +++ b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb @@ -20,7 +20,7 @@ describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do end it 'schedules background migrations' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect(GpgKeySubkey.count).to eq(0) migrate! diff --git a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb index d230f064444..9f7e47bae0d 100644 --- a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb +++ b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb @@ -33,7 +33,7 @@ describe ScheduleMergeRequestDiffMigrations, :migration, :sidekiq do end it 'schedules background migrations' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' expect(merge_request_diffs.where(non_empty).count).to eq 3 diff --git a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb index 1aab4ae1650..5bcb923af7b 100644 --- a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb +++ b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb @@ -33,7 +33,7 @@ describe ScheduleMergeRequestDiffMigrationsTakeTwo, :migration, :sidekiq do end it 'migrates the data' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' expect(merge_request_diffs.where(non_empty).count).to eq 3 diff --git a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb b/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb index c9fdbe95d13..76fe16581ac 100644 --- a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb +++ b/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb @@ -53,7 +53,7 @@ describe ScheduleMergeRequestLatestMergeRequestDiffIdMigrations, :migration, :si end it 'schedules background migrations' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 3 migrate! diff --git a/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb b/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb index 027f4a91c90..fa4ddd5fbc7 100644 --- a/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb +++ b/spec/migrations/schedule_set_confidential_note_events_on_webhooks_spec.rb @@ -31,7 +31,7 @@ describe ScheduleSetConfidentialNoteEventsOnWebhooks, :migration, :sidekiq do end it 'correctly processes web hooks' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect(web_hooks_table.where(confidential_note_events: nil).count).to eq 4 expect(web_hooks_table.where(confidential_note_events: true).count).to eq 1 diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 774a638b430..915bf134d57 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -179,7 +179,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end it 'migrates data to object storage' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do subject build_trace_chunk.reload @@ -201,7 +201,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end it 'does not migrate data to object storage' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do data_store = build_trace_chunk.data_store subject diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb index 0d6b4384ada..90a2caaeb88 100644 --- a/spec/models/spam_log_spec.rb +++ b/spec/models/spam_log_spec.rb @@ -22,7 +22,7 @@ describe SpamLog do spam_log = build(:spam_log) user = spam_log.user - Sidekiq::Testing.inline! do + perform_enqueued_jobs do spam_log.remove_user(deleted_by: admin) end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 65b387a2170..3a8948f8477 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -226,6 +226,25 @@ describe API::Groups do expect(json_response.first['name']).to eq(group2.name) end end + + context 'when using min_access_level in the request' do + let!(:group3) { create(:group, :private) } + let(:response_groups) { json_response.map { |group| group['id'] } } + + before do + group1.add_developer(user2) + group3.add_master(user2) + end + + it 'returns an array of groups the user has at least master access' do + get api('/groups', user2), min_access_level: 40 + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(response_groups).to eq([group2.id, group3.id]) + end + end end describe "GET /groups/:id" do diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 41243854ebc..55332f56508 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -102,7 +102,7 @@ describe API::ProjectImport do it 'correctly overrides params during the import' do override_params = { 'description' => 'Hello world' } - Sidekiq::Testing.inline! do + perform_enqueued_jobs do post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5ac008c7e40..71e3436fa76 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -400,6 +400,22 @@ describe API::Projects do end end end + + context 'and with min_access_level' do + before do + project2.add_master(user2) + project3.add_developer(user2) + project4.add_reporter(user2) + end + + it 'returns an array of groups the user has at least developer access' do + get api('/projects', user2), { min_access_level: 30 } + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(project2.id, project3.id) + end + end end context 'when authenticated as a different user' do @@ -681,6 +697,20 @@ describe API::Projects do expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) end + + it 'returns projects filetered by minimal access level' do + private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace) + private_project2 = create(:project, :private, name: 'private_project2', creator_id: user4.id, namespace: user4.namespace) + private_project1.add_developer(user2) + private_project2.add_reporter(user2) + + get api("/users/#{user4.id}/projects/", user2), { min_access_level: 30 } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(private_project1.id) + end end describe 'POST /projects/user/:id' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a97c3f3461a..6a051f865aa 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -11,6 +11,7 @@ describe API::Users do let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 } let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 } + let(:private_user) { create(:user, private_profile: true) } describe 'GET /users' do context "when unauthenticated" do @@ -254,6 +255,13 @@ describe API::Users do expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['is_admin']).to be(false) end + + it "includes the `created_at` field for private users" do + get api("/users/#{private_user.id}", admin) + + expect(response).to match_response_schema('public_api/v4/user/admin') + expect(json_response.keys).to include 'created_at' + end end context 'for an anonymous user' do @@ -272,6 +280,20 @@ describe API::Users do expect(response).to have_gitlab_http_status(404) end + + it "returns the `created_at` field for public users" do + get api("/users/#{user.id}") + + expect(response).to match_response_schema('public_api/v4/user/basic') + expect(json_response.keys).to include 'created_at' + end + + it "does not return the `created_at` field for private users" do + get api("/users/#{private_user.id}") + + expect(response).to match_response_schema('public_api/v4/user/basic') + expect(json_response.keys).not_to include 'created_at' + end end it "returns a 404 error if user id not found" do @@ -374,6 +396,18 @@ describe API::Users do expect(new_user.recently_sent_password_reset?).to eq(true) end + it "creates user with private profile" do + post api('/users', admin), attributes_for(:user, private_profile: true) + + expect(response).to have_gitlab_http_status(201) + + user_id = json_response['id'] + new_user = User.find(user_id) + + expect(new_user).not_to eq(nil) + expect(new_user.private_profile?).to eq(true) + end + it "does not create user with invalid email" do post api('/users', admin), email: 'invalid email', @@ -583,6 +617,13 @@ describe API::Users do expect(user.reload.external?).to be_truthy end + it "updates private profile" do + put api("/users/#{user.id}", admin), { private_profile: true } + + expect(response).to have_gitlab_http_status(200) + expect(user.reload.private_profile).to eq(true) + end + it "does not update admin status" do put api("/users/#{admin_user.id}", admin), { can_create_group: false } @@ -1067,7 +1108,7 @@ describe API::Users do end it "deletes user" do - Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}", admin) } expect(response).to have_gitlab_http_status(204) expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound @@ -1079,30 +1120,30 @@ describe API::Users do end it "does not delete for unauthenticated user" do - Sidekiq::Testing.inline! { delete api("/users/#{user.id}") } + perform_enqueued_jobs { delete api("/users/#{user.id}") } expect(response).to have_gitlab_http_status(401) end it "is not available for non admin users" do - Sidekiq::Testing.inline! { delete api("/users/#{user.id}", user) } + perform_enqueued_jobs { delete api("/users/#{user.id}", user) } expect(response).to have_gitlab_http_status(403) end it "returns 404 for non-existing user" do - Sidekiq::Testing.inline! { delete api("/users/999999", admin) } + perform_enqueued_jobs { delete api("/users/999999", admin) } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do - Sidekiq::Testing.inline! { delete api("/users/ASDF", admin) } + perform_enqueued_jobs { delete api("/users/ASDF", admin) } expect(response).to have_gitlab_http_status(404) end context "hard delete disabled" do it "moves contributions to the ghost user" do - Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}", admin) } expect(response).to have_gitlab_http_status(204) expect(issue.reload).to be_persisted @@ -1112,7 +1153,7 @@ describe API::Users do context "hard delete enabled" do it "removes contributions" do - Sidekiq::Testing.inline! { delete api("/users/#{user.id}?hard_delete=true", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin) } expect(response).to have_gitlab_http_status(204) expect(Issue.exists?(issue.id)).to be_falsy diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index a9baccd061a..b54491cf5f9 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -49,7 +49,7 @@ describe Groups::DestroyService do context 'Sidekiq inline' do before do # Run sidekiq immediately to check that renamed dir will be removed - Sidekiq::Testing.inline! { destroy_group(group, user, async) } + perform_enqueued_jobs { destroy_group(group, user, async) } end it 'verifies that paths have been deleted' do diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb index 9aa9237d875..a43da01f37e 100644 --- a/spec/services/projects/create_from_template_service_spec.rb +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -28,7 +28,7 @@ describe Projects::CreateFromTemplateService do context 'the result project' do before do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do @project = subject.execute end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 38660ad7a01..e428808ab68 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -45,18 +45,18 @@ describe Projects::DestroyService do shared_examples 'handles errors thrown during async destroy' do |error_message| it 'does not allow the error to bubble up' do expect do - Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + perform_enqueued_jobs { destroy_project(project, user, {}) } end.not_to raise_error end it 'unmarks the project as "pending deletion"' do - Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + perform_enqueued_jobs { destroy_project(project, user, {}) } expect(project.reload.pending_delete).to be(false) end it 'stores an error message in `projects.delete_error`' do - Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + perform_enqueued_jobs { destroy_project(project, user, {}) } expect(project.reload.delete_error).to be_present expect(project.delete_error).to include(error_message) @@ -66,7 +66,7 @@ describe Projects::DestroyService do context 'Sidekiq inline' do before do # Run sidekiq immediatly to check that renamed repository will be removed - Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + perform_enqueued_jobs { destroy_project(project, user, {}) } end context 'when has remote mirrors' do @@ -110,7 +110,7 @@ describe Projects::DestroyService do end it 'keeps project team intact upon an error' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do begin destroy_project(project, user, {}) rescue ::Redis::CannotConnectError @@ -128,7 +128,7 @@ describe Projects::DestroyService do before do project.project_feature.update_attribute("issues_access_level", ProjectFeature::PRIVATE) # Run sidekiq immediately to check that renamed repository will be removed - Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + perform_enqueued_jobs { destroy_project(project, user, {}) } end it_behaves_like 'deleting the project' @@ -172,7 +172,7 @@ describe Projects::DestroyService do it 'allows error to bubble up and rolls back project deletion' do expect do - Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + perform_enqueued_jobs { destroy_project(project, user, {}) } end.to raise_error(Exception, 'Other error message') expect(project.reload.pending_delete).to be(false) diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 1cf373d1d72..18ecef1c0a1 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -35,7 +35,7 @@ describe Projects::HousekeepingService do allow(subject).to receive(:gc_period).and_return(1) project.increment_pushes_since_gc - Sidekiq::Testing.inline! do + perform_enqueued_jobs do expect { subject.execute }.to change { project.pushes_since_gc }.to(0) end end diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index f82d4b483e7..3bae8bfbd42 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -173,7 +173,7 @@ describe Users::DestroyService do describe "user personal's repository removal" do before do - Sidekiq::Testing.inline! { service.execute(user) } + perform_enqueued_jobs { service.execute(user) } end context 'legacy storage' do diff --git a/spec/workers/storage_migrator_worker_spec.rb b/spec/workers/storage_migrator_worker_spec.rb index 815432aacce..808084c8f7c 100644 --- a/spec/workers/storage_migrator_worker_spec.rb +++ b/spec/workers/storage_migrator_worker_spec.rb @@ -13,7 +13,7 @@ describe StorageMigratorWorker do end it 'migrates projects in the specified range' do - Sidekiq::Testing.inline! do + perform_enqueued_jobs do worker.perform(ids.min, ids.max) end |