Commit fb511b66 authored by Luke Bennett's avatar Luke Bennett

Merge remote-tracking branch 'origin/master' into...

Merge remote-tracking branch 'origin/master' into 45443-unable-to-save-user-profile-update-with-safari
parents 820c3771 98eccfc4
......@@ -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)
......
......@@ -53,4 +53,8 @@ export default class Autosave {
return window.localStorage.removeItem(this.key);
}
dispose() {
this.field.off('input');
}
}
......@@ -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');
}
}
......
......@@ -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"
>
......
<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);
......
......@@ -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"
......
......@@ -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"
......
......@@ -20,8 +20,7 @@ export default {
},
},
computed: {
...mapGetters('diffs', ['commitId']),
...mapGetters(['discussionsByLineCode']),
...mapGetters('diffs', ['commitId', 'discussionsByLineCode']),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
......
......@@ -26,7 +26,7 @@ export default {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
...mapGetters(['discussionsByLineCode']),
...mapGetters('diffs', ['discussionsByLineCode']),
leftLineCode() {
return this.line.left.lineCode;
},
......
......@@ -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"
......
......@@ -21,8 +21,7 @@ export default {
},
},
computed: {
...mapGetters('diffs', ['commitId']),
...mapGetters(['discussionsByLineCode']),
...mapGetters('diffs', ['commitId', 'discussionsByLineCode']),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
......
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);
......
......@@ -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;
}, {});
}
......@@ -38,6 +38,7 @@ export default {
<button
:aria-label="label"
type="button"
class="btn-blank"
@click.stop.prevent="clicked"
>
<icon
......
......@@ -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">
......
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() {
......
......@@ -7,7 +7,7 @@ import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
export default {
name: 'IssueNoteForm',
name: 'NoteForm',
components: {
issueWarning,
markdownField,
......
......@@ -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,20 +145,18 @@ export default {
return this.isDiffDiscussion ? '' : 'card discussion-wrapper';
},
},
mounted() {
watch: {
isReplying() {
if (this.isReplying) {
this.initAutoSave(this.transformedDiscussion);
}
},
updated() {
if (this.isReplying) {
if (!this.autosave) {
this.initAutoSave(this.transformedDiscussion);
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;
this.nextDiscussionsSvg = nextDiscussionsSvg;
......@@ -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>
......
......@@ -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();
},
},
};
......@@ -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;
......
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
......@@ -47,7 +47,6 @@
.card-body {
padding: $gl-padding;
background-color: $white-light;
.form-actions {
margin: -$gl-padding;
......
......@@ -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
......
......@@ -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;
svg {
fill: $blue-500;
vertical-align: middle;
}
img {
max-width: $gcp-signup-offer-icon-max-width;
}
a:not(.btn) {
color: $gl-link-color;
font-weight: normal;
text-decoration: none;
}
@include media-breakpoint-up(sm) {
> div {
.gcp-signup-offer--content {
display: flex;
align-items: center;
h4 {
font-size: 16px;
line-height: 24px;
}
.gcp-logo {
margin: 0;
.gcp-signup-offer--icon {
align-self: flex-start;
}
}
}
......@@ -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;
}
}
......
......@@ -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?
......@@ -85,6 +86,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)
......
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
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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? }
......
# frozen_string_literal: true
module ApplicationSettings
class BaseService < ::BaseService
def initialize(application_setting, user, params = {})
......
# frozen_string_literal: true
module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService
attr_reader :params, :application_setting
......
# frozen_string_literal: true
module Applications
class CreateService
def initialize(current_user, params)
......
# frozen_string_literal: true
module Auth
class ContainerRegistryAuthenticationService < BaseService
AUDIENCE = 'container_registry'.freeze
......
# frozen_string_literal: true
module Badges
class BaseService
protected
......
# frozen_string_literal: true
module Badges
class BuildService < Badges::BaseService
# returns the created badge
......
# frozen_string_literal: true
module Badges
class CreateService < Badges::BaseService
# returns the created badge
......
# frozen_string_literal: true
module Badges
class UpdateService < Badges::BaseService
# returns the updated badge
......
# frozen_string_literal: true
module Boards
class BaseService < ::BaseService
# Parent can either a group or a project
......
# frozen_string_literal: true
module Boards
class CreateService < Boards::BaseService
def execute
......
# frozen_string_literal: true
module Boards
module Issues
class CreateService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
module Issues
class ListService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
module Issues
class MoveService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
class ListService < Boards::BaseService
def execute
......
# frozen_string_literal: true
module Boards
module Lists
class CreateService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
module Lists
class DestroyService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
module Lists
class GenerateService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
module Lists
class ListService < Boards::BaseService
......
# frozen_string_literal: true
module Boards
module Lists
class MoveService < Boards::BaseService
......
# frozen_string_literal: true
module ChatNames
class AuthorizeUserService
include Gitlab::Routing
......
# frozen_string_literal: true
module ChatNames
class FindUserService
def initialize(service, params)
......
# frozen_string_literal: true
module Ci
class CreatePipelineScheduleService < BaseService
def execute
......
# frozen_string_literal: true
module Ci
class CreatePipelineService < BaseService
attr_reader :pipeline
......
# frozen_string_literal: true
module Ci
##
# We call this service everytime we persist a CI/CD job.
......
# frozen_string_literal: true
module Ci
class ExtractSectionsFromBuildTraceService < BaseService
def execute(build)
......
# frozen_string_literal: true
##
# TODO:
# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb
......
# frozen_string_literal: true
module Ci
class PipelineTriggerService < BaseService
include Gitlab::Utils::StrongMemoize
......
# frozen_string_literal: true
module Ci
class PlayBuildService < ::BaseService
def execute(build)
......
# frozen_string_literal: true
module Ci
class ProcessPipelineService < BaseService
attr_reader :pipeline
......
# frozen_string_literal: true
module Ci
# This class responsible for assigning
# proper pending build to runner on runner API request
......
# frozen_string_literal: true
module Ci
class RetryBuildService < ::BaseService
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
......
# frozen_string_literal: true
module Ci
class RetryPipelineService < ::BaseService
include Gitlab::OptimisticLocking
......
# frozen_string_literal: true
module Ci
class StopEnvironmentsService < BaseService
attr_reader :ref
......
# frozen_string_literal: true
module Ci
class UpdateBuildQueueService
def execute(build)
......
# frozen_string_literal: true
module Ci
class UpdateRunnerService
attr_reader :runner
......
# frozen_string_literal: true
module Clusters
module Applications
class BaseHelmService
......
# frozen_string_literal: true
module Clusters
module Applications
class CheckIngressIpAddressService < BaseHelmService
......
# frozen_string_literal: true
module Clusters
module Applications
class CheckInstallationProgressService < BaseHelmService
......
# frozen_string_literal: true
module Clusters
module Applications
class InstallService < BaseHelmService
......
# frozen_string_literal: true
module Clusters
module Applications
class ScheduleInstallationService < ::BaseService
......
# frozen_string_literal: true
module Clusters
class CreateService < BaseService
attr_reader :access_token
......
# frozen_string_literal: true
module Clusters
module Gcp
class FetchOperationService
......
# frozen_string_literal: true
module Clusters
module Gcp
class FinalizeCreationService
......
# frozen_string_literal: true
module Clusters
module Gcp
class ProvisionService
......
# frozen_string_literal: true
module Clusters
module Gcp
class VerifyProvisionStatusService
......
# frozen_string_literal: true
module Clusters
class UpdateService < BaseService
def execute(cluster)
......
# frozen_string_literal: true
module Commits
class ChangeService < Commits::CreateService
def initialize(*args)
......
# frozen_string_literal: true
module Commits
class CherryPickService < ChangeService
def create_commit!
......
# frozen_string_literal: true
module Commits
class CreateService < ::BaseService
ValidationError = Class.new(StandardError)
......
# frozen_string_literal: true
module Commits
class RevertService < ChangeService
def create_commit!
......
# frozen_string_literal: true
#
# Concern that helps with getting an exclusive lease for running a block
# of code.
......
# frozen_string_literal: true
module Issues
module ResolveDiscussions
include Gitlab::Utils::StrongMemoize
......
# frozen_string_literal: true
module UpdateVisibilityLevel
def valid_visibility_level_change?(target, new_visibility)
# check that user is allowed to set specified visibility_level
......
# frozen_string_literal: true
module Users
module NewUserNotifier
def notify_new_user(user, reset_token)
......
# frozen_string_literal: true
module Users
module ParticipableService
extend ActiveSupport::Concern
......
# frozen_string_literal: true
module DeployKeys
class CreateService < Keys::BaseService
def execute
......
# frozen_string_literal: true
module DeployTokens
class CreateService < BaseService
def execute
......
# frozen_string_literal: true
module Discussions
class BaseService < ::BaseService
end
......
# frozen_string_literal: true
module Discussions
class ResolveService < Discussions::BaseService
def execute(one_or_more_discussions)
......
# frozen_string_literal: true
module Discussions
class UpdateDiffPositionService < BaseService
def execute(discussion)
......
# frozen_string_literal: true
module Emails
class BaseService
def initialize(current_user, params = {})
......
# frozen_string_literal: true
module Emails
class ConfirmService < ::Emails::BaseService
def execute(email)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment