diff options
248 files changed, 2190 insertions, 615 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e90f599ced1..e1a6a014c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.0.4 (2018-07-17) + +### Security (1 change) + +- Fix symlink vulnerability in project import. + + ## 11.0.3 (2018-07-05) ### Fixed (14 changes, 1 of them is from the community) @@ -295,6 +302,14 @@ entry. - Workhorse to send raw diff and patch for commits. +## 10.8.6 (2018-07-17) + +### Security (2 changes) + +- Fix symlink vulnerability in project import. +- Merge branch 'fix-mr-widget-border' into 'master'. + + ## 10.8.5 (2018-06-21) ### Security (5 changes) @@ -524,6 +539,13 @@ entry. - Gitaly handles repository forks by default. +## 10.7.7 (2018-07-17) + +### Security (1 change) + +- Fix symlink vulnerability in project import. + + ## 10.7.6 (2018-06-21) ### Security (6 changes) diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js index fa00a3cf386..e8c59fab609 100644 --- a/app/assets/javascripts/autosave.js +++ b/app/assets/javascripts/autosave.js @@ -53,4 +53,8 @@ export default class Autosave { return window.localStorage.removeItem(this.key); } + + dispose() { + this.field.off('input'); + } } diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 8139aa69fc7..e565af800d0 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -162,8 +162,10 @@ export default class Clusters { if (type === 'password') { this.tokenField.setAttribute('type', 'text'); + this.showTokenButton.textContent = s__('ClusterIntegration|Hide'); } else { this.tokenField.setAttribute('type', 'password'); + this.showTokenButton.textContent = s__('ClusterIntegration|Show'); } } diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue index ad838a32518..0fe0007057b 100644 --- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue +++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue @@ -77,7 +77,8 @@ export default { diffViewType: state => state.diffs.diffViewType, diffFiles: state => state.diffs.diffFiles, }), - ...mapGetters(['isLoggedIn', 'discussionsByLineCode']), + ...mapGetters(['isLoggedIn']), + ...mapGetters('diffs', ['discussionsByLineCode']), lineHref() { return this.lineCode ? `#${this.lineCode}` : '#'; }, @@ -189,7 +190,6 @@ export default { </button> <a v-if="lineNumber" - v-once :data-linenumber="lineNumber" :href="lineHref" > diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue index 32f9516d332..cbe4551d06b 100644 --- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue +++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue @@ -1,17 +1,17 @@ <script> -import $ from 'jquery'; import { mapState, mapGetters, mapActions } from 'vuex'; import createFlash from '~/flash'; import { s__ } from '~/locale'; import noteForm from '../../notes/components/note_form.vue'; import { getNoteFormData } from '../store/utils'; -import Autosave from '../../autosave'; -import { DIFF_NOTE_TYPE, NOTE_TYPE } from '../constants'; +import autosave from '../../notes/mixins/autosave'; +import { DIFF_NOTE_TYPE } from '../constants'; export default { components: { noteForm, }, + mixins: [autosave], props: { diffFileHash: { type: String, @@ -41,28 +41,35 @@ export default { }, mounted() { if (this.isLoggedIn) { - const noteableData = this.getNoteableData; const keys = [ - NOTE_TYPE, - this.noteableType, - noteableData.id, - noteableData.diff_head_sha, + this.noteableData.diff_head_sha, DIFF_NOTE_TYPE, - noteableData.source_project_id, + this.noteableData.source_project_id, this.line.lineCode, ]; - this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), keys); + this.initAutoSave(this.noteableData, keys); } }, methods: { ...mapActions('diffs', ['cancelCommentForm']), ...mapActions(['saveNote', 'refetchDiscussionById']), - handleCancelCommentForm() { - this.autosave.reset(); + handleCancelCommentForm(shouldConfirm, isDirty) { + if (shouldConfirm && isDirty) { + const msg = s__('Notes|Are you sure you want to cancel creating this comment?'); + + // eslint-disable-next-line no-alert + if (!window.confirm(msg)) { + return; + } + } + this.cancelCommentForm({ lineCode: this.line.lineCode, }); + this.$nextTick(() => { + this.resetAutoSave(); + }); }, handleSaveNote(note) { const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash); diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue index ca265dd892c..a6f011ff31e 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue @@ -26,13 +26,16 @@ export default { ...mapState({ diffLineCommentForms: state => state.diffs.diffLineCommentForms, }), - ...mapGetters(['discussionsByLineCode']), + ...mapGetters('diffs', ['discussionsByLineCode']), discussions() { return this.discussionsByLineCode[this.line.lineCode] || []; }, className() { return this.discussions.length ? '' : 'js-temp-notes-holder'; }, + hasCommentForm() { + return this.diffLineCommentForms[this.line.lineCode]; + }, }, }; </script> @@ -53,7 +56,7 @@ export default { :discussions="discussions" /> <diff-line-note-form - v-if="diffLineCommentForms[line.lineCode]" + v-if="hasCommentForm" :diff-file-hash="diffFileHash" :line="line" :note-target-line="line" diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue index 0197a510ef1..0e306f39a9f 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue @@ -101,7 +101,6 @@ export default { class="diff-line-num new_line" /> <td - v-once :class="line.type" class="line_content" v-html="line.richText" diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue index 9fd19b74cd7..8e491d293e5 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_view.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue @@ -20,8 +20,7 @@ export default { }, }, computed: { - ...mapGetters('diffs', ['commitId']), - ...mapGetters(['discussionsByLineCode']), + ...mapGetters('diffs', ['commitId', 'discussionsByLineCode']), ...mapState({ diffLineCommentForms: state => state.diffs.diffLineCommentForms, }), diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue index cc5248c25d9..05e5cafc717 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue @@ -26,7 +26,7 @@ export default { ...mapState({ diffLineCommentForms: state => state.diffs.diffLineCommentForms, }), - ...mapGetters(['discussionsByLineCode']), + ...mapGetters('diffs', ['discussionsByLineCode']), leftLineCode() { return this.line.left.lineCode; }, diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue index ee5bb4d8d05..0031cedc68f 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue @@ -119,7 +119,6 @@ export default { class="diff-line-num old_line" /> <td - v-once :id="line.left.lineCode" :class="parallelViewLeftLineType" class="line_content parallel left-side" @@ -140,7 +139,6 @@ export default { class="diff-line-num new_line" /> <td - v-once :id="line.right.lineCode" :class="line.right.type" class="line_content parallel right-side" diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue index 32528c9e7ab..8f8d6bbc818 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue @@ -21,8 +21,7 @@ export default { }, }, computed: { - ...mapGetters('diffs', ['commitId']), - ...mapGetters(['discussionsByLineCode']), + ...mapGetters('diffs', ['commitId', 'discussionsByLineCode']), ...mapState({ diffLineCommentForms: state => state.diffs.diffLineCommentForms, }), diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index 855de79adf8..d3881fa1a0a 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -1,5 +1,7 @@ import _ from 'underscore'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants'; +import { getDiffRefsByLineCode } from './utils'; export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE; @@ -56,6 +58,44 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) = discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash), ) || []; +/** + * Returns an Object with discussions by their diff line code + * To avoid rendering outdated discussions on the Changes tab we should do a bunch of SHA + * comparisions. `note.position.formatter` have the current version diff refs but + * `note.original_position.formatter` will have the first version's diff refs. + * If line diff refs matches with one of them, we should render it as a discussion on Changes tab. + * + * @param {Object} diff + * @returns {Array} + */ +export const discussionsByLineCode = (state, getters, rootState, rootGetters) => { + const diffRefsByLineCode = getDiffRefsByLineCode(state.diffFiles); + + return rootGetters.discussions.reduce((acc, note) => { + const isDiffDiscussion = note.diff_discussion; + const hasLineCode = note.line_code; + const isResolvable = note.resolvable; + const diffRefs = diffRefsByLineCode[note.line_code]; + + if (isDiffDiscussion && hasLineCode && isResolvable && diffRefs) { + const refs = convertObjectPropsToCamelCase(note.position.formatter); + const originalRefs = convertObjectPropsToCamelCase(note.original_position.formatter); + + if (_.isEqual(refs, diffRefs) || _.isEqual(originalRefs, diffRefs)) { + const lineCode = note.line_code; + + if (acc[lineCode]) { + acc[lineCode].push(note); + } else { + acc[lineCode] = [note]; + } + } + } + + return acc; + }, {}); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma∂ tests export const getDiffFileByHash = state => fileHash => state.diffFiles.find(file => file.fileHash === fileHash); diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index d9589baa76e..82082ac508a 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) { return parsedLine; } + +export function getDiffRefsByLineCode(diffFiles) { + return diffFiles.reduce((acc, diffFile) => { + const { baseSha, headSha, startSha } = diffFile.diffRefs; + const { newPath, oldPath } = diffFile; + + // We can only use highlightedDiffLines to create the map of diff lines because + // highlightedDiffLines will also include every parallel diff line in it. + if (diffFile.highlightedDiffLines) { + diffFile.highlightedDiffLines.forEach(line => { + const { lineCode, oldLine, newLine } = line; + + if (lineCode) { + acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine }; + } + }); + } + + return acc; + }, {}); +} diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue index 92fe98508ad..1e6803abf3a 100644 --- a/app/assets/javascripts/monitoring/components/graph/flag.vue +++ b/app/assets/javascripts/monitoring/components/graph/flag.vue @@ -125,6 +125,7 @@ export default { :class="flagOrientation" class="prometheus-graph-flag popover" > + <div class="arrow-shadow"></div> <div class="arrow"></div> <div class="popover-title"> <h5 v-if="deploymentFlagData"> diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js index 748b8cb6e6e..176f7d9eef2 100644 --- a/app/assets/javascripts/monitoring/stores/monitoring_store.js +++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js @@ -1,7 +1,10 @@ import _ from 'underscore'; function sortMetrics(metrics) { - return _.chain(metrics).sortBy('title').sortBy('weight').value(); + return _.chain(metrics) + .sortBy('title') + .sortBy('weight') + .value(); } function normalizeMetrics(metrics) { @@ -39,7 +42,9 @@ export default class MonitoringStore { } storeEnvironmentsData(environmentsData = []) { - this.environmentsData = environmentsData; + this.environmentsData = environmentsData.filter( + environment => !!environment.latest.last_deployment, + ); } getMetricsCount() { diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 26482a02e00..abcd4422d7c 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -7,7 +7,7 @@ import issuableStateMixin from '../mixins/issuable_state'; import resolvable from '../mixins/resolvable'; export default { - name: 'IssueNoteForm', + name: 'NoteForm', components: { issueWarning, markdownField, diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index bee635398b3..2f1a68731c7 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -6,6 +6,7 @@ import nextDiscussionsSvg from 'icons/_next_discussion.svg'; import { convertObjectPropsToCamelCase, scrollToElement } from '~/lib/utils/common_utils'; import { truncateSha } from '~/lib/utils/text_utility'; import systemNote from '~/vue_shared/components/notes/system_note.vue'; +import { s__ } from '~/locale'; import Flash from '../../flash'; import { SYSTEM_NOTE } from '../constants'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; @@ -144,19 +145,17 @@ export default { return this.isDiffDiscussion ? '' : 'card discussion-wrapper'; }, }, - mounted() { - if (this.isReplying) { - this.initAutoSave(this.transformedDiscussion); - } - }, - updated() { - if (this.isReplying) { - if (!this.autosave) { - this.initAutoSave(this.transformedDiscussion); + watch: { + isReplying() { + if (this.isReplying) { + this.$nextTick(() => { + // Pass an extra key to separate reply and note edit forms + this.initAutoSave(this.transformedDiscussion, ['Reply']); + }); } else { - this.setAutoSave(); + this.disposeAutoSave(); } - } + }, }, created() { this.resolveDiscussionsSvg = resolveDiscussionsSvg; @@ -194,16 +193,18 @@ export default { showReplyForm() { this.isReplying = true; }, - cancelReplyForm(shouldConfirm) { - if (shouldConfirm && this.$refs.noteForm.isDirty) { + cancelReplyForm(shouldConfirm, isDirty) { + if (shouldConfirm && isDirty) { + const msg = s__('Notes|Are you sure you want to cancel creating this comment?'); + // eslint-disable-next-line no-alert - if (!window.confirm('Are you sure you want to cancel creating this comment?')) { + if (!window.confirm(msg)) { return; } } - this.resetAutoSave(); this.isReplying = false; + this.resetAutoSave(); }, saveReply(noteText, form, callback) { const postData = { @@ -420,7 +421,8 @@ Please check your network connection and try again.`; :is-editing="false" save-button-title="Comment" @handleFormUpdate="saveReply" - @cancelForm="cancelReplyForm" /> + @cancelForm="cancelReplyForm" + /> <note-signed-out-widget v-if="!canReply" /> </div> </div> diff --git a/app/assets/javascripts/notes/mixins/autosave.js b/app/assets/javascripts/notes/mixins/autosave.js index 36cc8d5d056..4f45f912479 100644 --- a/app/assets/javascripts/notes/mixins/autosave.js +++ b/app/assets/javascripts/notes/mixins/autosave.js @@ -4,12 +4,18 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility'; export default { methods: { - initAutoSave(noteable) { - this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), [ + initAutoSave(noteable, extraKeys = []) { + let keys = [ 'Note', - capitalizeFirstCharacter(noteable.noteable_type), + capitalizeFirstCharacter(noteable.noteable_type || noteable.noteableType), noteable.id, - ]); + ]; + + if (extraKeys) { + keys = keys.concat(extraKeys); + } + + this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), keys); }, resetAutoSave() { this.autosave.reset(); @@ -17,5 +23,8 @@ export default { setAutoSave() { this.autosave.save(); }, + disposeAutoSave() { + this.autosave.dispose(); + }, }, }; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 5c65e1c3bb5..e9e95dd4219 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -28,18 +28,6 @@ export const notesById = state => return acc; }, {}); -export const discussionsByLineCode = state => - state.discussions.reduce((acc, note) => { - if (note.diff_discussion && note.line_code && note.resolvable) { - // For context about line notes: there might be multiple notes with the same line code - const items = acc[note.line_code] || []; - items.push(note); - - Object.assign(acc, { [note.line_code]: items }); - } - return acc; - }, {}); - export const noteableType = state => { const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants; diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index c42c4a1fbe7..e7ff76c8218 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -1,24 +1,40 @@ <script> -/* This is a re-usable vue component for rendering a svg sprite - icon - Sample configuration: - - <icon - name="retry" - :size="32" - css-classes="top" - /> - -*/ // only allow classes in images.scss e.g. s12 const validSizes = [8, 12, 16, 18, 24, 32, 48, 72]; +let iconValidator = () => true; + +/* + During development/tests we want to validate that we are just using icons that are actually defined +*/ +if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line global-require + const data = require('@gitlab-org/gitlab-svgs/dist/icons.json'); + const { icons } = data; + iconValidator = value => { + if (icons.includes(value)) { + return true; + } + // eslint-disable-next-line no-console + console.warn(`Icon '${value}' is not a known icon of @gitlab/gitlab-svg`); + return false; + }; +} +/** This is a re-usable vue component for rendering a svg sprite icon + * @example + * <icon + * name="retry" + * :size="32" + * css-classes="top" + * /> + */ export default { props: { name: { type: String, required: true, + validator: iconValidator, }, size: { @@ -83,6 +99,6 @@ export default { :x="x" :y="y" > - <use v-bind="{ 'xlink:href':spriteHref }" /> + <use v-bind="{ 'xlink:href':spriteHref }"/> </svg> </template> diff --git a/app/assets/javascripts/vue_shared/components/reports/help_popover.vue b/app/assets/javascripts/vue_shared/components/reports/help_popover.vue new file mode 100644 index 00000000000..c5faa29fd2a --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/help_popover.vue @@ -0,0 +1,48 @@ +<script> +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +import { inserted } from '~/feature_highlight/feature_highlight_helper'; +import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover'; + +export default { + name: 'ReportsHelpPopover', + components: { + Icon, + }, + props: { + options: { + type: Object, + required: true, + }, + }, + mounted() { + const $el = $(this.$el); + + $el + .popover({ + html: true, + trigger: 'focus', + container: 'body', + placement: 'top', + template: + '<div class="popover" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>', + ...this.options, + }) + .on('mouseenter', mouseenter) + .on('mouseleave', debouncedMouseleave(300)) + .on('inserted.bs.popover', inserted) + .on('show.bs.popover', () => { + window.addEventListener('scroll', togglePopover.bind($el, false), { once: true }); + }); + }, +}; +</script> +<template> + <button + type="button" + class="btn btn-blank btn-transparent btn-help" + tabindex="0" + > + <icon name="question" /> + </button> +</template> diff --git a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue b/app/assets/javascripts/vue_shared/components/reports/issues_list.vue new file mode 100644 index 00000000000..e1e03e39ee0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/issues_list.vue @@ -0,0 +1,99 @@ +<script> +import IssuesBlock from './report_issues.vue'; + +/** + * Renders block of issues + */ + +export default { + components: { + IssuesBlock, + }, + props: { + unresolvedIssues: { + type: Array, + required: false, + default: () => [], + }, + resolvedIssues: { + type: Array, + required: false, + default: () => [], + }, + neutralIssues: { + type: Array, + required: false, + default: () => [], + }, + allIssues: { + type: Array, + required: false, + default: () => [], + }, + type: { + type: String, + required: true, + }, + }, + data() { + return { + isFullReportVisible: false, + }; + }, + computed: { + unresolvedIssuesStatus() { + return this.type === 'license' ? 'neutral' : 'failed'; + }, + }, + methods: { + openFullReport() { + this.isFullReportVisible = true; + }, + }, +}; +</script> +<template> + <div class="report-block-container"> + + <issues-block + v-if="unresolvedIssues.length" + :type="type" + :status="unresolvedIssuesStatus" + :issues="unresolvedIssues" + class="js-mr-code-new-issues" + /> + + <issues-block + v-if="isFullReportVisible" + :type="type" + :issues="allIssues" + class="js-mr-code-all-issues" + status="failed" + /> + + <issues-block + v-if="neutralIssues.length" + :type="type" + :issues="neutralIssues" + class="js-mr-code-non-issues" + status="neutral" + /> + + <issues-block + v-if="resolvedIssues.length" + :type="type" + :issues="resolvedIssues" + class="js-mr-code-resolved-issues" + status="success" + /> + + <button + v-if="allIssues.length && !isFullReportVisible" + type="button" + class="btn-link btn-blank prepend-left-10 js-expand-full-list break-link" + @click="openFullReport" + > + {{ s__("ciReport|Show complete code vulnerabilities report") }} + </button> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue b/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue new file mode 100644 index 00000000000..4f81cee2a38 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue @@ -0,0 +1,33 @@ +<script> +import { mapActions } from 'vuex'; + +export default { + props: { + issue: { + type: Object, + required: true, + }, + // failed || success + status: { + type: String, + required: true, + }, + }, + methods: { + ...mapActions(['openModal']), + handleIssueClick() { + const { issue, status, openModal } = this; + openModal({ issue, status }); + }, + }, +}; +</script> +<template> + <button + type="button" + class="btn-link btn-blank text-left break-link vulnerability-name-button" + @click="handleIssueClick()" + > + {{ issue.title }} + </button> +</template> diff --git a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue b/app/assets/javascripts/vue_shared/components/reports/report_issues.vue new file mode 100644 index 00000000000..ecffb02a3a0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/report_issues.vue @@ -0,0 +1,72 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + name: 'ReportIssues', + components: { + Icon, + }, + props: { + issues: { + type: Array, + required: true, + }, + type: { + type: String, + required: true, + }, + // failed || success + status: { + type: String, + required: true, + }, + }, + computed: { + iconName() { + if (this.isStatusFailed) { + return 'status_failed_borderless'; + } else if (this.isStatusSuccess) { + return 'status_success_borderless'; + } + + return 'status_created_borderless'; + }, + isStatusFailed() { + return this.status === 'failed'; + }, + isStatusSuccess() { + return this.status === 'success'; + }, + isStatusNeutral() { + return this.status === 'neutral'; + }, + }, +}; +</script> +<template> + <div> + <ul class="report-block-list"> + <li + v-for="(issue, index) in issues" + :class="{ 'is-dismissed': issue.isDismissed }" + :key="index" + class="report-block-list-issue" + > + <div + :class="{ + failed: isStatusFailed, + success: isStatusSuccess, + neutral: isStatusNeutral, + }" + class="report-block-list-icon append-right-5" + > + <icon + :name="iconName" + :size="32" + /> + </div> + + </li> + </ul> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/reports/report_link.vue b/app/assets/javascripts/vue_shared/components/reports/report_link.vue new file mode 100644 index 00000000000..74d68f9f439 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/report_link.vue @@ -0,0 +1,29 @@ +<script> +export default { + name: 'ReportIssueLink', + props: { + issue: { + type: Object, + required: true, + }, + }, +}; +</script> +<template> + <div class="report-block-list-issue-description-link"> + in + + <a + v-if="issue.urlPath" + :href="issue.urlPath" + target="_blank" + rel="noopener noreferrer nofollow" + class="break-link" + > + {{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template> + </a> + <template v-else> + {{ issue.path }}<template v-if="issue.line">:{{ issue.line }}</template> + </template> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/reports/report_section.vue b/app/assets/javascripts/vue_shared/components/reports/report_section.vue new file mode 100644 index 00000000000..d383ed99a0c --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/report_section.vue @@ -0,0 +1,192 @@ +<script> +import { __ } from '~/locale'; +import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; +import IssuesList from './issues_list.vue'; +import Popover from './help_popover.vue'; + +const LOADING = 'LOADING'; +const ERROR = 'ERROR'; +const SUCCESS = 'SUCCESS'; + +export default { + name: 'ReportSection', + components: { + IssuesList, + StatusIcon, + Popover, + }, + props: { + alwaysOpen: { + type: Boolean, + required: false, + default: false, + }, + type: { + type: String, + required: false, + default: '', + }, + status: { + type: String, + required: true, + }, + loadingText: { + type: String, + required: false, + default: '', + }, + errorText: { + type: String, + required: false, + default: '', + }, + successText: { + type: String, + required: true, + }, + unresolvedIssues: { + type: Array, + required: false, + default: () => [], + }, + resolvedIssues: { + type: Array, + required: false, + default: () => [], + }, + neutralIssues: { + type: Array, + required: false, + default: () => [], + }, + allIssues: { + type: Array, + required: false, + default: () => [], + }, + infoText: { + type: [String, Boolean], + required: false, + default: false, + }, + hasIssues: { + type: Boolean, + required: true, + }, + popoverOptions: { + type: Object, + default: () => ({}), + required: false, + }, + }, + + data() { + return { + isCollapsed: true, + }; + }, + + computed: { + collapseText() { + return this.isCollapsed ? __('Expand') : __('Collapse'); + }, + isLoading() { + return this.status === LOADING; + }, + loadingFailed() { + return this.status === ERROR; + }, + isSuccess() { + return this.status === SUCCESS; + }, + isCollapsible() { + return !this.alwaysOpen && this.hasIssues; + }, + isExpanded() { + return this.alwaysOpen || !this.isCollapsed; + }, + statusIconName() { + if (this.isLoading) { + return 'loading'; + } + if (this.loadingFailed || this.unresolvedIssues.length || this.neutralIssues.length) { + return 'warning'; + } + return 'success'; + }, + headerText() { + if (this.isLoading) { + return this.loadingText; + } + + if (this.isSuccess) { + return this.successText; + } + + if (this.loadingFailed) { + return this.errorText; + } + + return ''; + }, + hasPopover() { + return Object.keys(this.popoverOptions).length > 0; + }, + }, + methods: { + toggleCollapsed() { + this.isCollapsed = !this.isCollapsed; + }, + }, +}; +</script> +<template> + <section class="media-section"> + <div + class="media" + > + <status-icon + :status="statusIconName" + /> + <div + class="media-body space-children d-flex" + > + <span + class="js-code-text code-text" + > + {{ headerText }} + + <popover + v-if="hasPopover" + :options="popoverOptions" + class="prepend-left-5" + /> + </span> + + <button + v-if="isCollapsible" + type="button" + class="js-collapse-btn btn bt-default float-right btn-sm" + @click="toggleCollapsed" + > + {{ collapseText }} + </button> + </div> + </div> + + <div + v-if="hasIssues" + v-show="isExpanded" + class="js-report-section-container" + > + <slot name="body"> + <issues-list + :unresolved-issues="unresolvedIssues" + :resolved-issues="resolvedIssues" + :all-issues="allIssues" + :type="type" + /> + </slot> + </div> + </section> +</template> diff --git a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue b/app/assets/javascripts/vue_shared/components/reports/summary_row.vue new file mode 100644 index 00000000000..997bad960e2 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/reports/summary_row.vue @@ -0,0 +1,66 @@ +<script> +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; +import Popover from './help_popover.vue'; + +/** + * Renders the summary row for each report + * + * Used both in MR widget and Pipeline's view for: + * - Unit tests reports + * - Security reports + */ + +export default { + name: 'ReportSummaryRow', + components: { + CiIcon, + LoadingIcon, + Popover, + }, + props: { + summary: { + type: String, + required: true, + }, + statusIcon: { + type: String, + required: true, + }, + popoverOptions: { + type: Object, + required: true, + }, + }, + computed: { + iconStatus() { + return { + group: this.statusIcon, + icon: `status_${this.statusIcon}`, + }; + }, + }, +}; +</script> +<template> + <div class="report-block-list-issue report-block-list-issue-parent"> + <div class="report-block-list-icon append-right-10 prepend-left-5"> + <loading-icon + v-if="statusIcon === 'loading'" + css-class="report-block-list-loading-icon" + /> + <ci-icon + v-else + :status="iconStatus" + /> + </div> + + <div class="report-block-list-issue-description"> + <div class="report-block-list-issue-description-text"> + {{ summary }} + </div> + + <popover :options="popoverOptions" /> + </div> + </div> +</template> diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index a8e28104a94..5ca4d944d73 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -47,7 +47,6 @@ .card-body { padding: $gl-padding; - background-color: $white-light; .form-actions { margin: -$gl-padding; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 6cfa09b56a7..6c2fdbe0608 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -297,7 +297,6 @@ $performance-bar-height: 35px; $flash-height: 52px; $context-header-height: 60px; $breadcrumb-min-height: 48px; -$gcp-signup-offer-icon-max-width: 125px; /* * Common component specific colors diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 56beb7718a4..0f22fe21143 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -32,49 +32,23 @@ } .gcp-signup-offer { - background-color: $blue-50; - border: 1px solid $blue-300; - border-radius: $border-radius-default; + border-left-color: $blue-500; - // TODO: To be superceded by cssLab - &.alert { - padding: 24px 16px; - - &-dismissable { - padding-right: 32px; - - .close { - top: -8px; - right: -16px; - color: $blue-500; - opacity: 1; - } - } - } - - .gcp-logo { - margin-bottom: $gl-padding; - text-align: center; - } - - img { - max-width: $gcp-signup-offer-icon-max-width; + svg { + fill: $blue-500; + vertical-align: middle; } - a:not(.btn) { - color: $gl-link-color; - font-weight: normal; - text-decoration: none; - } + .gcp-signup-offer--content { + display: flex; - @include media-breakpoint-up(sm) { - > div { - display: flex; - align-items: center; + h4 { + font-size: 16px; + line-height: 24px; } - .gcp-logo { - margin: 0; + .gcp-signup-offer--icon { + align-self: flex-start; } } } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3144dcc4dc0..8915b323b3c 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -293,6 +293,8 @@ .prometheus-graph-flag { display: block; min-width: 160px; + border: 0; + box-shadow: 0 1px 4px 0 $black-transparent; h5 { padding: 0; @@ -312,7 +314,6 @@ &.popover { padding: 0; - border: 1px solid $border-color; &.left { left: auto; @@ -320,12 +321,19 @@ margin-right: 10px; > .arrow { - right: -16px; + right: -14px; border-left-color: $border-color; } > .arrow::after { - border-left-color: $theme-gray-50; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 4px solid $theme-gray-50; + } + + .arrow-shadow { + right: -3px; + box-shadow: 1px 0 9px 0 $black-transparent; } } @@ -335,19 +343,35 @@ margin-left: 10px; > .arrow { - left: -16px; + left: -7px; border-right-color: $border-color; } > .arrow::after { - border-right-color: $theme-gray-50; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 4px solid $theme-gray-50; + } + + .arrow-shadow { + left: -3px; + box-shadow: 1px 0 8px 0 $black-transparent; } } > .arrow { - top: 16px; - margin-top: -8px; - border-width: 8px; + top: 10px; + margin: 0; + } + + .arrow-shadow { + content: ""; + position: absolute; + width: 7px; + height: 7px; + background-color: transparent; + transform: rotate(45deg); + top: 13px; } > .popover-title, @@ -355,10 +379,12 @@ padding: 8px; font-size: 12px; white-space: nowrap; + position: relative; } > .popover-title { background-color: $theme-gray-50; + border-radius: $border-radius-default $border-radius-default 0 0; } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5194bdb6105..9922f589375 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base include EnforcesTwoFactorAuthentication include WithPerformanceBar + before_action :limit_unauthenticated_session_times before_action :authenticate_sessionless_user! before_action :authenticate_user! before_action :enforce_terms!, if: :should_enforce_terms? @@ -86,6 +87,24 @@ class ApplicationController < ActionController::Base end end + # By default, all sessions are given the same expiration time configured in + # the session store (e.g. 1 week). However, unauthenticated users can + # generate a lot of sessions, primarily for CSRF verification. It makes + # sense to reduce the TTL for unauthenticated to something much lower than + # the default (e.g. 1 hour) to limit Redis memory. In addition, Rails + # creates a new session after login, so the short TTL doesn't even need to + # be extended. + def limit_unauthenticated_session_times + return if current_user + + # Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259 + return unless request.env['rack.session.options'] + + # This works because Rack uses these options every time a request is handled: + # https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342 + request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay'] + end + protected def append_info_to_payload(payload) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 2f304b040c7..41084ec686f 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -1,3 +1,5 @@ +require 'json' + module IconsHelper extend self include FontAwesome::Rails::IconHelper @@ -38,6 +40,13 @@ module IconsHelper end def sprite_icon(icon_name, size: nil, css_class: nil) + if Gitlab::Sentry.should_raise? + unless known_sprites.include?(icon_name) + exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg") + raise exception + end + end + css_classes = size ? "s#{size}" : "" css_classes << " #{css_class}" unless css_class.blank? content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes) @@ -134,4 +143,10 @@ module IconsHelper icon_class end + + private + + def known_sprites + @known_sprites ||= JSON.parse(File.read(Rails.root.join('node_modules/@gitlab-org/gitlab-svgs/dist/icons.json')))['icons'] + end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index f7dafca7834..cadb88ba632 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -105,7 +105,8 @@ module SearchHelper category: "Groups", id: group.id, label: "#{search_result_sanitize(group.full_name)}", - url: group_path(group) + url: group_path(group), + avatar_url: group.avatar_url || '' } end end @@ -119,7 +120,8 @@ module SearchHelper id: p.id, value: "#{search_result_sanitize(p.name)}", label: "#{search_result_sanitize(p.full_name)}", - url: project_path(p) + url: project_path(p), + avatar_url: p.avatar_url || '' } end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 3d72c447b4b..a073bbfad20 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -182,7 +182,7 @@ class MergeRequestDiff < ActiveRecord::Base end def diffs(diff_options = nil) - if without_files? && comparison = diff_refs.compare_in(project) + if without_files? && comparison = diff_refs&.compare_in(project) # It should fetch the repository when diffs are cleaned by the system. # We don't keep these for storage overload purposes. # See https://gitlab.com/gitlab-org/gitlab-ce/issues/37639 diff --git a/app/models/repository.rb b/app/models/repository.rb index a96c73e6ab7..e248f94cbd8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -154,12 +154,9 @@ class Repository # Returns a list of commits that are not present in any reference def new_commits(newrev) - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 - refs = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - ::Gitlab::Git::RevList.new(raw, newrev: newrev).new_refs - end + commits = raw.new_commits(newrev) - refs.map { |sha| commit(sha.strip) } + ::Commit.decorate(commits, project) end # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384 diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb index 8a39a4950f5..7505bbdeb3d 100644 --- a/app/serializers/discussion_entity.rb +++ b/app/serializers/discussion_entity.rb @@ -4,6 +4,7 @@ class DiscussionEntity < Grape::Entity expose :id, :reply_id expose :position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? } + expose :original_position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? } expose :line_code, if: -> (d, _) { d.diff_discussion? } expose :expanded?, as: :expanded expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? } diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb index 69281ee3137..c8eccb8e6cd 100644 --- a/app/services/lfs/file_transformer.rb +++ b/app/services/lfs/file_transformer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Lfs # Usage: Calling `new_file` check to see if a file should be in LFS and # return a transformed result with `content` and `encoding` to commit. diff --git a/app/services/lfs/lock_file_service.rb b/app/services/lfs/lock_file_service.rb index bbe10f84ef4..78434909d68 100644 --- a/app/services/lfs/lock_file_service.rb +++ b/app/services/lfs/lock_file_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Lfs class LockFileService < BaseService def execute diff --git a/app/services/lfs/locks_finder_service.rb b/app/services/lfs/locks_finder_service.rb index 13c6cc6f81c..d52cf0e3cc4 100644 --- a/app/services/lfs/locks_finder_service.rb +++ b/app/services/lfs/locks_finder_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Lfs class LocksFinderService < BaseService def execute diff --git a/app/services/lfs/unlock_file_service.rb b/app/services/lfs/unlock_file_service.rb index 7e3edf21d54..4d1443bf772 100644 --- a/app/services/lfs/unlock_file_service.rb +++ b/app/services/lfs/unlock_file_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Lfs class UnlockFileService < BaseService def execute diff --git a/app/services/mattermost/create_team_service.rb b/app/services/mattermost/create_team_service.rb index e3206810f3a..afcd6439a14 100644 --- a/app/services/mattermost/create_team_service.rb +++ b/app/services/mattermost/create_team_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Mattermost class CreateTeamService < ::BaseService def initialize(group, current_user) diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb index 6be08b590bc..52b890d1821 100644 --- a/app/services/members/approve_access_request_service.rb +++ b/app/services/members/approve_access_request_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Members class ApproveAccessRequestService < Members::BaseService def execute(access_requester, skip_authorization: false, skip_log_audit_event: false) diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb index 74556fb20cf..8248f1441d7 100644 --- a/app/services/members/base_service.rb +++ b/app/services/members/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Members class BaseService < ::BaseService # current_user - The user that performs the action diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb index bc6a9405aac..714b8586737 100644 --- a/app/services/members/create_service.rb +++ b/app/services/members/create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Members class CreateService < Members::BaseService DEFAULT_LIMIT = 100 diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 5b51e1982f1..aca0ba66646 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Members class DestroyService < Members::BaseService def execute(member, skip_authorization: false) diff --git a/app/services/members/request_access_service.rb b/app/services/members/request_access_service.rb index 24293b30005..b9b0550e290 100644 --- a/app/services/members/request_access_service.rb +++ b/app/services/members/request_access_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Members class RequestAccessService < Members::BaseService def execute(source) diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index cb19cf01dd7..1f5618dae53 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Members class UpdateService < Members::BaseService # returns the updated member diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 6805b2f7d1c..79c43b8e7d5 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class AddTodoWhenBuildFailsService < MergeRequests::BaseService # Adds a todo to the parent merge_request when a CI build fails diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb index 8c6c4841020..e9107b9998e 100644 --- a/app/services/merge_requests/assign_issues_service.rb +++ b/app/services/merge_requests/assign_issues_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class AssignIssuesService < BaseService def assignable_issues diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 4c420b38258..e6dd0e12a3a 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class BaseService < ::IssuableBaseService def create_note(merge_request, state = merge_request.state) diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index a98bbdf74dd..bc988eb2a26 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class BuildService < MergeRequests::BaseService include Gitlab::Utils::StrongMemoize @@ -140,7 +142,8 @@ module MergeRequests closes_issue = "Closes #{issue.to_reference}" if description.present? - merge_request.description += closes_issue.prepend("\n\n") + descr_parts = [merge_request.description, closes_issue] + merge_request.description = descr_parts.join("\n\n") else merge_request.description = closes_issue end @@ -164,9 +167,11 @@ module MergeRequests return if merge_request.title.present? if issue_iid.present? - merge_request.title = "Resolve #{issue.to_reference}" + title_parts = ["Resolve #{issue.to_reference}"] branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize - merge_request.title += " \"#{branch_title}\"" if branch_title.present? + + title_parts << "\"#{branch_title}\"" if branch_title.present? + merge_request.title = title_parts.join(' ') end end diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index db701c1145d..04527bb9713 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class CloseService < MergeRequests::BaseService def execute(merge_request, commit = nil) diff --git a/app/services/merge_requests/conflicts/base_service.rb b/app/services/merge_requests/conflicts/base_service.rb index b50875347d9..402f6c4e4c0 100644 --- a/app/services/merge_requests/conflicts/base_service.rb +++ b/app/services/merge_requests/conflicts/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests module Conflicts class BaseService diff --git a/app/services/merge_requests/conflicts/list_service.rb b/app/services/merge_requests/conflicts/list_service.rb index 72cbc49adb2..c6b3a6a1a69 100644 --- a/app/services/merge_requests/conflicts/list_service.rb +++ b/app/services/merge_requests/conflicts/list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests module Conflicts class ListService < MergeRequests::Conflicts::BaseService diff --git a/app/services/merge_requests/conflicts/resolve_service.rb b/app/services/merge_requests/conflicts/resolve_service.rb index 27cafd2d7d9..b9f734310be 100644 --- a/app/services/merge_requests/conflicts/resolve_service.rb +++ b/app/services/merge_requests/conflicts/resolve_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests module Conflicts class ResolveService < MergeRequests::Conflicts::BaseService diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb index 3407b312700..fd91dc4acd0 100644 --- a/app/services/merge_requests/create_from_issue_service.rb +++ b/app/services/merge_requests/create_from_issue_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class CreateFromIssueService < MergeRequests::CreateService def initialize(project, user, params) diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index fe1ac70781e..c36a2ecbfe3 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class CreateService < MergeRequests::BaseService def execute diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb index 40079b21189..2a8ea316921 100644 --- a/app/services/merge_requests/delete_non_latest_diffs_service.rb +++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class DeleteNonLatestDiffsService BATCH_SIZE = 10 diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb index bffc09c34f0..479e0fe6699 100644 --- a/app/services/merge_requests/ff_merge_service.rb +++ b/app/services/merge_requests/ff_merge_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests # MergeService class # diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb index 668a1741736..7c88c9abb41 100644 --- a/app/services/merge_requests/get_urls_service.rb +++ b/app/services/merge_requests/get_urls_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class GetUrlsService < BaseService attr_reader :project diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 3d587f97906..fb44f809c41 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests # MergeService class # diff --git a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb index 9a4e6eb2e88..973e5b64e88 100644 --- a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class MergeWhenPipelineSucceedsService < MergeRequests::BaseService # Marks the passed `merge_request` to be merged when the pipeline succeeds or diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 7606d68ff29..3d2aea4e9b6 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests # PostMergeService class # diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index c741e913860..31b3ebf311e 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class RebaseService < MergeRequests::WorkingCopyBaseService REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 0127d781686..48da796505f 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class RefreshService < MergeRequests::BaseService def execute(oldrev, newrev, ref) diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb index 2ec7b403903..8d85dc9eb5f 100644 --- a/app/services/merge_requests/reload_diffs_service.rb +++ b/app/services/merge_requests/reload_diffs_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class ReloadDiffsService def initialize(merge_request, current_user) diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index 8f1c95ac1b7..f2fc13ad028 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class ReopenService < MergeRequests::BaseService def execute(merge_request) diff --git a/app/services/merge_requests/resolved_discussion_notification_service.rb b/app/services/merge_requests/resolved_discussion_notification_service.rb index 66a0cbc81d4..03ded1512f9 100644 --- a/app/services/merge_requests/resolved_discussion_notification_service.rb +++ b/app/services/merge_requests/resolved_discussion_notification_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class ResolvedDiscussionNotificationService < MergeRequests::BaseService def execute(merge_request) diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb index a40fb2786bd..a439a380255 100644 --- a/app/services/merge_requests/squash_service.rb +++ b/app/services/merge_requests/squash_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class SquashService < MergeRequests::WorkingCopyBaseService def execute(merge_request) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 7350725e223..b112edbce7f 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class UpdateService < MergeRequests::BaseService def execute(merge_request) diff --git a/app/services/merge_requests/working_copy_base_service.rb b/app/services/merge_requests/working_copy_base_service.rb index 186e05bf966..2d2be1f4c25 100644 --- a/app/services/merge_requests/working_copy_base_service.rb +++ b/app/services/merge_requests/working_copy_base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MergeRequests class WorkingCopyBaseService < MergeRequests::BaseService attr_reader :merge_request diff --git a/app/services/milestones/base_service.rb b/app/services/milestones/base_service.rb index cce0863d611..f30194c0bfe 100644 --- a/app/services/milestones/base_service.rb +++ b/app/services/milestones/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class BaseService < ::BaseService # Parent can either a group or a project diff --git a/app/services/milestones/close_service.rb b/app/services/milestones/close_service.rb index 5b06c4b601d..a252f5c144e 100644 --- a/app/services/milestones/close_service.rb +++ b/app/services/milestones/close_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class CloseService < Milestones::BaseService def execute(milestone) diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb index ed2e833d833..6c3edd2e147 100644 --- a/app/services/milestones/create_service.rb +++ b/app/services/milestones/create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class CreateService < Milestones::BaseService def execute diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb index b18651476a8..15c04525075 100644 --- a/app/services/milestones/destroy_service.rb +++ b/app/services/milestones/destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class DestroyService < Milestones::BaseService def execute(milestone) diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb index 2187f26d1ed..37aa6d3a9bc 100644 --- a/app/services/milestones/promote_service.rb +++ b/app/services/milestones/promote_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class PromoteService < Milestones::BaseService PromoteMilestoneError = Class.new(StandardError) diff --git a/app/services/milestones/reopen_service.rb b/app/services/milestones/reopen_service.rb index 3efb33157c5..125a3ec1367 100644 --- a/app/services/milestones/reopen_service.rb +++ b/app/services/milestones/reopen_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class ReopenService < Milestones::BaseService def execute(milestone) diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb index 74edbf9b41d..81b20943bab 100644 --- a/app/services/milestones/update_service.rb +++ b/app/services/milestones/update_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Milestones class UpdateService < Milestones::BaseService def execute(milestone) diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb index 77e7b8a5ea7..df5fe65de3c 100644 --- a/app/services/notes/build_service.rb +++ b/app/services/notes/build_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class BuildService < ::BaseService def execute diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 9ea28733f5f..049e6c5a871 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class CreateService < ::BaseService def execute diff --git a/app/services/notes/destroy_service.rb b/app/services/notes/destroy_service.rb index fb78420d324..64e9accd97f 100644 --- a/app/services/notes/destroy_service.rb +++ b/app/services/notes/destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class DestroyService < BaseService def execute(note) diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb index 199b8028dbc..48722cc2a79 100644 --- a/app/services/notes/post_process_service.rb +++ b/app/services/notes/post_process_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class PostProcessService attr_accessor :note diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb index 0a33d5f3f3d..7280449bb1c 100644 --- a/app/services/notes/quick_actions_service.rb +++ b/app/services/notes/quick_actions_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class QuickActionsService < BaseService UPDATE_SERVICES = { diff --git a/app/services/notes/render_service.rb b/app/services/notes/render_service.rb index efc9d6da2aa..0e1a55ae2ff 100644 --- a/app/services/notes/render_service.rb +++ b/app/services/notes/render_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class RenderService < BaseRenderer # Renders a collection of Note instances. diff --git a/app/services/notes/resolve_service.rb b/app/services/notes/resolve_service.rb index 0db8ee809a9..cf24795f050 100644 --- a/app/services/notes/resolve_service.rb +++ b/app/services/notes/resolve_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class ResolveService < ::BaseService def execute(note) diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index e16ef398184..35db409eb27 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Notes class UpdateService < BaseService def execute(note) diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb index 3047268b2d1..bbdde4408d2 100644 --- a/app/services/projects/after_import_service.rb +++ b/app/services/projects/after_import_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class AfterImportService RESERVED_REF_PREFIXES = Repository::RESERVED_REFS_NAMES.map { |n| File.join('refs', n, '/') } diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 9d0eaaf3152..10eb2cea4a2 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class AutocompleteService < BaseService def issues diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb index e8fd3ef57e5..78cc2869b72 100644 --- a/app/services/projects/base_move_relations_service.rb +++ b/app/services/projects/base_move_relations_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class BaseMoveRelationsService < BaseService attr_reader :source_project diff --git a/app/services/projects/batch_count_service.rb b/app/services/projects/batch_count_service.rb index 178ebc5a143..aec3b32da89 100644 --- a/app/services/projects/batch_count_service.rb +++ b/app/services/projects/batch_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Service class for getting and caching the number of elements of several projects # Warning: do not user this service with a really large set of projects # because the service use maps to retrieve the project ids. diff --git a/app/services/projects/batch_forks_count_service.rb b/app/services/projects/batch_forks_count_service.rb index e61fe6c86b2..9bf369df999 100644 --- a/app/services/projects/batch_forks_count_service.rb +++ b/app/services/projects/batch_forks_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Service class for getting and caching the number of forks of several projects # Warning: do not user this service with a really large set of projects # because the service use maps to retrieve the project ids diff --git a/app/services/projects/batch_open_issues_count_service.rb b/app/services/projects/batch_open_issues_count_service.rb index 3b0ade2419b..d375fcf9dbd 100644 --- a/app/services/projects/batch_open_issues_count_service.rb +++ b/app/services/projects/batch_open_issues_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Service class for getting and caching the number of issues of several projects # Warning: do not user this service with a really large set of projects # because the service use maps to retrieve the project ids diff --git a/app/services/projects/count_service.rb b/app/services/projects/count_service.rb index 4c8e000928f..3cee80c7bbc 100644 --- a/app/services/projects/count_service.rb +++ b/app/services/projects/count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects # Base class for the various service classes that count project data (e.g. # issues or forks). diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb index 29b133cc466..f5c48e56880 100644 --- a/app/services/projects/create_from_template_service.rb +++ b/app/services/projects/create_from_template_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class CreateFromTemplateService < BaseService def initialize(user, params) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 85491089d8e..02a3a3eb096 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class CreateService < BaseService def initialize(user, params) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 87173cc79ec..46a8a5e4d98 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class DestroyService < BaseService include Gitlab::ShellAdapter diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb index 604747e39d0..dd297c9ba43 100644 --- a/app/services/projects/download_service.rb +++ b/app/services/projects/download_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class DownloadService < BaseService WHITELIST = [ diff --git a/app/services/projects/enable_deploy_key_service.rb b/app/services/projects/enable_deploy_key_service.rb index 121385afca3..b7c172028e9 100644 --- a/app/services/projects/enable_deploy_key_service.rb +++ b/app/services/projects/enable_deploy_key_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class EnableDeployKeyService < BaseService def execute diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index a8aafa9fb4f..33ad2120a75 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class ForkService < BaseService def execute(fork_to_project = nil) diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb index dc6eb19affd..b570c6d4754 100644 --- a/app/services/projects/forks_count_service.rb +++ b/app/services/projects/forks_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects # Service class for getting and caching the number of forks of a project. class ForksCountService < Projects::CountService diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index a16268f4fd2..bc6e9caebb8 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This service is an adapter used to for the GitLab Import feature, and # creating a project from a template. # The latter will under the hood just import an archive supplied by GitLab. diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb index 35624577024..1392775f805 100644 --- a/app/services/projects/group_links/create_service.rb +++ b/app/services/projects/group_links/create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects module GroupLinks class CreateService < BaseService diff --git a/app/services/projects/group_links/destroy_service.rb b/app/services/projects/group_links/destroy_service.rb index e3a20b4c1e4..8aefad048ce 100644 --- a/app/services/projects/group_links/destroy_service.rb +++ b/app/services/projects/group_links/destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects module GroupLinks class DestroyService < BaseService diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb index bc897d891d5..649c916a593 100644 --- a/app/services/projects/hashed_storage/migrate_attachments_service.rb +++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects module HashedStorage AttachmentMigrationError = Class.new(StandardError) diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb index 68c1af2396b..70f00b7fdeb 100644 --- a/app/services/projects/hashed_storage/migrate_repository_service.rb +++ b/app/services/projects/hashed_storage/migrate_repository_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects module HashedStorage class MigrateRepositoryService < BaseService diff --git a/app/services/projects/hashed_storage_migration_service.rb b/app/services/projects/hashed_storage_migration_service.rb index 662702c1db5..1828c99a65e 100644 --- a/app/services/projects/hashed_storage_migration_service.rb +++ b/app/services/projects/hashed_storage_migration_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class HashedStorageMigrationService < BaseService attr_reader :logger diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index 120d57a188d..2f6dc4207dd 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Projects::HousekeepingService class # # Used for git housekeeping diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 7bf0b90b491..e3491282a8a 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects module ImportExport class ExportService < BaseService diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 1781a01cbd4..60f400edfce 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class ImportService < BaseService include Gitlab::ShellAdapter diff --git a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb index d9fb74b090e..a837ea82e38 100644 --- a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This service lists the download link from a remote source based on the # oids provided module Projects diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb index 618c30b971f..7d4fa4e08df 100644 --- a/app/services/projects/lfs_pointers/lfs_download_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This service downloads and links lfs objects from a remote URL module Projects module LfsPointers diff --git a/app/services/projects/lfs_pointers/lfs_import_service.rb b/app/services/projects/lfs_pointers/lfs_import_service.rb index b6b0dec142f..97ce681a911 100644 --- a/app/services/projects/lfs_pointers/lfs_import_service.rb +++ b/app/services/projects/lfs_pointers/lfs_import_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This service manages the whole worflow of discovering the Lfs files in a # repository, linking them to the project and downloading (and linking) the non # existent ones. diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb index d20bdf86c58..a2eba8e124e 100644 --- a/app/services/projects/lfs_pointers/lfs_link_service.rb +++ b/app/services/projects/lfs_pointers/lfs_link_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Given a list of oids, this services links the existent Lfs Objects to the project module Projects module LfsPointers diff --git a/app/services/projects/lfs_pointers/lfs_list_service.rb b/app/services/projects/lfs_pointers/lfs_list_service.rb index b770982cbc0..22160017f4f 100644 --- a/app/services/projects/lfs_pointers/lfs_list_service.rb +++ b/app/services/projects/lfs_pointers/lfs_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This service list all existent Lfs objects in a repository module Projects module LfsPointers diff --git a/app/services/projects/move_access_service.rb b/app/services/projects/move_access_service.rb index 3af3a22d486..8e2c3ad2f69 100644 --- a/app/services/projects/move_access_service.rb +++ b/app/services/projects/move_access_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class MoveAccessService < BaseMoveRelationsService def execute(source_project, remove_remaining_elements: true) diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb index dde420655b0..40a22837eaf 100644 --- a/app/services/projects/move_deploy_keys_projects_service.rb +++ b/app/services/projects/move_deploy_keys_projects_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class MoveDeployKeysProjectsService < BaseMoveRelationsService def execute(source_project, remove_remaining_elements: true) diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb index d2901ea1457..076a7a50aa9 100644 --- a/app/services/projects/move_forks_service.rb +++ b/app/services/projects/move_forks_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class MoveForksService < BaseMoveRelationsService def execute(source_project, remove_remaining_elements: true) diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb index 298da5f1a82..a5099519594 100644 --- a/app/services/projects/move_lfs_objects_projects_service.rb +++ b/app/services/projects/move_lfs_objects_projects_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class MoveLfsObjectsProjectsService < BaseMoveRelationsService def execute(source_project, remove_remaining_elements: true) diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb index f7be461a5da..746605d56f1 100644 --- a/app/services/projects/move_notification_settings_service.rb +++ b/app/services/projects/move_notification_settings_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class MoveNotificationSettingsService < BaseMoveRelationsService def execute(source_project, remove_remaining_elements: true) diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb index 5ef12fc49e5..60f2af88e99 100644 --- a/app/services/projects/move_project_authorizations_service.rb +++ b/app/services/projects/move_project_authorizations_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # NOTE: This service cannot be used directly because it is part of a # a bigger process. Instead, use the service MoveAccessService which moves # project memberships, project group links, authorizations and refreshes diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb index dbeffd7dae9..d9038030f7e 100644 --- a/app/services/projects/move_project_group_links_service.rb +++ b/app/services/projects/move_project_group_links_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # NOTE: This service cannot be used directly because it is part of a # a bigger process. Instead, use the service MoveAccessService which moves # project memberships, project group links, authorizations and refreshes diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb index 22a5f0a3fe6..bb0c0d10242 100644 --- a/app/services/projects/move_project_members_service.rb +++ b/app/services/projects/move_project_members_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # NOTE: This service cannot be used directly because it is part of a # a bigger process. Instead, use the service MoveAccessService which moves # project memberships, project group links, authorizations and refreshes diff --git a/app/services/projects/move_users_star_projects_service.rb b/app/services/projects/move_users_star_projects_service.rb index 079fd5b9685..20121d429e2 100644 --- a/app/services/projects/move_users_star_projects_service.rb +++ b/app/services/projects/move_users_star_projects_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class MoveUsersStarProjectsService < BaseMoveRelationsService def execute(source_project, remove_remaining_elements: true) diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb index 78b1477186a..5d6620c3c54 100644 --- a/app/services/projects/open_issues_count_service.rb +++ b/app/services/projects/open_issues_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects # Service class for counting and caching the number of open issues of a # project. diff --git a/app/services/projects/open_merge_requests_count_service.rb b/app/services/projects/open_merge_requests_count_service.rb index 77e6448fd5e..76ec13952ab 100644 --- a/app/services/projects/open_merge_requests_count_service.rb +++ b/app/services/projects/open_merge_requests_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects # Service class for counting and caching the number of open merge requests of # a project. diff --git a/app/services/projects/overwrite_project_service.rb b/app/services/projects/overwrite_project_service.rb index ce94f147aa9..696e1b665b2 100644 --- a/app/services/projects/overwrite_project_service.rb +++ b/app/services/projects/overwrite_project_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class OverwriteProjectService < BaseService def execute(source_project) diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index 21741913385..7080f388e53 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class ParticipantsService < BaseService include Users::ParticipableService diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb index a8ef2108492..fdfa91801ab 100644 --- a/app/services/projects/propagate_service_template.rb +++ b/app/services/projects/propagate_service_template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class PropagateServiceTemplate BATCH_SIZE = 100 diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 61acdd58021..a4a66330546 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Projects::TransferService class # # Used for transfer project to another namespace diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index 842fe4e09c4..2c0d91fe34f 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class UnlinkForkService < BaseService def execute diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 25017c5cbe3..efbd4c7b323 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class UpdatePagesConfigurationService < BaseService attr_reader :project diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 1d8caec9c6f..eb2478be3cf 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class UpdatePagesService < BaseService InvalidStateError = Class.new(StandardError) diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb index 8183a2f26d7..4651f7c4f8f 100644 --- a/app/services/projects/update_remote_mirror_service.rb +++ b/app/services/projects/update_remote_mirror_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class UpdateRemoteMirrorService < BaseService attr_reader :errors diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index f4fbaacc08b..d3dc11435fe 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Projects class UpdateService < BaseService include UpdateVisibilityLevel diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 83f7b99d2a5..b1365659834 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -136,10 +136,6 @@ class FileUploader < GitlabUploader } end - def filename - self.file.filename - end - def upload=(value) super diff --git a/app/views/admin/identities/edit.html.haml b/app/views/admin/identities/edit.html.haml index 1ad6ce969cb..fa09138c502 100644 --- a/app/views/admin/identities/edit.html.haml +++ b/app/views/admin/identities/edit.html.haml @@ -1,3 +1,6 @@ +- add_to_breadcrumbs "Users", admin_users_path +- add_to_breadcrumbs @user.name, admin_user_identities_path(@user) +- breadcrumb_title "Edit Identity" - page_title _("Edit"), @identity.provider, _("Identities"), @user.name, _("Users") %h3.page-title = _('Edit identity for %{user_name}') % { user_name: @user.name } diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml index 59373ee6752..df3df159947 100644 --- a/app/views/admin/identities/index.html.haml +++ b/app/views/admin/identities/index.html.haml @@ -1,3 +1,5 @@ +- add_to_breadcrumbs "Users", admin_users_path +- breadcrumb_title @user.name - page_title _("Identities"), @user.name, _("Users") = render 'admin/users/head' diff --git a/app/views/admin/identities/new.html.haml b/app/views/admin/identities/new.html.haml index ee743b0fd3c..c28d22625b5 100644 --- a/app/views/admin/identities/new.html.haml +++ b/app/views/admin/identities/new.html.haml @@ -1,3 +1,6 @@ +- add_to_breadcrumbs "Users", admin_users_path +- add_to_breadcrumbs @user.name, admin_user_identities_path(@user) +- breadcrumb_title "New Identity" - page_title _("New Identity") %h3.page-title= _('New identity') %hr diff --git a/app/views/admin/impersonation_tokens/index.html.haml b/app/views/admin/impersonation_tokens/index.html.haml index 1378dde52ab..9e490713ef3 100644 --- a/app/views/admin/impersonation_tokens/index.html.haml +++ b/app/views/admin/impersonation_tokens/index.html.haml @@ -1,3 +1,5 @@ +- add_to_breadcrumbs "Users", admin_users_path +- breadcrumb_title @user.name - page_title "Impersonation Tokens", @user.name, "Users" = render 'admin/users/head' diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml index 0f644121e62..103bbb3b063 100644 --- a/app/views/admin/users/keys.html.haml +++ b/app/views/admin/users/keys.html.haml @@ -1,3 +1,5 @@ +- add_to_breadcrumbs "Users", admin_users_path +- breadcrumb_title @user.name - page_title "SSH Keys", @user.name, "Users" = render 'admin/users/head' = render 'profiles/keys/key_table', admin: true diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index cf50d45f755..3d39c1da408 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -1,3 +1,5 @@ +- add_to_breadcrumbs "Users", admin_users_path +- breadcrumb_title @user.name - page_title "Groups and projects", @user.name, "Users" = render 'admin/users/head' diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index b0562226f5f..f730fd05176 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -149,8 +149,8 @@ %br = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - else - .card.bg-warning - .card-header + .card.border-warning + .card-header.bg-warning.text-white Block this user .card-body %p Blocking user has the following effects: @@ -170,8 +170,8 @@ %br = link_to 'Unlock user', unlock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - .card.bg-danger - .card-header + .card.border-danger + .card-header.bg-danger.text-white = s_('AdminUsers|Delete user') .card-body - if @user.can_be_removed? && can?(current_user, :destroy_user, @user) @@ -196,8 +196,8 @@ %p You don't have access to delete this user. - .card.bg-danger - .card-header + .card.border-danger + .card-header.bg-danger.text-white = s_('AdminUsers|Delete user and contributions') .card-body - if can?(current_user, :destroy_user, @user) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 89940512bc6..74ab8cf8250 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -12,6 +12,9 @@ .project-home-desc - if @project.description.present? = markdown_field(@project, :description) + - if can?(current_user, :read_project, @project) + .text-secondary.prepend-top-8 + = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id } - if @project.forked? %p diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml index 9298d93663d..73b11d509d3 100644 --- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml +++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml @@ -1,12 +1,12 @@ - link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') -.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' } +.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' } %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } × - %div - .col-sm-2.gcp-logo - = image_tag 'illustrations/logos/google-cloud-platform_logo.svg' - .col-sm-10 - %h4= s_('ClusterIntegration|Redeem up to $500 in free credit for Google Cloud Platform') + .gcp-signup-offer--content + .gcp-signup-offer--icon.append-right-8 + = sprite_icon("information", size: 16) + .gcp-signup-offer--copy + %h4= s_('ClusterIntegration|Did you know?') %p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link } - %a.btn.btn-info{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' } + %a.btn.btn-default{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' } Apply for credit diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml index f8db30df7b4..329b9e7e562 100644 --- a/app/views/projects/deploy_tokens/_form.html.haml +++ b/app/views/projects/deploy_tokens/_form.html.haml @@ -14,16 +14,16 @@ .form-group = f.label :scopes, class: 'label-light' - %fieldset - = f.check_box :read_repository - = label_tag ("deploy_token_read_repository"), 'read_repository' - %span= s_('DeployTokens|Allows read-only access to the repository') + %fieldset.form-group.form-check + = f.check_box :read_repository, class: 'form-check-input' + = label_tag ("deploy_token_read_repository"), 'read_repository', class: 'label-light form-check-label' + .text-secondary= s_('DeployTokens|Allows read-only access to the repository') - if container_registry_enabled?(project) - %fieldset - = f.check_box :read_registry - = label_tag ("deploy_token_read_registry"), 'read_registry' - %span= s_('DeployTokens|Allows read-only access to the registry images') + %fieldset.form-group.form-check + = f.check_box :read_registry, class: 'form-check-input' + = label_tag ("deploy_token_read_registry"), 'read_registry', class: 'label-light form-check-label' + .text-secondary= s_('DeployTokens|Allows read-only access to the registry images') .prepend-top-default = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success' diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index ca82054d799..8ce822c43b7 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -5,8 +5,8 @@ %hr - if @project.import_failed? - .card.bg-danger - .card-header The repository could not be imported. + .card.border-danger + .card-header.bg-danger.text-white The repository could not be imported. .card-body %pre :preserve diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 9b77c4e3494..ae8c801b705 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,7 +1,7 @@ - if @project.pages_deployed? - if can?(current_user, :remove_pages, @project) - .card.bg-danger - .card-header Remove pages + .card.border-danger + .card-header.bg-danger.text-white Remove pages .errors-holder .card-body %p diff --git a/changelogs/unreleased/27456-improve-feedback-when-dev-cannot-push-to-empty-repo.yml b/changelogs/unreleased/27456-improve-feedback-when-dev-cannot-push-to-empty-repo.yml new file mode 100644 index 00000000000..55d82c4ee5d --- /dev/null +++ b/changelogs/unreleased/27456-improve-feedback-when-dev-cannot-push-to-empty-repo.yml @@ -0,0 +1,5 @@ +--- +title: Improve feedback when a developer is unable to push to an empty repository +merge_request: 20519 +author: +type: changed diff --git a/changelogs/unreleased/41784-monitoring-graph-popovers.yml b/changelogs/unreleased/41784-monitoring-graph-popovers.yml new file mode 100644 index 00000000000..757445d7e0c --- /dev/null +++ b/changelogs/unreleased/41784-monitoring-graph-popovers.yml @@ -0,0 +1,5 @@ +--- +title: Update design for system metrics popovers +merge_request: 20655 +author: +type: fixed diff --git a/changelogs/unreleased/4525-fix-project-indexes.yml b/changelogs/unreleased/4525-fix-project-indexes.yml new file mode 100644 index 00000000000..930e3b934c2 --- /dev/null +++ b/changelogs/unreleased/4525-fix-project-indexes.yml @@ -0,0 +1,5 @@ +--- +title: Rework some projects table indexes around repository_storage field +merge_request: 20377 +author: +type: fixed diff --git a/changelogs/unreleased/47419-Fix-breadcrumbs.yml b/changelogs/unreleased/47419-Fix-breadcrumbs.yml new file mode 100644 index 00000000000..1a7f8196683 --- /dev/null +++ b/changelogs/unreleased/47419-Fix-breadcrumbs.yml @@ -0,0 +1,5 @@ +--- +title: Fix breadcrumbs in Admin/User interface. +merge_request: 19608 +author: Robin Naundorf +type: fixed diff --git a/changelogs/unreleased/48804-redesign-gcp-banner.yml b/changelogs/unreleased/48804-redesign-gcp-banner.yml new file mode 100644 index 00000000000..729f959badc --- /dev/null +++ b/changelogs/unreleased/48804-redesign-gcp-banner.yml @@ -0,0 +1,5 @@ +--- +title: Redesign GCP offer banner +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/49324-add-support-for-tar-gz-autodevops-charts.yml b/changelogs/unreleased/49324-add-support-for-tar-gz-autodevops-charts.yml new file mode 100644 index 00000000000..a87eef3f7f3 --- /dev/null +++ b/changelogs/unreleased/49324-add-support-for-tar-gz-autodevops-charts.yml @@ -0,0 +1,5 @@ +--- +title: Add support for tar.gz AUTO_DEVOPS_CHART charts (#49324) +merge_request: 20691 +author: '@kondi1' +type: added diff --git a/changelogs/unreleased/_acet-fix-expanding-context-lines.yml b/changelogs/unreleased/_acet-fix-expanding-context-lines.yml new file mode 100644 index 00000000000..41b4dbca5d6 --- /dev/null +++ b/changelogs/unreleased/_acet-fix-expanding-context-lines.yml @@ -0,0 +1,5 @@ +--- +title: Fix rendering of the context lines in MR diffs page +merge_request: 20642 +author: +type: fixed diff --git a/changelogs/unreleased/_acet-fix-mr-autosave.yml b/changelogs/unreleased/_acet-fix-mr-autosave.yml new file mode 100644 index 00000000000..f87b32f68e2 --- /dev/null +++ b/changelogs/unreleased/_acet-fix-mr-autosave.yml @@ -0,0 +1,5 @@ +--- +title: Fix autosave and ESC confirmation issues for MR discussions +merge_request: 20569 +author: +type: fixed diff --git a/changelogs/unreleased/_acet-fix-outdated-discussions.yml b/changelogs/unreleased/_acet-fix-outdated-discussions.yml new file mode 100644 index 00000000000..d31483b4765 --- /dev/null +++ b/changelogs/unreleased/_acet-fix-outdated-discussions.yml @@ -0,0 +1,5 @@ +--- +title: Fix showing outdated discussions on Changes tab +merge_request: 20445 +author: +type: fixed diff --git a/changelogs/unreleased/features-show-project-id-on-home-panel.yml b/changelogs/unreleased/features-show-project-id-on-home-panel.yml new file mode 100644 index 00000000000..f592be07a52 --- /dev/null +++ b/changelogs/unreleased/features-show-project-id-on-home-panel.yml @@ -0,0 +1,5 @@ +--- +title: Show Project ID on project home panel +merge_request: 20305 +author: Tuğçe Nur TaÅŸ +type: added diff --git a/changelogs/unreleased/fix-diff-note.yml b/changelogs/unreleased/fix-diff-note.yml new file mode 100644 index 00000000000..6f10f86b9bc --- /dev/null +++ b/changelogs/unreleased/fix-diff-note.yml @@ -0,0 +1,5 @@ +--- +title: Fix serialization of LegacyDiffNote +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fix-filename-for-direct-uploads.yml b/changelogs/unreleased/fix-filename-for-direct-uploads.yml new file mode 100644 index 00000000000..a1bbf3704c0 --- /dev/null +++ b/changelogs/unreleased/fix-filename-for-direct-uploads.yml @@ -0,0 +1,5 @@ +--- +title: Fix filename for accelerated uploads +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/frozen-string-enable-apps-services-inner-more.yml b/changelogs/unreleased/frozen-string-enable-apps-services-inner-more.yml new file mode 100644 index 00000000000..ea962cf8edc --- /dev/null +++ b/changelogs/unreleased/frozen-string-enable-apps-services-inner-more.yml @@ -0,0 +1,5 @@ +--- +title: Enable more frozen string in app/services/**/*.rb +merge_request: 20677 +author: gfyoung +type: performance diff --git a/changelogs/unreleased/osw-fallback-to-collection-when-no-diff-refs.yml b/changelogs/unreleased/osw-fallback-to-collection-when-no-diff-refs.yml new file mode 100644 index 00000000000..71a2d94fc55 --- /dev/null +++ b/changelogs/unreleased/osw-fallback-to-collection-when-no-diff-refs.yml @@ -0,0 +1,5 @@ +--- +title: Render MR page when diffs cannot be fetched from the database or the git repository +merge_request: 20680 +author: +type: fixed diff --git a/changelogs/unreleased/ravlen-deploy-tokens-display-update.yml b/changelogs/unreleased/ravlen-deploy-tokens-display-update.yml new file mode 100644 index 00000000000..fd5a6521882 --- /dev/null +++ b/changelogs/unreleased/ravlen-deploy-tokens-display-update.yml @@ -0,0 +1,5 @@ +--- +title: "Cleans up display of Deploy Tokens to match Personal Access Tokens" +merge_request: 20578 +author: Marcel Amirault +type: added
\ No newline at end of file diff --git a/changelogs/unreleased/sh-fix-issue-49133.yml b/changelogs/unreleased/sh-fix-issue-49133.yml new file mode 100644 index 00000000000..847220d88b2 --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-49133.yml @@ -0,0 +1,5 @@ +--- +title: Fix symlink vulnerability in project import +merge_request: +author: +type: security diff --git a/changelogs/unreleased/sh-limit-unauthenticated-session-times.yml b/changelogs/unreleased/sh-limit-unauthenticated-session-times.yml new file mode 100644 index 00000000000..44a46b4115e --- /dev/null +++ b/changelogs/unreleased/sh-limit-unauthenticated-session-times.yml @@ -0,0 +1,5 @@ +--- +title: Limit the TTL for anonymous sessions to 1 hour +merge_request: 20700 +author: +type: performance diff --git a/changelogs/unreleased/toggle-password-cluster.yml b/changelogs/unreleased/toggle-password-cluster.yml new file mode 100644 index 00000000000..1a43c4baa25 --- /dev/null +++ b/changelogs/unreleased/toggle-password-cluster.yml @@ -0,0 +1,5 @@ +--- +title: Toggle Show / Hide Button for Kubernetes Password +merge_request: 20659 +author: gfyoung +type: fixed diff --git a/changelogs/unreleased/update-card-body-style.yml b/changelogs/unreleased/update-card-body-style.yml new file mode 100644 index 00000000000..d9197c18502 --- /dev/null +++ b/changelogs/unreleased/update-card-body-style.yml @@ -0,0 +1,5 @@ +--- +title: Remove background color from card-body style +merge_request: 20689 +author: George Tsiolis +type: fixed diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4b9cc59ec45..44bc72a7185 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -140,6 +140,7 @@ Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10 Settings.gitlab['session_expire_delay'] ||= 10080 +Settings.gitlab['unauthenticated_session_expire_delay'] ||= 1.hour.to_i Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil? Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil? Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? diff --git a/db/post_migrate/20180704145007_update_project_indexes.rb b/db/post_migrate/20180704145007_update_project_indexes.rb new file mode 100644 index 00000000000..193563b36db --- /dev/null +++ b/db/post_migrate/20180704145007_update_project_indexes.rb @@ -0,0 +1,23 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class UpdateProjectIndexes < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + NEW_INDEX_NAME = 'idx_project_repository_check_partial' + + disable_ddl_transaction! + + def up + add_concurrent_index(:projects, + [:repository_storage, :created_at], + name: NEW_INDEX_NAME, + where: 'last_repository_check_at IS NULL' + ) + end + + def down + remove_concurrent_index_by_name(:projects, NEW_INDEX_NAME) + end +end diff --git a/db/schema.rb b/db/schema.rb index d2aa31fae30..1a5555fb3a4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1674,6 +1674,7 @@ ActiveRecord::Schema.define(version: 20180704204006) do add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree + add_index "projects", ["repository_storage", "created_at"], name: "idx_project_repository_check_partial", where: "(last_repository_check_at IS NULL)", using: :btree add_index "projects", ["repository_storage"], name: "index_projects_on_repository_storage", using: :btree add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index 48f3df1925a..692f81dd7cd 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -16,16 +16,8 @@ The default deployment includes: ### Limitations -Some features and functions are not currently available in the beta release: -* [GitLab Pages](../../user/project/pages/) -* [Reply by email](../../administration/reply_by_email.html) -* [Project templates](../../gitlab-basics/create-project.html) -* [Project import/export](../../user/project/settings/import_export.html) -* [Geo](https://docs.gitlab.com/ee/administration/geo/replication/) - -Currently out of scope: -* [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/) -* [MySQL support](https://docs.gitlab.com/omnibus/settings/database.html#using-a-mysql-database-management-server-enterprise-edition-only) +Some features and functions are not currently available in the beta release. +For details, see [known issues and limitations](https://gitlab.com/charts/gitlab/blob/master/doc/architecture/beta.md#known-issues-and-limitations) in the charts repository. ## Prerequisites diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index d054561d5f3..20886faf418 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -64,7 +64,8 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md). ## Repository size limit -The maximum size your Git repository is allowed to be including LFS. +The maximum size your Git repository is allowed to be, including LFS. If you are near +or over the size limit, you can [reduce your repository size with Git](../project/repository/reducing_the_repo_size_using_git.md). | Setting | GitLab.com | Default | | ----------- | ----------------- | ------------- | diff --git a/lib/api/entities.rb b/lib/api/entities.rb index b256c33c631..3f3a95ea8e6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -701,7 +701,7 @@ module API expose :system?, as: :system expose :noteable_id, :noteable_type - expose :position, if: ->(note, options) { note.diff_note? } do |note| + expose :position, if: ->(note, options) { note.is_a?(DiffNote) } do |note| note.position.to_h end diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index f76a6fb5f17..7a4224e5bbe 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -93,7 +93,7 @@ module Gitlab end else unless user_access.can_push_to_branch?(branch_name) - raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_protected_branch] + raise GitAccess::UnauthorizedError, push_to_protected_branch_rejected_message end end end @@ -140,6 +140,29 @@ module Gitlab private + def push_to_protected_branch_rejected_message + if project.empty_repo? + empty_project_push_message + else + ERROR_MESSAGES[:push_protected_branch] + end + end + + def empty_project_push_message + <<~MESSAGE + + A default branch (e.g. master) does not yet exist for #{project.full_path} + Ask a project Owner or Maintainer to create a default branch: + + #{project_members_url} + + MESSAGE + end + + def project_members_url + Gitlab::Routing.url_helpers.project_project_members_url(project) + end + def should_run_commit_validations? commit_check.validate_lfs_file_locks? end diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 4e2d817d12c..5b264868af0 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -1,4 +1,4 @@ -# Gitlab::Git::Commit is a wrapper around native Rugged::Commit object +# Gitlab::Git::Commit is a wrapper around Gitaly::GitCommit module Gitlab module Git class Commit @@ -55,7 +55,6 @@ module Gitlab # A rugged reference? commit_id = Gitlab::Git::Ref.dereference_object(commit_id) - return decorate(repo, commit_id) if commit_id.is_a?(Rugged::Commit) # Some weird thing? return nil unless commit_id.is_a?(String) @@ -68,9 +67,7 @@ module Gitlab end decorate(repo, commit) if commit - rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, - Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository, - Rugged::OdbError, Rugged::TreeError, ArgumentError + rescue Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository, ArgumentError nil end @@ -142,20 +139,6 @@ module Gitlab Gitlab::Git::Commit.new(repository, commit, ref) end - # Returns the `Rugged` sorting type constant for one or more given - # sort types. Valid keys are `:none`, `:topo`, and `:date`, or an array - # containing more than one of them. `:date` uses a combination of date and - # topological sorting to closer mimic git's native ordering. - def rugged_sort_type(sort_type) - @rugged_sort_types ||= { - none: Rugged::SORT_NONE, - topo: Rugged::SORT_TOPO, - date: Rugged::SORT_DATE | Rugged::SORT_TOPO - } - - @rugged_sort_types.fetch(sort_type, Rugged::SORT_NONE) - end - def shas_with_signatures(repository, shas) Gitlab::GitalyClient::CommitService.new(repository).filter_shas_with_signatures(shas) end @@ -223,8 +206,6 @@ module Gitlab case raw_commit when Hash init_from_hash(raw_commit) - when Rugged::Commit - init_from_rugged(raw_commit) when Gitaly::GitCommit init_from_gitaly(raw_commit) else @@ -265,23 +246,6 @@ module Gitlab @repository.gitaly_commit_client.diff_from_parent(self, options) end - # Not to be called directly, but right now its used for tests and in old - # migrations - def rugged_diff_from_parent(options = {}) - options ||= {} - break_rewrites = options[:break_rewrites] - actual_options = Gitlab::Git::Diff.filter_diff_options(options) - - diff = if rugged_commit.parents.empty? - rugged_commit.diff(actual_options.merge(reverse: true)) - else - rugged_commit.parents[0].diff(rugged_commit, actual_options) - end - - diff.find_similar!(break_rewrites: break_rewrites) - diff - end - def deltas @deltas ||= begin deltas = @repository.gitaly_commit_client.commit_deltas(self) @@ -352,14 +316,6 @@ module Gitlab encode! @committer_email end - def rugged_commit - @rugged_commit ||= if raw_commit.is_a?(Rugged::Commit) - raw_commit - else - @repository.rev_parse_target(id) - end - end - def merge_commit? parent_ids.size > 1 end @@ -405,22 +361,6 @@ module Gitlab end end - def init_from_rugged(commit) - author = commit.author - committer = commit.committer - - @raw_commit = commit - @id = commit.oid - @message = commit.message - @authored_date = author[:time] - @committed_date = committer[:time] - @author_name = author[:name] - @author_email = author[:email] - @committer_name = committer[:name] - @committer_email = committer[:email] - @parent_ids = commit.parents.map(&:oid) - end - def init_from_gitaly(commit) @raw_commit = commit @id = commit.id diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 2cbd9c218d4..39fbf6e2526 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -252,14 +252,6 @@ module Gitlab end end - def batch_existence(object_ids, existing: true) - filter_method = existing ? :select : :reject - - object_ids.public_send(filter_method) do |oid| # rubocop:disable GitlabSecurity/PublicSend - rugged.exists?(oid) - end - end - # Returns an Array of branch and tag names def ref_names branch_names + tag_names @@ -389,6 +381,21 @@ module Gitlab end end + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 + def new_commits(newrev) + gitaly_migrate(:new_commits) do |is_enabled| + if is_enabled + gitaly_ref_client.list_new_commits(newrev) + else + refs = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + rev_list(including: newrev, excluding: :all).split("\n").map(&:strip) + end + + Gitlab::Git::Commit.batch_by_oid(self, refs) + end + end + end + def count_commits(options) options = process_count_commits_options(options.dup) @@ -409,13 +416,6 @@ module Gitlab end end - # Return the object that +revspec+ points to. If +revspec+ is an - # annotated tag, then return the tag's target instead. - def rev_parse_target(revspec) - obj = rugged.rev_parse(revspec) - Ref.dereference_object(obj) - end - # Counts the amount of commits between `from` and `to`. def count_commits_between(from, to, options = {}) count_commits(from: from, to: to, **options) @@ -1132,33 +1132,6 @@ module Gitlab run_git!(args, lazy_block: block) end - def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:) - base_args = %w(worktree add --detach) - - # Note that we _don't_ want to test for `.present?` here: If the caller - # passes an non nil empty value it means it still wants sparse checkout - # but just isn't interested in any file, perhaps because it wants to - # checkout files in by a changeset but that changeset only adds files. - if sparse_checkout_files - # Create worktree without checking out - run_git!(base_args + ['--no-checkout', worktree_path], env: env) - worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp - - configure_sparse_checkout(worktree_git_path, sparse_checkout_files) - - # After sparse checkout configuration, checkout `branch` in worktree - run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env) - else - # Create worktree and checkout `branch` in it - run_git!(base_args + [worktree_path, branch], env: env) - end - - yield - ensure - FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path) - FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path) - end - def checksum # The exists? RPC is much cheaper, so we perform this request first raise NoRepository, "Repository does not exists" unless exists? @@ -1204,38 +1177,6 @@ module Gitlab end end - # Adding a worktree means checking out the repository. For large repos, - # this can be very expensive, so set up sparse checkout for the worktree - # to only check out the files we're interested in. - def configure_sparse_checkout(worktree_git_path, files) - run_git!(%w(config core.sparseCheckout true)) - - return if files.empty? - - worktree_info_path = File.join(worktree_git_path, 'info') - FileUtils.mkdir_p(worktree_info_path) - File.write(File.join(worktree_info_path, 'sparse-checkout'), files) - end - - def rugged_fetch_source_branch(source_repository, source_branch, local_ref) - with_repo_branch_commit(source_repository, source_branch) do |commit| - if commit - write_ref(local_ref, commit.sha) - true - else - false - end - end - end - - def worktree_path(prefix, id) - id = id.to_s - raise ArgumentError, "worktree id can't be empty" unless id.present? - raise ArgumentError, "worktree id can't contain slashes " if id.include?("/") - - File.join(path, 'gitlab-worktree', "#{prefix}-#{id}") - end - def git_env_for_user(user) { 'GIT_COMMITTER_NAME' => user.name, @@ -1296,17 +1237,6 @@ module Gitlab Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact end - # Return the Rugged patches for the diff between +from+ and +to+. - def diff_patches(from, to, options = {}, *paths) - options ||= {} - break_rewrites = options[:break_rewrites] - actual_options = Gitlab::Git::Diff.filter_diff_options(options.merge(paths: paths)) - - diff = rugged.diff(from, to, actual_options) - diff.find_similar!(break_rewrites: break_rewrites) - diff.each_patch - end - def sort_branches(branches, sort_by) case sort_by when 'name' @@ -1394,10 +1324,6 @@ module Gitlab def rev_list_param(spec) spec == :all ? ['--all'] : spec end - - def sha_from_ref(ref) - rev_parse_target(ref).oid - end end end end diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb index e35ea5762eb..9faa62be28e 100644 --- a/lib/gitlab/git/repository_mirroring.rb +++ b/lib/gitlab/git/repository_mirroring.rb @@ -50,7 +50,7 @@ module Gitlab name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '') begin - target_commit = Gitlab::Git::Commit.find(self, ref.target) + target_commit = Gitlab::Git::Commit.find(self, ref.target.oid) branches << Gitlab::Git::Branch.new(self, name, ref.target, target_commit) rescue Rugged::ReferenceError # Omit invalid branch diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb deleted file mode 100644 index 2ba68343aa5..00000000000 --- a/lib/gitlab/git/rev_list.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Gitlab - module Git - class RevList - include Gitlab::Git::Popen - - attr_reader :oldrev, :newrev, :repository - - def initialize(repository, newrev:, oldrev: nil) - @oldrev = oldrev - @newrev = newrev - @repository = repository - end - - # This method returns an array of new commit references - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 - # - def new_refs - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - repository.rev_list(including: newrev, excluding: :all).split("\n") - end - end - - private - - def execute(args) - repository.rev_list(args).split("\n") - end - - def get_objects(including: [], excluding: [], options: [], require_path: nil) - opts = { including: including, excluding: excluding, options: options, objects: true } - - repository.rev_list(opts) do |lazy_output| - objects = objects_from_output(lazy_output, require_path: require_path) - - yield(objects) - end - end - - def objects_from_output(object_output, require_path: nil) - object_output.map do |output_line| - sha, path = output_line.split(' ', 2) - - next if require_path && path.to_s.empty? - - sha - end.reject(&:nil?) - end - end - end -end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index 7f4eed9222a..1ad6376dac7 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -56,6 +56,25 @@ module Gitlab encode!(response.name.dup) end + def list_new_commits(newrev) + request = Gitaly::ListNewCommitsRequest.new( + repository: @gitaly_repo, + commit_id: newrev + ) + + response = GitalyClient + .call(@storage, :ref_service, :list_new_commits, request, timeout: GitalyClient.medium_timeout) + + commits = [] + response.each do |msg| + msg.commits.each do |c| + commits << Gitlab::Git::Commit.new(@repository, c) + end + end + + commits + end + def count_tag_names tag_names.count end diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index 0f4c3498036..4c411f4847e 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -4,6 +4,7 @@ module Gitlab include Gitlab::ImportExport::CommandLineUtil MAX_RETRIES = 8 + IGNORED_FILENAMES = %w(. ..).freeze def self.import(*args) new(*args).import @@ -59,7 +60,7 @@ module Gitlab end def extracted_files - Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ %r{.*/\.{1,2}$} } + Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| IGNORED_FILENAMES.include?(File.basename(f)) } end end end diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index 4b9cb59eab5..53e5ac02e42 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -21,7 +21,7 @@ class UploadedFile raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path) @content_type = content_type - @original_filename = filename || ::File.basename(path) + @original_filename = sanitize_filename(filename || path) @content_type = content_type @sha256 = sha256 @remote_id = remote_id @@ -55,6 +55,16 @@ class UploadedFile end end + # copy-pasted from CarrierWave::SanitizedFile + def sanitize_filename(name) + name = name.tr("\\", "/") # work-around for IE + name = ::File.basename(name) + name = name.gsub(CarrierWave::SanitizedFile.sanitize_regexp, "_") + name = "_#{name}" if name =~ /\A\.+\z/ + name = "unnamed" if name.empty? + name.mb_chars.to_s + end + def path @tempfile.path end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 973d3214e08..bcc27a4f805 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,6 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-07-10 16:02-0700\n" +"PO-Revision-Date: 2018-07-10 16:02-0700\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -1248,6 +1250,9 @@ msgstr "" msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" +msgid "ClusterIntegration|Did you know?" +msgstr "" + msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" @@ -1284,6 +1289,9 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" +msgid "ClusterIntegration|Hide" +msgstr "" + msgid "ClusterIntegration|Ingress" msgstr "" @@ -1410,9 +1418,6 @@ msgstr "" msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." msgstr "" -msgid "ClusterIntegration|Redeem up to $500 in free credit for Google Cloud Platform" -msgstr "" - msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" @@ -3533,6 +3538,9 @@ msgstr "" msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token." msgstr "" +msgid "Notes|Are you sure you want to cancel creating this comment?" +msgstr "" + msgid "Notification events" msgstr "" @@ -4067,6 +4075,9 @@ msgstr "" msgid "ProjectLifecycle|Stage" msgstr "" +msgid "ProjectPage|Project ID: %{project_id}" +msgstr "" + msgid "Projects" msgstr "" @@ -5870,6 +5881,9 @@ msgstr "" msgid "branch name" msgstr "" +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + msgid "command line instructions" msgstr "" diff --git a/package.json b/package.json index 9dd7d80945f..e1801d4d435 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { - "@gitlab-org/gitlab-svgs": "^1.25.0", + "@gitlab-org/gitlab-svgs": "^1.26.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-core": "^6.26.3", diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index fc753554fc4..3df6db05970 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -59,7 +59,7 @@ module QA end def add_file(name, contents) - File.write(name, contents) + ::File.write(name, contents) `git add #{name}` end diff --git a/qa/qa/page/component/dropzone.rb b/qa/qa/page/component/dropzone.rb index 15bdc742fda..fd44c57123a 100644 --- a/qa/qa/page/component/dropzone.rb +++ b/qa/qa/page/component/dropzone.rb @@ -15,7 +15,7 @@ module QA # instantiated on one page because there is no distinguishing # attribute per dropzone file field. def attach_file(attachment) - filename = File.basename(attachment) + filename = ::File.basename(attachment) field_style = { visibility: 'visible', height: '', width: '' } page.attach_file(attachment, class: 'dz-hidden-input', make_visible: field_style) diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb index c33ada0de7a..ff9f0004524 100644 --- a/qa/qa/runtime/api/request.rb +++ b/qa/qa/runtime/api/request.rb @@ -28,7 +28,7 @@ module QA # # Returns the relative path to the requested API resource def request_path(path, version: API_VERSION, **query_string) - full_path = File.join('/api', version, path) + full_path = ::File.join('/api', version, path) if query_string.any? full_path << (path.include?('?') ? '&' : '?') diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 877864fb40c..0c8eca5229e 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -86,7 +86,7 @@ module QA end Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| - File.join(QA::Runtime::Namespace.name, example.file_path.sub('./qa/specs/features/', '')) + ::File.join(QA::Runtime::Namespace.name, example.file_path.sub('./qa/specs/features/', '')) end Capybara.configure do |config| @@ -94,7 +94,7 @@ module QA config.javascript_driver = :chrome config.default_max_wait_time = 10 # https://github.com/mattheworiordan/capybara-screenshot/issues/164 - config.save_path = File.expand_path('../../tmp', __dir__) + config.save_path = ::File.expand_path('../../tmp', __dir__) end end diff --git a/qa/qa/runtime/key/base.rb b/qa/qa/runtime/key/base.rb index c7e5ebada7b..67a992e2115 100644 --- a/qa/qa/runtime/key/base.rb +++ b/qa/qa/runtime/key/base.rb @@ -25,8 +25,8 @@ module QA end def populate_key_data(path) - @private_key = File.binread(path) - @public_key = File.binread("#{path}.pub") + @private_key = ::File.binread(path) + @public_key = ::File.binread("#{path}.pub") @fingerprint = `ssh-keygen -l -E md5 -f #{path} | cut -d' ' -f2 | cut -d: -f2-`.chomp end diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb index 4f83a773645..b1f7ec482c8 100644 --- a/qa/qa/runtime/release.rb +++ b/qa/qa/runtime/release.rb @@ -13,7 +13,7 @@ module QA end def version - @version ||= File.directory?("#{__dir__}/../ee") ? :EE : :CE + @version ||= ::File.directory?("#{__dir__}/../ee") ? :EE : :CE end def strategy diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb index 567e5fd6cca..46eb2dabb11 100644 --- a/qa/qa/scenario/test/instance.rb +++ b/qa/qa/scenario/test/instance.rb @@ -26,7 +26,7 @@ module QA if rspec_options.any? rspec_options else - File.expand_path('../../specs/features', __dir__) + ::File.expand_path('../../specs/features', __dir__) end end end diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb index ee1f08da238..5c65128d10c 100644 --- a/qa/spec/git/repository_spec.rb +++ b/qa/spec/git/repository_spec.rb @@ -29,7 +29,7 @@ describe QA::Git::Repository do def cd_empty_temp_directory tmp_dir = 'tmp/git-repository-spec/' - FileUtils.rm_r(tmp_dir) if File.exist?(tmp_dir) + FileUtils.rm_r(tmp_dir) if ::File.exist?(tmp_dir) FileUtils.mkdir_p tmp_dir FileUtils.cd tmp_dir end diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index aedbc3863a7..34d2ff11447 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -32,7 +32,7 @@ describe QA::Page::View do context 'when pattern is found' do before do - allow(File).to receive(:foreach) + allow(::File).to receive(:foreach) .and_yield('some element').once allow(element).to receive(:matches?) .with('some element').and_return(true) @@ -45,7 +45,7 @@ describe QA::Page::View do context 'when pattern has not been found' do before do - allow(File).to receive(:foreach) + allow(::File).to receive(:foreach) .and_yield('some element').once allow(element).to receive(:matches?) .with('some element').and_return(false) diff --git a/qa/spec/scenario/test/instance_spec.rb b/qa/spec/scenario/test/instance_spec.rb index a74a9538be8..0d0b534911f 100644 --- a/qa/spec/scenario/test/instance_spec.rb +++ b/qa/spec/scenario/test/instance_spec.rb @@ -30,7 +30,7 @@ describe QA::Scenario::Test::Instance do subject.perform("test") expect(runner).to have_received(:options=) - .with(File.expand_path('../../../qa/specs/features', __dir__)) + .with(::File.expand_path('../../../qa/specs/features', __dir__)) end end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index c2c6cf95406..8e6613cd688 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -1,6 +1,6 @@ require_relative '../qa' -Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f } +Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f } RSpec.configure do |config| config.expect_with :rspec do |expectations| diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 74f362fd7fc..f1165c73847 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -89,6 +89,32 @@ describe ApplicationController do end end + describe 'session expiration' do + controller(described_class) do + def index + render text: 'authenticated' + end + end + + context 'authenticated user' do + it 'does not set the expire_after option' do + sign_in(create(:user)) + + get :index + + expect(request.env['rack.session.options'][:expire_after]).to be_nil + end + end + + context 'unauthenticated user' do + it 'sets the expire_after option' do + get :index + + expect(request.env['rack.session.options'][:expire_after]).to eq(Settings.gitlab['unauthenticated_session_expire_delay']) + end + end + end + describe 'rescue from Gitlab::Git::Storage::Inaccessible' do controller(described_class) do def index diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 444415011a9..1692f299552 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -53,6 +53,23 @@ describe Projects::MergeRequestsController do it_behaves_like "loads labels", :show describe 'as html' do + context 'when diff files were cleaned' do + render_views + + it 'renders page when diff size is not persisted and diff_refs does not exist' do + diff = merge_request.merge_request_diff + + diff.clean! + diff.update!(real_size: nil, + start_commit_sha: nil, + base_commit_sha: nil) + + go(format: :html) + + expect(response).to be_success + end + end + it "renders merge request page" do go(format: :html) diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 9fdc3e616a6..6844ed8aa4a 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -39,6 +39,7 @@ FactoryBot.define do factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote do association :project, :repository + position '' end factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 9e3221577c7..6c194c9a646 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -315,6 +315,40 @@ describe "Admin::Users" do end end + describe 'show breadcrumbs' do + it do + visit admin_user_path(user) + + check_breadcrumb(user.name) + + visit projects_admin_user_path(user) + + check_breadcrumb(user.name) + + visit keys_admin_user_path(user) + + check_breadcrumb(user.name) + + visit admin_user_impersonation_tokens_path(user) + + check_breadcrumb(user.name) + + visit admin_user_identities_path(user) + + check_breadcrumb(user.name) + + visit new_admin_user_identity_path(user) + + check_breadcrumb("New Identity") + + visit admin_user_identities_path(user) + + find('.table').find(:link, 'Edit').click + + check_breadcrumb("Edit Identity") + end + end + describe 'show user attributes' do it do visit admin_users_path @@ -409,4 +443,8 @@ describe "Admin::Users" do expect(page).not_to have_content('twitter') end end + + def check_breadcrumb(content) + expect(find('.breadcrumbs-sub-title')).to have_content(content) + end end diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index 93d8e672f8c..82f588d1a08 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -55,6 +55,29 @@ describe IconsHelper do expect(sprite_icon(icon_name, size: 72, css_class: 'icon-danger').to_s) .to eq "<svg class=\"s72 icon-danger\"><use xlink:href=\"#{icons_path}##{icon_name}\"></use></svg>" end + + describe 'non existing icon' do + non_existing = 'non_existing_icon_sprite' + + it 'should raise in development mode' do + allow(Rails.env).to receive(:development?).and_return(true) + + expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) + end + + it 'should raise in test mode' do + allow(Rails.env).to receive(:test?).and_return(true) + + expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) + end + + it 'should not raise in production mode' do + allow(Rails.env).to receive(:test?).and_return(false) + allow(Rails.env).to receive(:development?).and_return(false) + + expect { sprite_icon(non_existing) }.not_to raise_error + end + end end describe 'file_type_icon_class' do diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 6c9a7febf14..8bfd520528f 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -55,6 +55,20 @@ describe SearchHelper do expect(search_autocomplete_opts(project.name).size).to eq(1) end + it "includes the required project attrs" do + project = create(:project, namespace: create(:namespace, owner: user)) + result = search_autocomplete_opts(project.name).first + + expect(result.keys).to match_array(%i[category id value label url avatar_url]) + end + + it "includes the required group attrs" do + create(:group).add_owner(user) + result = search_autocomplete_opts("gro").first + + expect(result.keys).to match_array(%i[category id label url avatar_url]) + end + it "does not include the public group" do group = create(:group) expect(search_autocomplete_opts(group.name).size).to eq(0) diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js index 38ae5b7e00c..dcb1c781591 100644 --- a/spec/javascripts/autosave_spec.js +++ b/spec/javascripts/autosave_spec.js @@ -59,12 +59,10 @@ describe('Autosave', () => { Autosave.prototype.restore.call(autosave); - expect( - field.trigger, - ).toHaveBeenCalled(); + expect(field.trigger).toHaveBeenCalled(); }); - it('triggers native event', (done) => { + it('triggers native event', done => { autosave.field.get(0).addEventListener('change', () => { done(); }); @@ -81,9 +79,7 @@ describe('Autosave', () => { it('does not trigger event', () => { spyOn(field, 'trigger').and.callThrough(); - expect( - field.trigger, - ).not.toHaveBeenCalled(); + expect(field.trigger).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index abe2954d506..839b8a06b48 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -45,17 +45,33 @@ describe('Clusters', () => { }); describe('showToken', () => { - it('should update tye field type', () => { + it('should update token field type', () => { cluster.showTokenButton.click(); + expect( cluster.tokenField.getAttribute('type'), ).toEqual('text'); cluster.showTokenButton.click(); + expect( cluster.tokenField.getAttribute('type'), ).toEqual('password'); }); + + it('should update show token button text', () => { + cluster.showTokenButton.click(); + + expect( + cluster.showTokenButton.textContent, + ).toEqual('Hide'); + + cluster.showTokenButton.click(); + + expect( + cluster.showTokenButton.textContent, + ).toEqual('Show'); + }); }); describe('checkForNewInstalls', () => { diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js index 2d136a63c52..cb85d12daf2 100644 --- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js +++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js @@ -18,10 +18,12 @@ describe('DiffLineGutterContent', () => { }; const setDiscussions = component => { component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); + component.$store.commit('diffs/SET_DIFF_DATA', { diffFiles: [getDiffFileMock()] }); }; const resetDiscussions = component => { component.$store.dispatch('setInitialNotes', []); + component.$store.commit('diffs/SET_DIFF_DATA', {}); }; describe('computed', () => { diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js index 4600aaea70b..6fe5fdaf7f9 100644 --- a/spec/javascripts/diffs/components/diff_line_note_form_spec.js +++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js @@ -3,6 +3,7 @@ import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue'; import store from '~/mr_notes/stores'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import diffFileMockData from '../mock_data/diff_file'; +import { noteableDataMock } from '../../notes/mock_data'; describe('DiffLineNoteForm', () => { let component; @@ -21,10 +22,9 @@ describe('DiffLineNoteForm', () => { noteTargetLine: diffLines[0], }); - Object.defineProperty(component, 'isLoggedIn', { - get() { - return true; - }, + Object.defineProperties(component, { + noteableData: { value: noteableDataMock }, + isLoggedIn: { value: true }, }); component.$mount(); @@ -32,12 +32,37 @@ describe('DiffLineNoteForm', () => { describe('methods', () => { describe('handleCancelCommentForm', () => { - it('should call cancelCommentForm with lineCode', () => { + it('should ask for confirmation when shouldConfirm and isDirty passed as truthy', () => { + spyOn(window, 'confirm').and.returnValue(false); + + component.handleCancelCommentForm(true, true); + expect(window.confirm).toHaveBeenCalled(); + }); + + it('should ask for confirmation when one of the params false', () => { + spyOn(window, 'confirm').and.returnValue(false); + + component.handleCancelCommentForm(true, false); + expect(window.confirm).not.toHaveBeenCalled(); + + component.handleCancelCommentForm(false, true); + expect(window.confirm).not.toHaveBeenCalled(); + }); + + it('should call cancelCommentForm with lineCode', done => { + spyOn(window, 'confirm'); spyOn(component, 'cancelCommentForm'); + spyOn(component, 'resetAutoSave'); component.handleCancelCommentForm(); - expect(component.cancelCommentForm).toHaveBeenCalledWith({ - lineCode: diffLines[0].lineCode, + expect(window.confirm).not.toHaveBeenCalled(); + component.$nextTick(() => { + expect(component.cancelCommentForm).toHaveBeenCalledWith({ + lineCode: diffLines[0].lineCode, + }); + expect(component.resetAutoSave).toHaveBeenCalled(); + + done(); }); }); }); @@ -66,7 +91,7 @@ describe('DiffLineNoteForm', () => { describe('mounted', () => { it('should init autosave', () => { - const key = 'autosave/Note/issue///DiffNote//1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1'; + const key = 'autosave/Note/Issue/98//DiffNote//1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1'; expect(component.autosave).toBeDefined(); expect(component.autosave.key).toEqual(key); diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js index b02328dd359..90dfa5c5a58 100644 --- a/spec/javascripts/diffs/components/inline_diff_view_spec.js +++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js @@ -33,6 +33,7 @@ describe('InlineDiffView', () => { it('should render discussions', done => { const el = component.$el; component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); + component.$store.commit('diffs/SET_DIFF_DATA', { diffFiles: [getDiffFileMock()] }); Vue.nextTick(() => { expect(el.querySelectorAll('.notes_holder').length).toEqual(1); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 41d0dfd8939..8cd57d2248b 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -12,6 +12,17 @@ export default { head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', }, }, + original_position: { + formatter: { + old_line: null, + new_line: 2, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + }, + }, line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', expanded: true, notes: [ diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js index 6210d4a7124..f5628a01a55 100644 --- a/spec/javascripts/diffs/store/getters_spec.js +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -2,6 +2,7 @@ import * as getters from '~/diffs/store/getters'; import state from '~/diffs/store/modules/diff_state'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; import discussion from '../mock_data/diff_discussions'; +import diffFile from '../mock_data/diff_file'; describe('Diffs Module Getters', () => { let localState; @@ -203,4 +204,38 @@ describe('Diffs Module Getters', () => { expect(getters.getDiffFileByHash(localState)('123')).toBeUndefined(); }); }); + + describe('discussionsByLineCode', () => { + let mockState; + + beforeEach(() => { + mockState = { diffFiles: [diffFile] }; + }); + + it('should return a map of diff lines with their line codes', () => { + const mockGetters = { discussions: [discussionMock] }; + + const map = getters.discussionsByLineCode(mockState, {}, {}, mockGetters); + expect(map['1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2']).toBeDefined(); + expect(Object.keys(map).length).toEqual(1); + }); + + it('should have the diff discussion on the map if the original position matches', () => { + discussionMock.position.formatter.base_sha = 'ff9200'; + const mockGetters = { discussions: [discussionMock] }; + + const map = getters.discussionsByLineCode(mockState, {}, {}, mockGetters); + expect(map['1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2']).toBeDefined(); + expect(Object.keys(map).length).toEqual(1); + }); + + it('should not add an outdated diff discussion to the returned map', () => { + discussionMock.position.formatter.base_sha = 'ff9200'; + discussionMock.original_position.formatter.base_sha = 'ff9200'; + const mockGetters = { discussions: [discussionMock] }; + + const map = getters.discussionsByLineCode(mockState, {}, {}, mockGetters); + expect(Object.keys(map).length).toEqual(0); + }); + }); }); diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index 32136d9ebff..8e7bd8afca4 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -207,4 +207,24 @@ describe('DiffsStoreUtils', () => { expect(utils.trimFirstCharOfLineContent()).toEqual({}); }); }); + + describe('getDiffRefsByLineCode', () => { + it('should return diffRefs for all highlightedDiffLines', () => { + const diffFile = getDiffFileMock(); + const map = utils.getDiffRefsByLineCode([diffFile]); + const { highlightedDiffLines } = diffFile; + const lineCodeCount = highlightedDiffLines.reduce( + (acc, line) => (line.lineCode ? acc + 1 : acc), + 0, + ); + + const { baseSha, headSha, startSha } = diffFile.diffRefs; + const targetLine = map[highlightedDiffLines[4].lineCode]; + + expect(Object.keys(map).length).toEqual(lineCodeCount); + expect(targetLine.baseSha).toEqual(baseSha); + expect(targetLine.headSha).toEqual(headSha); + expect(targetLine.startSha).toEqual(startSha); + }); + }); }); diff --git a/spec/javascripts/ide/components/ide_status_bar_spec.js b/spec/javascripts/ide/components/ide_status_bar_spec.js index 9895682f388..0e93c5193a1 100644 --- a/spec/javascripts/ide/components/ide_status_bar_spec.js +++ b/spec/javascripts/ide/components/ide_status_bar_spec.js @@ -70,7 +70,7 @@ describe('ideStatusBar', () => { status: { text: 'success', details_path: 'test', - icon: 'success', + icon: 'status_success', }, }, }); diff --git a/spec/javascripts/ide/components/jobs/detail/description_spec.js b/spec/javascripts/ide/components/jobs/detail/description_spec.js index 9b715a41499..babae00d2f7 100644 --- a/spec/javascripts/ide/components/jobs/detail/description_spec.js +++ b/spec/javascripts/ide/components/jobs/detail/description_spec.js @@ -23,6 +23,6 @@ describe('IDE job description', () => { }); it('renders CI icon', () => { - expect(vm.$el.querySelector('.ci-status-icon .ic-status_passed_borderless')).not.toBe(null); + expect(vm.$el.querySelector('.ci-status-icon .ic-status_success_borderless')).not.toBe(null); }); }); diff --git a/spec/javascripts/ide/components/jobs/item_spec.js b/spec/javascripts/ide/components/jobs/item_spec.js index 79e07f00e7b..2f97d39e98e 100644 --- a/spec/javascripts/ide/components/jobs/item_spec.js +++ b/spec/javascripts/ide/components/jobs/item_spec.js @@ -24,7 +24,7 @@ describe('IDE jobs item', () => { }); it('renders CI icon', () => { - expect(vm.$el.querySelector('.ic-status_passed_borderless')).not.toBe(null); + expect(vm.$el.querySelector('.ic-status_success_borderless')).not.toBe(null); }); it('does not render view logs button if not started', done => { diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index 4bbda4c8e80..7be450a0df7 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -74,7 +74,7 @@ export const jobs = [ name: 'test', path: 'testing', status: { - icon: 'status_passed', + icon: 'status_success', text: 'passed', }, stage: 'test', @@ -86,7 +86,7 @@ export const jobs = [ name: 'test 2', path: 'testing2', status: { - icon: 'status_passed', + icon: 'status_success', text: 'passed', }, stage: 'test', @@ -98,7 +98,7 @@ export const jobs = [ name: 'test 3', path: 'testing3', status: { - icon: 'status_passed', + icon: 'status_success', text: 'passed', }, stage: 'test', @@ -146,7 +146,7 @@ export const fullPipelinesResponse = { }, details: { status: { - icon: 'status_passed', + icon: 'status_success', text: 'passed', }, stages: [...stages], diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js index cef30a007db..e21e2c6d6e3 100644 --- a/spec/javascripts/jobs/header_spec.js +++ b/spec/javascripts/jobs/header_spec.js @@ -20,7 +20,7 @@ describe('Job details header', () => { job: { status: { group: 'failed', - icon: 'ci-status-failed', + icon: 'status_failed', label: 'failed', text: 'failed', details_path: 'path', diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index dd025255bd1..8fdd9b309b7 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -14,7 +14,7 @@ export default { finished_at: threeWeeksAgo.toISOString(), queued: 9.54, status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -72,7 +72,7 @@ export default { }, details: { status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 10808315372..e4c98a3bcb5 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -6561,6 +6561,9 @@ export const environmentData = [ folder_path: '/root/hello-prometheus/environments/folders/production', created_at: '2018-06-29T16:53:38.301Z', updated_at: '2018-06-29T16:57:09.825Z', + last_deployment: { + id: 127, + }, }, }, { @@ -6580,6 +6583,20 @@ export const environmentData = [ folder_path: '/root/hello-prometheus/environments/folders/review', created_at: '2018-07-03T18:39:41.702Z', updated_at: '2018-07-03T18:44:54.010Z', + last_deployment: { + id: 128, + }, + }, + }, + { + name: 'no-deployment', + size: 1, + latest: { + id: 36, + name: 'no-deployment/noop-branch', + state: 'available', + created_at: '2018-07-04T18:39:41.702Z', + updated_at: '2018-07-04T18:44:54.010Z', }, }, ]; diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js index 08d54946787..ccdf4eda563 100644 --- a/spec/javascripts/monitoring/monitoring_store_spec.js +++ b/spec/javascripts/monitoring/monitoring_store_spec.js @@ -1,5 +1,5 @@ import MonitoringStore from '~/monitoring/stores/monitoring_store'; -import MonitoringMock, { deploymentData } from './mock_data'; +import MonitoringMock, { deploymentData, environmentData } from './mock_data'; describe('MonitoringStore', function () { this.store = new MonitoringStore(); @@ -21,4 +21,9 @@ describe('MonitoringStore', function () { expect(this.store.deploymentData.length).toEqual(3); expect(typeof this.store.deploymentData[0]).toEqual('object'); }); + + it('only stores environment data that contains deployments', () => { + this.store.storeEnvironmentsData(environmentData); + expect(this.store.environmentsData.length).toEqual(2); + }); }); diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index 7da931fd9cb..f3f50aed232 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -46,10 +46,15 @@ describe('noteable_discussion component', () => { it('should toggle reply form', done => { vm.$el.querySelector('.js-vue-discussion-reply').click(); + Vue.nextTick(() => { - expect(vm.$refs.noteForm).not.toBeNull(); expect(vm.isReplying).toEqual(true); - done(); + + // There is a watcher for `isReplying` which will init autosave in the next tick + Vue.nextTick(() => { + expect(vm.$refs.noteForm).not.toBeNull(); + done(); + }); }); }); diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js index 9c55a19ebc7..56476253ad0 100644 --- a/spec/javascripts/pipelines/graph/job_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_component_spec.js @@ -10,7 +10,7 @@ describe('pipeline graph job component', () => { id: 4256, name: 'test', status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', tooltip: 'passed', @@ -65,7 +65,7 @@ describe('pipeline graph job component', () => { id: 4257, name: 'test', status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -111,7 +111,7 @@ describe('pipeline graph job component', () => { id: 4258, name: 'test', status: { - icon: 'icon_status_success', + icon: 'status_success', }, }, }); @@ -125,7 +125,7 @@ describe('pipeline graph job component', () => { id: 4259, name: 'test', status: { - icon: 'icon_status_success', + icon: 'status_success', label: 'success', tooltip: 'success', }, diff --git a/spec/javascripts/pipelines/graph/job_name_component_spec.js b/spec/javascripts/pipelines/graph/job_name_component_spec.js index 8e2071ba0b3..c861d452dd0 100644 --- a/spec/javascripts/pipelines/graph/job_name_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_name_component_spec.js @@ -10,7 +10,7 @@ describe('job name component', () => { propsData: { name: 'foo', status: { - icon: 'icon_status_success', + icon: 'status_success', }, }, }).$mount(); diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js index 9e25a4b3fed..b2161d54bce 100644 --- a/spec/javascripts/pipelines/graph/mock_data.js +++ b/spec/javascripts/pipelines/graph/mock_data.js @@ -13,7 +13,7 @@ export default { path: '/root/ci-mock/pipelines/123', details: { status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -33,7 +33,7 @@ export default { name: 'test', size: 1, status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -58,7 +58,7 @@ export default { created_at: '2017-04-13T09:25:18.959Z', updated_at: '2017-04-13T09:25:23.118Z', status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -78,7 +78,7 @@ export default { }, ], status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -98,7 +98,7 @@ export default { name: 'deploy to production', size: 1, status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -123,7 +123,7 @@ export default { created_at: '2017-04-19T14:29:46.463Z', updated_at: '2017-04-19T14:30:27.498Z', status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -145,7 +145,7 @@ export default { name: 'deploy to staging', size: 1, status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -170,7 +170,7 @@ export default { created_at: '2017-04-18T16:32:08.420Z', updated_at: '2017-04-18T16:32:12.631Z', status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', @@ -190,7 +190,7 @@ export default { }, ], status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index f744f1af5e6..9d1e71fd117 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -7,7 +7,7 @@ describe('stage column component', () => { id: 4250, name: 'test', status: { - icon: 'icon_status_success', + icon: 'status_success', text: 'passed', label: 'passed', group: 'success', diff --git a/spec/javascripts/pipelines/header_component_spec.js b/spec/javascripts/pipelines/header_component_spec.js index cecc7ceb53d..034d3b4957d 100644 --- a/spec/javascripts/pipelines/header_component_spec.js +++ b/spec/javascripts/pipelines/header_component_spec.js @@ -18,7 +18,7 @@ describe('Pipeline details header', () => { details: { status: { group: 'failed', - icon: 'ci-status-failed', + icon: 'status_failed', label: 'failed', text: 'failed', details_path: 'path', diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js index 16f6db39d6a..3f6789759ae 100644 --- a/spec/javascripts/pipelines/stage_spec.js +++ b/spec/javascripts/pipelines/stage_spec.js @@ -20,7 +20,7 @@ describe('Pipelines stage component', () => { stage: { status: { group: 'success', - icon: 'icon_status_success', + icon: 'status_success', title: 'success', }, dropdown_path: 'path.json', @@ -84,7 +84,7 @@ describe('Pipelines stage component', () => { component.stage = { status: { group: 'running', - icon: 'running', + icon: 'status_running', title: 'running', }, dropdown_path: 'bar.json', diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index c0b5a7d4455..7fd1a2350f7 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -76,7 +76,7 @@ export default { path: '/root/acets-app/pipelines/172', details: { status: { - icon: 'icon_status_success', + icon: 'status_success', favicon: 'favicon_status_success', text: 'passed', label: 'passed', @@ -91,7 +91,7 @@ export default { name: 'build', title: 'build: failed', status: { - icon: 'icon_status_failed', + icon: 'status_failed', favicon: 'favicon_status_failed', text: 'failed', label: 'failed', @@ -106,7 +106,7 @@ export default { name: 'review', title: 'review: skipped', status: { - icon: 'icon_status_skipped', + icon: 'status_skipped', favicon: 'favicon_status_skipped', text: 'skipped', label: 'skipped', diff --git a/spec/javascripts/vue_shared/components/ci_icon_spec.js b/spec/javascripts/vue_shared/components/ci_icon_spec.js index 423bc746a22..b59a7d7544f 100644 --- a/spec/javascripts/vue_shared/components/ci_icon_spec.js +++ b/spec/javascripts/vue_shared/components/ci_icon_spec.js @@ -13,7 +13,7 @@ describe('CI Icon component', () => { it('should render a span element with an svg', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_success', + icon: 'status_success', }, }); @@ -24,7 +24,7 @@ describe('CI Icon component', () => { it('should render a success status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_success', + icon: 'status_success', group: 'success', }, }); @@ -35,7 +35,7 @@ describe('CI Icon component', () => { it('should render a failed status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_failed', + icon: 'status_failed', group: 'failed', }, }); @@ -46,7 +46,7 @@ describe('CI Icon component', () => { it('should render success with warnings status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_warning', + icon: 'status_warning', group: 'warning', }, }); @@ -57,7 +57,7 @@ describe('CI Icon component', () => { it('should render pending status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_pending', + icon: 'status_pending', group: 'pending', }, }); @@ -68,7 +68,7 @@ describe('CI Icon component', () => { it('should render running status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_running', + icon: 'status_running', group: 'running', }, }); @@ -79,7 +79,7 @@ describe('CI Icon component', () => { it('should render created status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_created', + icon: 'status_created', group: 'created', }, }); @@ -90,7 +90,7 @@ describe('CI Icon component', () => { it('should render skipped status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_skipped', + icon: 'status_skipped', group: 'skipped', }, }); @@ -101,7 +101,7 @@ describe('CI Icon component', () => { it('should render canceled status', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_canceled', + icon: 'status_canceled', group: 'canceled', }, }); @@ -112,7 +112,7 @@ describe('CI Icon component', () => { it('should render status for manual action', () => { vm = mountComponent(Component, { status: { - icon: 'icon_status_manual', + icon: 'status_manual', group: 'manual', }, }); diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index 65499a2d730..f17818c17c7 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -12,7 +12,7 @@ describe('Header CI Component', () => { props = { status: { group: 'failed', - icon: 'ci-status-failed', + icon: 'status_failed', label: 'failed', text: 'failed', details_path: 'path', diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js index cc030e29d61..882420e602e 100644 --- a/spec/javascripts/vue_shared/components/icon_spec.js +++ b/spec/javascripts/vue_shared/components/icon_spec.js @@ -10,7 +10,7 @@ describe('Sprite Icon Component', function () { const IconComponent = Vue.extend(Icon); icon = mountComponent(IconComponent, { - name: 'test', + name: 'commit', size: 32, cssClasses: 'extraclasses', }); @@ -30,7 +30,7 @@ describe('Sprite Icon Component', function () { it('should have <use> as a child element with the correct href', function () { expect(icon.$el.firstChild.tagName).toBe('use'); - expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_icons}#test`); + expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_icons}#commit`); }); it('should properly compute iconSizeClass', function () { @@ -50,5 +50,13 @@ describe('Sprite Icon Component', function () { expect(containsSizeClass).toBe(true); expect(containsCustomClass).toBe(true); }); + + it('`name` validator should return false for non existing icons', () => { + expect(Icon.props.name.validator('non_existing_icon_sprite')).toBe(false); + }); + + it('`name` validator should return false for existing icons', () => { + expect(Icon.props.name.validator('commit')).toBe(true); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js index aa4c9c4c88c..2a6015fe35f 100644 --- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js +++ b/spec/javascripts/vue_shared/components/notes/system_note_spec.js @@ -19,7 +19,7 @@ describe('system note component', () => { path: '/root', }, note_html: '<p dir="auto">closed</p>', - system_note_icon_name: 'icon_status_closed', + system_note_icon_name: 'status_closed', created_at: '2017-08-02T10:51:58.559Z', }, }; diff --git a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js b/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js new file mode 100644 index 00000000000..8635203c413 --- /dev/null +++ b/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js @@ -0,0 +1,45 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import component from '~/vue_shared/components/reports/modal_open_name.vue'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; + +describe('Modal open name', () => { + const Component = Vue.extend(component); + let vm; + + const store = new Vuex.Store({ + actions: { + openModal: () => {}, + }, + state: {}, + mutations: {}, + }); + + beforeEach(() => { + vm = mountComponentWithStore(Component, { + store, + props: { + issue: { + title: 'Issue', + }, + status: 'failed', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders the issue name', () => { + expect(vm.$el.textContent.trim()).toEqual('Issue'); + }); + + it('calls openModal actions when button is clicked', () => { + spyOn(vm, 'openModal'); + + vm.$el.click(); + + expect(vm.openModal).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/reports/report_issues_spec.js b/spec/javascripts/vue_shared/components/reports/report_issues_spec.js new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/spec/javascripts/vue_shared/components/reports/report_issues_spec.js diff --git a/spec/javascripts/vue_shared/components/reports/report_link_spec.js b/spec/javascripts/vue_shared/components/reports/report_link_spec.js new file mode 100644 index 00000000000..a4691f3712f --- /dev/null +++ b/spec/javascripts/vue_shared/components/reports/report_link_spec.js @@ -0,0 +1,71 @@ +import Vue from 'vue'; +import component from '~/vue_shared/components/reports/report_link.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('report link', () => { + let vm; + + const Component = Vue.extend(component); + + afterEach(() => { + vm.$destroy(); + }); + + describe('With url', () => { + it('renders link', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: '/Gemfile.lock', + }, + }); + + expect(vm.$el.textContent.trim()).toContain('in'); + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/Gemfile.lock'); + expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Gemfile.lock'); + }); + }); + + describe('Without url', () => { + it('does not render link', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + }, + }); + + expect(vm.$el.querySelector('a')).toBeNull(); + expect(vm.$el.textContent.trim()).toContain('in'); + expect(vm.$el.textContent.trim()).toContain('Gemfile.lock'); + }); + }); + + describe('with line', () => { + it('renders line number', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: + 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', + line: 22, + }, + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain('Gemfile.lock:22'); + }); + }); + + describe('without line', () => { + it('does not render line number', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: + 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', + }, + }); + + expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(':22'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/reports/report_section_spec.js b/spec/javascripts/vue_shared/components/reports/report_section_spec.js new file mode 100644 index 00000000000..07401181ffd --- /dev/null +++ b/spec/javascripts/vue_shared/components/reports/report_section_spec.js @@ -0,0 +1,174 @@ +import Vue from 'vue'; +import reportSection from '~/vue_shared/components/reports/report_section.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Report section', () => { + let vm; + const ReportSection = Vue.extend(reportSection); + + const resolvedIssues = [ + { + name: 'Insecure Dependency', + fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5', + path: 'Gemfile.lock', + line: 12, + urlPath: 'foo/Gemfile.lock', + }, + ]; + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + beforeEach(() => { + vm = mountComponent(ReportSection, { + type: 'codequality', + status: 'SUCCESS', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + resolvedIssues, + hasIssues: false, + alwaysOpen: false, + }); + }); + + describe('isCollapsible', () => { + const testMatrix = [ + { hasIssues: false, alwaysOpen: false, isCollapsible: false }, + { hasIssues: false, alwaysOpen: true, isCollapsible: false }, + { hasIssues: true, alwaysOpen: false, isCollapsible: true }, + { hasIssues: true, alwaysOpen: true, isCollapsible: false }, + ]; + + testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => { + const issues = hasIssues ? 'has issues' : 'has no issues'; + const open = alwaysOpen ? 'is always open' : 'is not always open'; + + it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => { + vm.hasIssues = hasIssues; + vm.alwaysOpen = alwaysOpen; + + Vue.nextTick() + .then(() => { + expect(vm.isCollapsible).toBe(isCollapsible); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('isExpanded', () => { + const testMatrix = [ + { isCollapsed: false, alwaysOpen: false, isExpanded: true }, + { isCollapsed: false, alwaysOpen: true, isExpanded: true }, + { isCollapsed: true, alwaysOpen: false, isExpanded: false }, + { isCollapsed: true, alwaysOpen: true, isExpanded: true }, + ]; + + testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => { + const issues = isCollapsed ? 'is collapsed' : 'is not collapsed'; + const open = alwaysOpen ? 'is always open' : 'is not always open'; + + it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => { + vm.isCollapsed = isCollapsed; + vm.alwaysOpen = alwaysOpen; + + Vue.nextTick() + .then(() => { + expect(vm.isExpanded).toBe(isExpanded); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + }); + describe('when it is loading', () => { + it('should render loading indicator', () => { + vm = mountComponent(ReportSection, { + type: 'codequality', + status: 'LOADING', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + hasIssues: false, + }); + expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report'); + }); + }); + + describe('with success status', () => { + beforeEach(() => { + vm = mountComponent(ReportSection, { + type: 'codequality', + status: 'SUCCESS', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + resolvedIssues, + hasIssues: true, + }); + }); + + it('should render provided data', () => { + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Code quality improved on 1 point and degraded on 1 point', + ); + + expect(vm.$el.querySelectorAll('.js-mr-code-resolved-issues li').length).toEqual( + resolvedIssues.length, + ); + }); + + describe('toggleCollapsed', () => { + const hiddenCss = { display: 'none' }; + + it('toggles issues', done => { + vm.$el.querySelector('button').click(); + + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); + + vm.$el.querySelector('button').click(); + }) + .then(Vue.nextTick) + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); + }) + .then(done) + .catch(done.fail); + }); + + it('is always expanded, if always-open is set to true', done => { + vm.alwaysOpen = true; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('with failed request', () => { + it('should render error indicator', () => { + vm = mountComponent(ReportSection, { + type: 'codequality', + status: 'ERROR', + loadingText: 'Loading codeclimate report', + errorText: 'Failed to load codeclimate report', + successText: 'Code quality improved on 1 point and degraded on 1 point', + hasIssues: false, + }); + expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js b/spec/javascripts/vue_shared/components/reports/summary_row_spec.js new file mode 100644 index 00000000000..ac076f05bc0 --- /dev/null +++ b/spec/javascripts/vue_shared/components/reports/summary_row_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import component from '~/vue_shared/components/reports/summary_row.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Summary row', () => { + const Component = Vue.extend(component); + let vm; + + const props = { + summary: 'SAST detected 1 new vulnerability and 1 fixed vulnerability', + popoverOptions: { + title: 'Static Application Security Testing (SAST)', + content: '<a>Learn more about SAST</a>', + }, + statusIcon: 'warning', + }; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders provided summary', () => { + expect( + vm.$el.querySelector('.report-block-list-issue-description-text').textContent.trim(), + ).toEqual(props.summary); + }); + + it('renders provided icon', () => { + expect(vm.$el.querySelector('.report-block-list-icon span').classList).toContain( + 'js-ci-status-icon-warning', + ); + }); +}); diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index 211e3aaa94b..0735ebd6dcb 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -9,6 +9,11 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m let(:merge_request) { merge_requests.create!(iid: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master').becomes(MergeRequest) } let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff } let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) } + let(:rugged) do + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.repository.rugged + end + end before do allow_any_instance_of(MergeRequestDiff) @@ -299,11 +304,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) } let(:expected_commits) { commits } - let(:diffs) do - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - first_commit.rugged_diff_from_parent.patches - end - end + let(:diffs) { rugged_diff(first_commit.sha).patches } let(:expected_diffs) { [] } include_examples 'updated MR diff' @@ -313,14 +314,15 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) } let(:expected_commits) { commits } - let(:diffs) do - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - first_commit.rugged_diff_from_parent.deltas - end - end + let(:diffs) { rugged_diff(first_commit.sha).deltas } let(:expected_diffs) { [] } include_examples 'updated MR diff' end + + def rugged_diff(commit_sha) + rugged_commit = rugged.lookup(commit_sha) + rugged_commit.parents[0].diff(rugged_commit) + end end end diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 7c04aa27971..4df426c54ae 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -132,6 +132,16 @@ describe Gitlab::Checks::ChangeAccess do expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') end + context 'when project repository is empty' do + let(:project) { create(:project) } + + it 'raises an error if the user is not allowed to push to protected branches' do + expect(user_access).to receive(:can_push_to_branch?).and_return(false) + + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /Ask a project Owner or Maintainer to create a default branch/) + end + end + context 'branch deletion' do let(:newrev) { '0000000000000000000000000000000000000000' } let(:ref) { 'refs/heads/feature' } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index ee74c2769eb..0adb684765d 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::Git::Commit, seed_helper: true do } @parents = [repo.head.target] - @gitlab_parents = @parents.map { |c| described_class.decorate(repository, c) } + @gitlab_parents = @parents.map { |c| described_class.find(repository, c.oid) } @tree = @parents.first.tree sha = Rugged::Commit.create( @@ -41,7 +41,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ) @raw_commit = repo.lookup(sha) - @commit = described_class.new(repository, @raw_commit) + @commit = described_class.find(repository, sha) end it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) } @@ -488,13 +488,15 @@ describe Gitlab::Git::Commit, seed_helper: true do end end - describe '#init_from_rugged' do - let(:gitlab_commit) { described_class.new(repository, rugged_commit) } - subject { gitlab_commit } + skip 'move this test to gitaly-ruby' do + describe '#init_from_rugged' do + let(:gitlab_commit) { described_class.new(repository, rugged_commit) } + subject { gitlab_commit } - describe '#id' do - subject { super().id } - it { is_expected.to eq(SeedRepo::Commit::ID) } + describe '#id' do + subject { super().id } + it { is_expected.to eq(SeedRepo::Commit::ID) } + end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 6480f6c407d..0365c3b20ef 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -639,21 +639,21 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#log" do shared_examples 'repository log' do let(:commit_with_old_name) do - Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id) + Gitlab::Git::Commit.find(repository, @commit_with_old_name_id) end let(:commit_with_new_name) do - Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id) + Gitlab::Git::Commit.find(repository, @commit_with_new_name_id) end let(:rename_commit) do - Gitlab::Git::Commit.decorate(repository, @rename_commit_id) + Gitlab::Git::Commit.find(repository, @rename_commit_id) end before(:context) do # Add new commits so that there's a renamed file in the commit history repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged - @commit_with_old_name_id = new_commit_edit_old_file(repo) - @rename_commit_id = new_commit_move_file(repo) - @commit_with_new_name_id = new_commit_edit_new_file(repo) + @commit_with_old_name_id = new_commit_edit_old_file(repo).oid + @rename_commit_id = new_commit_move_file(repo).oid + @commit_with_new_name_id = new_commit_edit_new_file(repo).oid end after(:context) do @@ -855,8 +855,8 @@ describe Gitlab::Git::Repository, seed_helper: true do def commit_files(commit) Gitlab::GitalyClient::StorageSettings.allow_disk_access do - commit.rugged_diff_from_parent.deltas.flat_map do |delta| - [delta.old_file[:path], delta.new_file[:path]].uniq.compact + commit.deltas.flat_map do |delta| + [delta.old_path, delta.new_path].uniq.compact end end end @@ -893,10 +893,6 @@ describe Gitlab::Git::Repository, seed_helper: true do context 'when Gitaly find_commits feature is enabled' do it_behaves_like 'repository log' end - - context 'when Gitaly find_commits feature is disabled', :disable_gitaly do - it_behaves_like 'repository log' - end end describe '#count_commits_between' do @@ -1441,31 +1437,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe '#batch_existence' do - let(:refs) { ['deadbeef', SeedRepo::RubyBlob::ID, '909e6157199'] } - - around do |example| - # TODO #batch_existence isn't used anywhere, can we remove it? - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - it 'returns existing refs back' do - result = repository.batch_existence(refs) - - expect(result).to eq([SeedRepo::RubyBlob::ID]) - end - - context 'existing: true' do - it 'inverts meaning and returns non-existing refs' do - result = repository.batch_existence(refs, existing: false) - - expect(result).to eq(%w(deadbeef 909e6157199)) - end - end - end - describe '#local_branches' do before(:all) do @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb deleted file mode 100644 index d9d405e1ccc..00000000000 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::RevList do - let(:repository) { create(:project, :repository).repository.raw } - let(:rev_list) { described_class.new(repository, newrev: 'newrev') } - - def args_for_popen(args_list) - [Gitlab.config.git.bin_path, 'rev-list', *args_list] - end - - def stub_popen_rev_list(*additional_args, with_lazy_block: true, output:) - repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.path } - - params = [ - args_for_popen(additional_args), - repo_path, - {}, - hash_including(lazy_block: with_lazy_block ? anything : nil) - ] - - expect(repository).to receive(:popen).with(*params) do |*_, lazy_block:| - output = lazy_block.call(output.lines.lazy.map(&:chomp)) if with_lazy_block - - [output, 0] - end - end - - context "#new_refs" do - it 'calls out to `popen`' do - stub_popen_rev_list('newrev', '--not', '--all', with_lazy_block: false, output: "sha1\nsha2") - - expect(rev_list.new_refs).to eq(%w[sha1 sha2]) - end - end -end diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index 58b9fb06cc5..265937f899e 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::ImportExport::FileImporter do let(:symlink_file) { "#{shared.export_path}/invalid.json" } let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" } + let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" } before do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) @@ -34,6 +35,10 @@ describe Gitlab::ImportExport::FileImporter do expect(File.exist?(hidden_symlink_file)).to be false end + it 'removes evil symlinks in root folder' do + expect(File.exist?(evil_symlink_file)).to be false + end + it 'removes symlinks in subfolders' do expect(File.exist?(subfolder_symlink_file)).to be false end @@ -75,5 +80,7 @@ describe Gitlab::ImportExport::FileImporter do FileUtils.touch(valid_file) FileUtils.ln_s(valid_file, symlink_file) FileUtils.ln_s(valid_file, subfolder_symlink_file) + FileUtils.ln_s(valid_file, hidden_symlink_file) + FileUtils.ln_s(valid_file, evil_symlink_file) end end diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb index cc99e7e8911..a2f5c2e7121 100644 --- a/spec/lib/uploaded_file_spec.rb +++ b/spec/lib/uploaded_file_spec.rb @@ -1,24 +1,28 @@ require 'spec_helper' describe UploadedFile do - describe ".from_params" do - let(:temp_dir) { Dir.tmpdir } - let(:temp_file) { Tempfile.new("test", temp_dir) } - let(:upload_path) { nil } + let(:temp_dir) { Dir.tmpdir } + let(:temp_file) { Tempfile.new("test", temp_dir) } - subject do - described_class.from_params(params, :file, upload_path) - end + before do + FileUtils.touch(temp_file) + end - before do - FileUtils.touch(temp_file) - end + after do + FileUtils.rm_f(temp_file) + end + + describe ".from_params" do + let(:upload_path) { nil } after do - FileUtils.rm_f(temp_file) FileUtils.rm_r(upload_path) if upload_path end + subject do + described_class.from_params(params, :file, upload_path) + end + context 'when valid file is specified' do context 'only local path is specified' do let(:params) do @@ -37,7 +41,7 @@ describe UploadedFile do context 'all parameters are specified' do let(:params) do { 'file.path' => temp_file.path, - 'file.name' => 'my_file.txt', + 'file.name' => 'dir/my file&.txt', 'file.type' => 'my/type', 'file.sha256' => 'sha256', 'file.remote_id' => 'remote_id' } @@ -48,7 +52,7 @@ describe UploadedFile do end it "generates filename from path" do - expect(subject.original_filename).to eq('my_file.txt') + expect(subject.original_filename).to eq('my_file_.txt') expect(subject.content_type).to eq('my/type') expect(subject.sha256).to eq('sha256') expect(subject.remote_id).to eq('remote_id') @@ -113,4 +117,11 @@ describe UploadedFile do end end end + + describe '#sanitize_filename' do + it { expect(described_class.new(temp_file.path).sanitize_filename('spaced name')).to eq('spaced_name') } + it { expect(described_class.new(temp_file.path).sanitize_filename('#$%^&')).to eq('_____') } + it { expect(described_class.new(temp_file.path).sanitize_filename('..')).to eq('_..') } + it { expect(described_class.new(temp_file.path).sanitize_filename('')).to eq('unnamed') } + end end diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index ac34efa4f9d..a30e6c23ac9 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -6,11 +6,12 @@ require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_ describe MigrateProcessCommitWorkerJobs do let(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:commit) do + let(:rugged) do Gitlab::GitalyClient::StorageSettings.allow_disk_access do - project.commit.raw.rugged_commit + project.repository.rugged end end + let(:commit) { rugged.rev_parse(project.commit.id) } describe 'Project' do describe 'find_including_path' do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index ccc3ff861c5..0aee78ac12d 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -82,6 +82,14 @@ describe MergeRequestDiff do diff.diffs end + + it 'returns persisted diffs if diff refs does not exist' do + expect(diff).to receive(:load_diffs) + + diff.update!(start_commit_sha: nil, base_commit_sha: nil) + + diff.diffs + end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 02d31098cfd..e65214808e1 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -296,24 +296,40 @@ describe Repository do end describe '#new_commits' do - let(:new_refs) do - double(:git_rev_list, new_refs: %w[ - c1acaa58bbcbc3eafe538cb8274ba387047b69f8 - 5937ac0a7beb003549fc5fd26fc247adbce4a52e - ]) - end + shared_examples 'finding unreferenced commits' do + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } - it 'delegates to Gitlab::Git::RevList' do - expect(Gitlab::Git::RevList).to receive(:new).with( - repository.raw, - newrev: 'aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj').and_return(new_refs) + subject { repository.new_commits(rev) } - commits = repository.new_commits('aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj') + context 'when there are no new commits' do + let(:rev) { repository.commit.id } - expect(commits).to eq([ - repository.commit('c1acaa58bbcbc3eafe538cb8274ba387047b69f8'), - repository.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') - ]) + it 'returns an empty array' do + expect(subject).to eq([]) + end + end + + context 'when new commits are found' do + let(:branch) { 'orphaned-branch' } + let!(:rev) { repository.commit(branch).id } + + it 'returns the commits' do + repository.delete_branch(branch) + + expect(subject).not_to be_empty + expect(subject).to all( be_a(::Commit) ) + expect(subject.size).to eq(1) + end + end + end + + context 'when Gitaly handles the request' do + it_behaves_like 'finding unreferenced commits' + end + + context 'when Gitaly is disabled', :disable_gitaly do + it_behaves_like 'finding unreferenced commits' end end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 32d9807f06a..c228bd2393b 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -125,7 +125,8 @@ module CycleAnalyticsHelpers _, opts = args commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - raw_repository.commit(branch_update.newrev).rugged_commit + rugged = raw_repository.rugged + rugged.rev_parse(branch_update.newrev) end branch_update.newrev = commit.amend( diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 3efe512a59c..7e24efda5dd 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -166,4 +166,50 @@ describe FileUploader do uploader.upload = upload end end + + describe '#cache!' do + subject do + uploader.store!(uploaded_file) + end + + context 'when remote file is used' do + let(:temp_file) { Tempfile.new("test") } + + let!(:fog_connection) do + stub_uploads_object_storage(described_class) + end + + let(:uploaded_file) do + UploadedFile.new(temp_file.path, filename: "my file.txt", remote_id: "test/123123") + end + + let!(:fog_file) do + fog_connection.directories.get('uploads').files.create( + key: 'tmp/uploads/test/123123', + body: 'content' + ) + end + + before do + FileUtils.touch(temp_file) + end + + after do + FileUtils.rm_f(temp_file) + end + + it 'file is stored remotely in permament location with sanitized name' do + subject + + expect(uploader).to be_exists + expect(uploader).not_to be_cached + expect(uploader).not_to be_file_storage + expect(uploader.path).not_to be_nil + expect(uploader.path).not_to include('tmp/upload') + expect(uploader.path).not_to include('tmp/cache') + expect(uploader.url).to include('/my_file.txt') + expect(uploader.object_store).to eq(described_class::Store::REMOTE) + end + end + end end diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index 15fce65979b..b56940a9613 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -1,55 +1,48 @@ require 'spec_helper' describe 'projects/_home_panel' do - let(:group) { create(:group) } - let(:project) { create(:project, :public, namespace: group) } + context 'notifications' do + let(:project) { create(:project) } - let(:notification_settings) do - user&.notification_settings_for(project) - end + before do + assign(:project, project) - before do - assign(:project, project) - assign(:notification_setting, notification_settings) + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:can?).with(user, :read_project, project).and_return(false) + end - allow(view).to receive(:current_user).and_return(user) - allow(view).to receive(:can?).and_return(false) - end + context 'when user is signed in' do + let(:user) { create(:user) } - context 'when user is signed in' do - let(:user) { create(:user) } + before do + notification_settings = user.notification_settings_for(project) + assign(:notification_setting, notification_settings) + end - it 'makes it possible to set notification level' do - render + it 'makes it possible to set notification level' do + render - expect(view).to render_template('shared/notifications/_button') - expect(rendered).to have_selector('.notification-dropdown') + expect(view).to render_template('shared/notifications/_button') + expect(rendered).to have_selector('.notification-dropdown') + end end - end - - context 'when user is signed out' do - let(:user) { nil } - it 'is not possible to set notification level' do - render + context 'when user is signed out' do + let(:user) { nil } - expect(rendered).not_to have_selector('.notification_dropdown') - end - end - - context 'when project' do - let!(:user) { create(:user) } - let(:badges) { project.badges } + before do + assign(:notification_setting, nil) + end - context 'has no badges' do - it 'should not render any badge' do + it 'is not possible to set notification level' do render - expect(rendered).to have_selector('.project-badges') - expect(rendered).not_to have_selector('.project-badges > a') + expect(rendered).not_to have_selector('.notification_dropdown') end end + end + context 'badges' do shared_examples 'show badges' do it 'should render the all badges' do render @@ -62,7 +55,31 @@ describe 'projects/_home_panel' do end end + let(:user) { create(:user) } + let(:badges) { project.badges } + + before do + assign(:project, project) + + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:can?).with(user, :read_project, project).and_return(false) + end + + context 'has no badges' do + let(:project) { create(:project) } + + it 'should not render any badge' do + render + + expect(rendered).to have_selector('.project-badges') + expect(rendered).not_to have_selector('.project-badges > a') + end + end + context 'only has group badges' do + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + before do create(:group_badge, group: project.group) end @@ -71,6 +88,8 @@ describe 'projects/_home_panel' do end context 'only has project badges' do + let(:project) { create(:project) } + before do create(:project_badge, project: project) end @@ -79,6 +98,9 @@ describe 'projects/_home_panel' do end context 'has both group and project badges' do + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + before do create(:project_badge, project: project) create(:group_badge, group: project.group) @@ -87,4 +109,35 @@ describe 'projects/_home_panel' do it_behaves_like 'show badges' end end + + context 'project id' do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + assign(:project, project) + + allow(view).to receive(:current_user).and_return(user) + end + + context 'user can read project' do + it 'is shown' do + allow(view).to receive(:can?).with(user, :read_project, project).and_return(true) + + render + + expect(rendered).to have_content("Project ID: #{project.id}") + end + end + + context 'user cannot read project' do + it 'is not shown' do + allow(view).to receive(:can?).with(user, :read_project, project).and_return(false) + + render + + expect(rendered).not_to have_content("Project ID: #{project.id}") + end + end + end end diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index ee0df7711e7..b698248bc38 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -677,6 +677,7 @@ rollout 100%: auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app} auto_chart_name=$(basename $auto_chart) auto_chart_name=${auto_chart_name%.tgz} + auto_chart_name=${auto_chart_name%.tar.gz} else auto_chart="chart" auto_chart_name="chart" diff --git a/yarn.lock b/yarn.lock index 9d0eae5f509..67f9aa98c74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,9 +78,9 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.25.0": - version "1.25.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.25.0.tgz#1a82b1be43e1a46e6b0767ef46f26f5fd6bbd101" +"@gitlab-org/gitlab-svgs@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.26.0.tgz#d89c633e866d031a9e4787b05eacc7144c3a7791" "@sindresorhus/is@^0.7.0": version "0.7.0" |