Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8e22ef10e4
commit
fc53ce8e6c
|
@ -35,10 +35,10 @@ export default {
|
||||||
title() {
|
title() {
|
||||||
const timeago = getTimeago();
|
const timeago = getTimeago();
|
||||||
const { timeDifference, standardDateFormat } = this;
|
const { timeDifference, standardDateFormat } = this;
|
||||||
const formatedDate = standardDateFormat;
|
const formattedDate = standardDateFormat;
|
||||||
|
|
||||||
if (timeDifference >= -1 && timeDifference < 7) {
|
if (timeDifference >= -1 && timeDifference < 7) {
|
||||||
return `${timeago.format(this.issueDueDate)} (${formatedDate})`;
|
return `${timeago.format(this.issueDueDate)} (${formattedDate})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return timeago.format(this.issueDueDate);
|
return timeago.format(this.issueDueDate);
|
||||||
|
|
|
@ -159,7 +159,7 @@ export default {
|
||||||
<div role="rowheader" class="table-mobile-header">{{ __('Created') }}</div>
|
<div role="rowheader" class="table-mobile-header">{{ __('Created') }}</div>
|
||||||
<div class="table-mobile-content text-secondary key-created-at">
|
<div class="table-mobile-content text-secondary key-created-at">
|
||||||
<span v-tooltip :title="tooltipTitle(deployKey.created_at)">
|
<span v-tooltip :title="tooltipTitle(deployKey.created_at)">
|
||||||
<icon name="calendar" /> <span>{{ timeFormated(deployKey.created_at) }}</span>
|
<icon name="calendar" /> <span>{{ timeFormatted(deployKey.created_at) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -177,6 +177,7 @@ export default {
|
||||||
projectPath: this.projectPath,
|
projectPath: this.projectPath,
|
||||||
dismissEndpoint: this.dismissEndpoint,
|
dismissEndpoint: this.dismissEndpoint,
|
||||||
showSuggestPopover: this.showSuggestPopover,
|
showSuggestPopover: this.showSuggestPopover,
|
||||||
|
useSingleDiffStyle: this.glFeatures.singleMrDiffView,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.shouldShow) {
|
if (this.shouldShow) {
|
||||||
|
|
|
@ -46,6 +46,7 @@ export const setBaseConfig = ({ commit }, options) => {
|
||||||
projectPath,
|
projectPath,
|
||||||
dismissEndpoint,
|
dismissEndpoint,
|
||||||
showSuggestPopover,
|
showSuggestPopover,
|
||||||
|
useSingleDiffStyle,
|
||||||
} = options;
|
} = options;
|
||||||
commit(types.SET_BASE_CONFIG, {
|
commit(types.SET_BASE_CONFIG, {
|
||||||
endpoint,
|
endpoint,
|
||||||
|
@ -54,11 +55,15 @@ export const setBaseConfig = ({ commit }, options) => {
|
||||||
projectPath,
|
projectPath,
|
||||||
dismissEndpoint,
|
dismissEndpoint,
|
||||||
showSuggestPopover,
|
showSuggestPopover,
|
||||||
|
useSingleDiffStyle,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchDiffFiles = ({ state, commit }) => {
|
export const fetchDiffFiles = ({ state, commit }) => {
|
||||||
const worker = new TreeWorker();
|
const worker = new TreeWorker();
|
||||||
|
const urlParams = {
|
||||||
|
w: state.showWhitespace ? '0' : '1',
|
||||||
|
};
|
||||||
|
|
||||||
commit(types.SET_LOADING, true);
|
commit(types.SET_LOADING, true);
|
||||||
|
|
||||||
|
@ -69,9 +74,10 @@ export const fetchDiffFiles = ({ state, commit }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.get(mergeUrlParams({ w: state.showWhitespace ? '0' : '1' }, state.endpoint))
|
.get(mergeUrlParams(urlParams, state.endpoint))
|
||||||
.then(res => {
|
.then(res => {
|
||||||
commit(types.SET_LOADING, false);
|
commit(types.SET_LOADING, false);
|
||||||
|
|
||||||
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
|
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
|
||||||
commit(types.SET_DIFF_DATA, res.data);
|
commit(types.SET_DIFF_DATA, res.data);
|
||||||
|
|
||||||
|
|
|
@ -31,4 +31,5 @@ export default () => ({
|
||||||
fileFinderVisible: false,
|
fileFinderVisible: false,
|
||||||
dismissEndpoint: '',
|
dismissEndpoint: '',
|
||||||
showSuggestPopover: true,
|
showSuggestPopover: true,
|
||||||
|
useSingleDiffStyle: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default {
|
||||||
projectPath,
|
projectPath,
|
||||||
dismissEndpoint,
|
dismissEndpoint,
|
||||||
showSuggestPopover,
|
showSuggestPopover,
|
||||||
|
useSingleDiffStyle,
|
||||||
} = options;
|
} = options;
|
||||||
Object.assign(state, {
|
Object.assign(state, {
|
||||||
endpoint,
|
endpoint,
|
||||||
|
@ -27,6 +28,7 @@ export default {
|
||||||
projectPath,
|
projectPath,
|
||||||
dismissEndpoint,
|
dismissEndpoint,
|
||||||
showSuggestPopover,
|
showSuggestPopover,
|
||||||
|
useSingleDiffStyle,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default {
|
||||||
__('Reported %{timeAgo} by %{reportedBy}'),
|
__('Reported %{timeAgo} by %{reportedBy}'),
|
||||||
{
|
{
|
||||||
reportedBy: `<strong>${this.error.culprit}</strong>`,
|
reportedBy: `<strong>${this.error.culprit}</strong>`,
|
||||||
timeAgo: this.timeFormated(this.stacktraceData.date_received),
|
timeAgo: this.timeFormatted(this.stacktraceData.date_received),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
@ -107,7 +107,7 @@ export default {
|
||||||
this.$refs.sentryIssueForm.submit();
|
this.$refs.sentryIssueForm.submit();
|
||||||
},
|
},
|
||||||
formatDate(date) {
|
formatDate(date) {
|
||||||
return `${this.timeFormated(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
|
return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
mixins: [timeAgoMixin],
|
mixins: [timeAgoMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
lastCommitFormatedAge: null,
|
lastCommitFormattedAge: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,7 +62,7 @@ export default {
|
||||||
},
|
},
|
||||||
commitAgeUpdate() {
|
commitAgeUpdate() {
|
||||||
if (this.lastCommit) {
|
if (this.lastCommit) {
|
||||||
this.lastCommitFormatedAge = this.timeFormated(this.lastCommit.committed_date);
|
this.lastCommitFormattedAge = this.timeFormatted(this.lastCommit.committed_date);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getCommitPath(shortSha) {
|
getCommitPath(shortSha) {
|
||||||
|
@ -118,7 +118,7 @@ export default {
|
||||||
:title="tooltipTitle(lastCommit.committed_date)"
|
:title="tooltipTitle(lastCommit.committed_date)"
|
||||||
data-placement="top"
|
data-placement="top"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
>{{ lastCommitFormatedAge }}</time
|
>{{ lastCommitFormattedAge }}</time
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<ide-status-list class="ml-auto" />
|
<ide-status-list class="ml-auto" />
|
||||||
|
|
|
@ -91,7 +91,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
<gl-tooltip :target="() => $refs.state" placement="bottom">
|
<gl-tooltip :target="() => $refs.state" placement="bottom">
|
||||||
<span class="d-block">
|
<span class="d-block">
|
||||||
<span class="bold"> {{ stateTitle }} </span> {{ timeFormated(closedOrCreatedDate) }}
|
<span class="bold"> {{ stateTitle }} </span> {{ timeFormatted(closedOrCreatedDate) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-tertiary">{{ tooltipTitle(closedOrCreatedDate) }}</span>
|
<span class="text-tertiary">{{ tooltipTitle(closedOrCreatedDate) }}</span>
|
||||||
</gl-tooltip>
|
</gl-tooltip>
|
||||||
|
|
|
@ -168,13 +168,13 @@ export default {
|
||||||
/>
|
/>
|
||||||
<detail-row
|
<detail-row
|
||||||
v-if="job.finished_at"
|
v-if="job.finished_at"
|
||||||
:value="timeFormated(job.finished_at)"
|
:value="timeFormatted(job.finished_at)"
|
||||||
class="js-job-finished"
|
class="js-job-finished"
|
||||||
title="Finished"
|
title="Finished"
|
||||||
/>
|
/>
|
||||||
<detail-row
|
<detail-row
|
||||||
v-if="job.erased_at"
|
v-if="job.erased_at"
|
||||||
:value="timeFormated(job.erased_at)"
|
:value="timeFormatted(job.erased_at)"
|
||||||
class="js-job-erased"
|
class="js-job-erased"
|
||||||
title="Erased"
|
title="Erased"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -114,7 +114,7 @@ export const logLinesParser = (lines = [], accumulator = []) =>
|
||||||
acc.push(parseHeaderLine(line, lineNumber));
|
acc.push(parseHeaderLine(line, lineNumber));
|
||||||
} else if (isCollapsibleSection(acc, last, line)) {
|
} else if (isCollapsibleSection(acc, last, line)) {
|
||||||
// if the object belongs to a nested section, we append it to the new `lines` array of the
|
// if the object belongs to a nested section, we append it to the new `lines` array of the
|
||||||
// previously formated header
|
// previously formatted header
|
||||||
last.lines.push(parseLine(line, lineNumber));
|
last.lines.push(parseLine(line, lineNumber));
|
||||||
} else if (line.section_duration) {
|
} else if (line.section_duration) {
|
||||||
// if the line has section_duration, we look for the correct header to add it
|
// if the line has section_duration, we look for the correct header to add it
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import axios from './axios_utils';
|
import axios from './axios_utils';
|
||||||
import { getLocationHash } from './url_utility';
|
import { getLocationHash } from './url_utility';
|
||||||
import { convertToCamelCase } from './text_utility';
|
import { convertToCamelCase, convertToSnakeCase } from './text_utility';
|
||||||
import { isObject } from './type_utility';
|
import { isObject } from './type_utility';
|
||||||
import breakpointInstance from '../../breakpoints';
|
import breakpointInstance from '../../breakpoints';
|
||||||
|
|
||||||
|
@ -697,6 +697,22 @@ export const convertObjectPropsToCamelCase = (obj = {}, options = {}) => {
|
||||||
}, initial);
|
}, initial);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts all the object keys to snake case
|
||||||
|
*
|
||||||
|
* @param {Object} obj Object to transform
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
// Follow up to add additional options param:
|
||||||
|
// https://gitlab.com/gitlab-org/gitlab/issues/39173
|
||||||
|
export const convertObjectPropsToSnakeCase = (obj = {}) =>
|
||||||
|
obj
|
||||||
|
? Object.entries(obj).reduce(
|
||||||
|
(acc, [key, value]) => ({ ...acc, [convertToSnakeCase(key)]: value }),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
: {};
|
||||||
|
|
||||||
export const imagePath = imgUrl =>
|
export const imagePath = imgUrl =>
|
||||||
`${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
|
`${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
|
||||||
|
|
||||||
|
|
|
@ -447,7 +447,7 @@ export const parsePikadayDate = dateString => {
|
||||||
/**
|
/**
|
||||||
* Used `onSelect` method in pickaday
|
* Used `onSelect` method in pickaday
|
||||||
* @param {Date} date UTC format
|
* @param {Date} date UTC format
|
||||||
* @return {String} Date formated in yyyy-mm-dd
|
* @return {String} Date formatted in yyyy-mm-dd
|
||||||
*/
|
*/
|
||||||
export const pikadayToString = date => {
|
export const pikadayToString = date => {
|
||||||
const day = pad(date.getDate());
|
const day = pad(date.getDate());
|
||||||
|
@ -513,8 +513,8 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => {
|
||||||
|
|
||||||
if (fullNameFormat && isNonZero) {
|
if (fullNameFormat && isNonZero) {
|
||||||
// Remove traling 's' if unit value is singular
|
// Remove traling 's' if unit value is singular
|
||||||
const formatedUnitName = unitValue > 1 ? unitName : unitName.replace(/s$/, '');
|
const formattedUnitName = unitValue > 1 ? unitName : unitName.replace(/s$/, '');
|
||||||
return `${memo} ${unitValue} ${formatedUnitName}`;
|
return `${memo} ${unitValue} ${formattedUnitName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
|
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default {
|
||||||
return this.mergeRequest.headPipeline && this.mergeRequest.headPipeline.detailedStatus;
|
return this.mergeRequest.headPipeline && this.mergeRequest.headPipeline.detailedStatus;
|
||||||
},
|
},
|
||||||
formattedTime() {
|
formattedTime() {
|
||||||
return this.timeFormated(this.mergeRequest.createdAt);
|
return this.timeFormatted(this.mergeRequest.createdAt);
|
||||||
},
|
},
|
||||||
statusBoxClass() {
|
statusBoxClass() {
|
||||||
switch (this.mergeRequest.state) {
|
switch (this.mergeRequest.state) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default {
|
||||||
hasFinishedTime() {
|
hasFinishedTime() {
|
||||||
return this.finishedTime !== '';
|
return this.finishedTime !== '';
|
||||||
},
|
},
|
||||||
durationFormated() {
|
durationFormatted() {
|
||||||
const date = new Date(this.duration * 1000);
|
const date = new Date(this.duration * 1000);
|
||||||
|
|
||||||
let hh = date.getUTCHours();
|
let hh = date.getUTCHours();
|
||||||
|
@ -59,7 +59,7 @@ export default {
|
||||||
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
|
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
|
||||||
<div class="table-mobile-content">
|
<div class="table-mobile-content">
|
||||||
<p v-if="hasDuration" class="duration">
|
<p v-if="hasDuration" class="duration">
|
||||||
<span v-html="iconTimerSvg"> </span> {{ durationFormated }}
|
<span v-html="iconTimerSvg"> </span> {{ durationFormatted }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="hasFinishedTime" class="finished-at d-none d-sm-none d-md-block">
|
<p v-if="hasFinishedTime" class="finished-at d-none d-sm-none d-md-block">
|
||||||
|
@ -71,7 +71,7 @@ export default {
|
||||||
data-placement="top"
|
data-placement="top"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
>
|
>
|
||||||
{{ timeFormated(finishedTime) }}
|
{{ timeFormatted(finishedTime) }}
|
||||||
</time>
|
</time>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -247,7 +247,7 @@ export default {
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span v-gl-tooltip.bottom :title="tooltipTitle(item.createdAt)">{{
|
<span v-gl-tooltip.bottom :title="tooltipTitle(item.createdAt)">{{
|
||||||
timeFormated(item.createdAt)
|
timeFormatted(item.createdAt)
|
||||||
}}</span>
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default {
|
||||||
},
|
},
|
||||||
releasedTimeAgo() {
|
releasedTimeAgo() {
|
||||||
return sprintf(__('released %{time}'), {
|
return sprintf(__('released %{time}'), {
|
||||||
time: this.timeFormated(this.release.released_at),
|
time: this.timeFormatted(this.release.released_at),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
userImageAltDescription() {
|
userImageAltDescription() {
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
releasedAtTimeAgo() {
|
releasedAtTimeAgo() {
|
||||||
return this.timeFormated(this.releasedAt);
|
return this.timeFormatted(this.releasedAt);
|
||||||
},
|
},
|
||||||
userImageAltDescription() {
|
userImageAltDescription() {
|
||||||
return this.author && this.author.username
|
return this.author && this.author.username
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
deployTimeago() {
|
deployTimeago() {
|
||||||
return this.timeFormated(this.deployment.deployed_at);
|
return this.timeFormatted(this.deployment.deployed_at);
|
||||||
},
|
},
|
||||||
deployedText() {
|
deployedText() {
|
||||||
return this.$options.deployedTextMap[this.computedDeploymentStatus];
|
return this.$options.deployedTextMap[this.computedDeploymentStatus];
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
||||||
return timeFor(
|
return timeFor(
|
||||||
this.milestoneDue,
|
this.milestoneDue,
|
||||||
sprintf(__('Expired %{expiredOn}'), {
|
sprintf(__('Expired %{expiredOn}'), {
|
||||||
expiredOn: this.timeFormated(this.milestoneDue),
|
expiredOn: this.timeFormatted(this.milestoneDue),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ export default {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
this.isMilestoneStarted ? __('Started %{startsIn}') : __('Starts %{startsIn}'),
|
this.isMilestoneStarted ? __('Started %{startsIn}') : __('Starts %{startsIn}'),
|
||||||
{
|
{
|
||||||
startsIn: this.timeFormated(this.milestoneStart),
|
startsIn: this.timeFormatted(this.milestoneStart),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default {
|
||||||
tooltipText(dateType = 'min') {
|
tooltipText(dateType = 'min') {
|
||||||
const defaultText = dateType === 'min' ? __('Start date') : __('Due date');
|
const defaultText = dateType === 'min' ? __('Start date') : __('Due date');
|
||||||
const date = this[`${dateType}Date`];
|
const date = this[`${dateType}Date`];
|
||||||
const timeAgo = dateType === 'min' ? this.timeFormated(date) : timeFor(date);
|
const timeAgo = dateType === 'min' ? this.timeFormatted(date) : timeFor(date);
|
||||||
const dateText = date ? [this.dateText(dateType), `(${timeAgo})`].join(' ') : '';
|
const dateText = date ? [this.dateText(dateType), `(${timeAgo})`].join(' ') : '';
|
||||||
|
|
||||||
if (date) {
|
if (date) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default {
|
||||||
v-gl-tooltip.viewport="{ placement: tooltipPlacement }"
|
v-gl-tooltip.viewport="{ placement: tooltipPlacement }"
|
||||||
:class="cssClass"
|
:class="cssClass"
|
||||||
:title="tooltipTitle(time)"
|
:title="tooltipTitle(time)"
|
||||||
v-text="timeFormated(time)"
|
v-text="timeFormatted(time)"
|
||||||
>
|
>
|
||||||
</time>
|
</time>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -159,7 +159,7 @@ const mixins = {
|
||||||
return this.displayReference.split(this.pathIdSeparator).pop();
|
return this.displayReference.split(this.pathIdSeparator).pop();
|
||||||
},
|
},
|
||||||
createdAtInWords() {
|
createdAtInWords() {
|
||||||
return this.createdAt ? this.timeFormated(this.createdAt) : '';
|
return this.createdAt ? this.timeFormatted(this.createdAt) : '';
|
||||||
},
|
},
|
||||||
createdAtTimestamp() {
|
createdAtTimestamp() {
|
||||||
return this.createdAt ? formatDate(new Date(this.createdAt)) : '';
|
return this.createdAt ? formatDate(new Date(this.createdAt)) : '';
|
||||||
|
@ -168,10 +168,10 @@ const mixins = {
|
||||||
return this.mergedAt ? formatDate(new Date(this.mergedAt)) : '';
|
return this.mergedAt ? formatDate(new Date(this.mergedAt)) : '';
|
||||||
},
|
},
|
||||||
mergedAtInWords() {
|
mergedAtInWords() {
|
||||||
return this.mergedAt ? this.timeFormated(this.mergedAt) : '';
|
return this.mergedAt ? this.timeFormatted(this.mergedAt) : '';
|
||||||
},
|
},
|
||||||
closedAtInWords() {
|
closedAtInWords() {
|
||||||
return this.closedAt ? this.timeFormated(this.closedAt) : '';
|
return this.closedAt ? this.timeFormatted(this.closedAt) : '';
|
||||||
},
|
},
|
||||||
closedAtTimestamp() {
|
closedAtTimestamp() {
|
||||||
return this.closedAt ? formatDate(new Date(this.closedAt)) : '';
|
return this.closedAt ? formatDate(new Date(this.closedAt)) : '';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { formatDate, getTimeago } from '../../lib/utils/datetime_utility';
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
timeFormated(time) {
|
timeFormatted(time) {
|
||||||
const timeago = getTimeago();
|
const timeago = getTimeago();
|
||||||
|
|
||||||
return timeago.format(time);
|
return timeago.format(time);
|
||||||
|
|
|
@ -20,6 +20,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
|
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
|
||||||
before_action only: [:show] do
|
before_action only: [:show] do
|
||||||
push_frontend_feature_flag(:diffs_batch_load, @project)
|
push_frontend_feature_flag(:diffs_batch_load, @project)
|
||||||
|
push_frontend_feature_flag(:single_mr_diff_view, @project)
|
||||||
end
|
end
|
||||||
|
|
||||||
before_action do
|
before_action do
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Show merge immediately dialog even if the MR's pipeline hasn't finished
|
||||||
|
merge_request: 21556
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -219,6 +219,94 @@ build_latest_vulnerabilities:
|
||||||
|
|
||||||
The above template will work for a GitLab Docker registry running on a local installation, however, if you're using a non-GitLab Docker registry, you'll need to change the `$CI_REGISTRY` value and the `docker login` credentials to match the details of your local registry.
|
The above template will work for a GitLab Docker registry running on a local installation, however, if you're using a non-GitLab Docker registry, you'll need to change the `$CI_REGISTRY` value and the `docker login` credentials to match the details of your local registry.
|
||||||
|
|
||||||
|
## Reports JSON format
|
||||||
|
|
||||||
|
CAUTION: **Caution:**
|
||||||
|
The JSON report artifacts are not a public API of Container Scanning and their format may change in the future.
|
||||||
|
|
||||||
|
The Container Scanning tool emits a JSON report file. Here is an example of the report structure with all important parts of
|
||||||
|
it highlighted:
|
||||||
|
|
||||||
|
```json-doc
|
||||||
|
{
|
||||||
|
"version": "2.3",
|
||||||
|
"vulnerabilities": [
|
||||||
|
{
|
||||||
|
"category": "container_scanning",
|
||||||
|
"message": "CVE-2019-3462 in apt",
|
||||||
|
"description": "Incorrect sanitation of the 302 redirect field in HTTP transport method of apt versions 1.4.8 and earlier can lead to content injection by a MITM attacker, potentially leading to remote code execution on the target machine.",
|
||||||
|
"cve": "debian:9:apt:CVE-2019-3462",
|
||||||
|
"severity": "High",
|
||||||
|
"confidence": "Unknown",
|
||||||
|
"solution": "Upgrade apt from 1.4.8 to 1.4.9",
|
||||||
|
"scanner": {
|
||||||
|
"id": "klar",
|
||||||
|
"name": "klar"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"dependency": {
|
||||||
|
"package": {
|
||||||
|
"name": "apt"
|
||||||
|
},
|
||||||
|
"version": "1.4.8"
|
||||||
|
},
|
||||||
|
"operating_system": "debian:9",
|
||||||
|
"image": "registry.gitlab.com/gitlab-org/security-products/dast/webgoat-8.0@sha256:bc09fe2e0721dfaeee79364115aeedf2174cce0947b9ae5fe7c33312ee019a4e"
|
||||||
|
},
|
||||||
|
"identifiers": [
|
||||||
|
{
|
||||||
|
"type": "cve",
|
||||||
|
"name": "CVE-2019-3462",
|
||||||
|
"value": "CVE-2019-3462",
|
||||||
|
"url": "https://security-tracker.debian.org/tracker/CVE-2019-3462"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"url": "https://security-tracker.debian.org/tracker/CVE-2019-3462"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remediations": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is the description of the report file structure nodes and their meaning. All fields are mandatory to be present in
|
||||||
|
the report JSON unless stated otherwise. Presence of optional fields depends on the underlying analyzers being used.
|
||||||
|
|
||||||
|
| Report JSON node | Description |
|
||||||
|
|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `version` | Report syntax version used to generate this JSON. |
|
||||||
|
| `vulnerabilities` | Array of vulnerability objects. |
|
||||||
|
| `vulnerabilities[].category` | Where this vulnerability belongs (SAST, Container Scanning etc.). For Container Scanning, it will always be `container_scanning`. |
|
||||||
|
| `vulnerabilities[].message` | A short text that describes the vulnerability, it may include occurrence's specific information. Optional. |
|
||||||
|
| `vulnerabilities[].description` | A long text that describes the vulnerability. Optional. |
|
||||||
|
| `vulnerabilities[].cve` | A fingerprint string value that represents a concrete occurrence of the vulnerability. It's used to determine whether two vulnerability occurrences are same or different. May not be 100% accurate. **This is NOT a [CVE](https://cve.mitre.org/)**. |
|
||||||
|
| `vulnerabilities[].severity` | How much the vulnerability impacts the software. Possible values: `Undefined` (an analyzer has not provided this info), `Info`, `Unknown`, `Low`, `Medium`, `High`, `Critical`. **Note:** Our current container scanning tool based on [klar](https://github.com/optiopay/klar) only provides the following levels: `Unknown`, `Low`, `Medium`, `High`, `Critical`. |
|
||||||
|
| `vulnerabilities[].confidence` | How reliable the vulnerability's assessment is. Possible values: `Undefined` (an analyzer has not provided this info), `Ignore`, `Unknown`, `Experimental`, `Low`, `Medium`, `High`, `Confirmed`. **Note:** Our current container scanning tool based on [klar](https://github.com/optiopay/klar) does not provide a confidence level, so this value is currently hardcoded to `Unknown`. |
|
||||||
|
| `vulnerabilities[].solution` | Explanation of how to fix the vulnerability. Optional. |
|
||||||
|
| `vulnerabilities[].scanner` | A node that describes the analyzer used to find this vulnerability. |
|
||||||
|
| `vulnerabilities[].scanner.id` | Id of the scanner as a snake_case string. |
|
||||||
|
| `vulnerabilities[].scanner.name` | Name of the scanner, for display purposes. |
|
||||||
|
| `vulnerabilities[].location` | A node that tells where the vulnerability is located. |
|
||||||
|
| `vulnerabilities[].location.dependency` | A node that describes the dependency of a project where the vulnerability is located. |
|
||||||
|
| `vulnerabilities[].location.dependency.package` | A node that provides the information on the package where the vulnerability is located. |
|
||||||
|
| `vulnerabilities[].location.dependency.package.name` | Name of the package where the vulnerability is located. |
|
||||||
|
| `vulnerabilities[].location.dependency.version` | Version of the vulnerable package. Optional. |
|
||||||
|
| `vulnerabilities[].location.operating_system` | The operating system that contains the vulnerable package. |
|
||||||
|
| `vulnerabilities[].location.image` | The Docker image that was analyzed. Optional. |
|
||||||
|
| `vulnerabilities[].identifiers` | An ordered array of references that identify a vulnerability on internal or external DBs. |
|
||||||
|
| `vulnerabilities[].identifiers[].type` | Type of the identifier. Possible values: common identifier types (among `cve`, `cwe`, `osvdb`, and `usn`). |
|
||||||
|
| `vulnerabilities[].identifiers[].name` | Name of the identifier for display purpose. |
|
||||||
|
| `vulnerabilities[].identifiers[].value` | Value of the identifier for matching purpose. |
|
||||||
|
| `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. |
|
||||||
|
| `vulnerabilities[].links` | An array of references to external documentation pieces or articles that describe the vulnerability further. Optional. |
|
||||||
|
| `vulnerabilities[].links[].name` | Name of the vulnerability details link. Optional. |
|
||||||
|
| `vulnerabilities[].links[].url` | URL of the vulnerability details document. Optional. |
|
||||||
|
| `remediations` | Not supported yet. |
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### docker: Error response from daemon: failed to copy xattrs
|
### docker: Error response from daemon: failed to copy xattrs
|
||||||
|
|
|
@ -5202,6 +5202,9 @@ msgstr ""
|
||||||
msgid "CustomCycleAnalytics|Add stage"
|
msgid "CustomCycleAnalytics|Add stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CustomCycleAnalytics|Editing stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "CustomCycleAnalytics|Enter a name for the stage"
|
msgid "CustomCycleAnalytics|Enter a name for the stage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -5235,6 +5238,9 @@ msgstr ""
|
||||||
msgid "CustomCycleAnalytics|Stop event label"
|
msgid "CustomCycleAnalytics|Stop event label"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "CustomCycleAnalytics|Update stage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Customize colors"
|
msgid "Customize colors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,9 @@ describe('Environment item', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render last deployment date', () => {
|
it('should render last deployment date', () => {
|
||||||
const formatedDate = format(environment.last_deployment.deployed_at);
|
const formattedDate = format(environment.last_deployment.deployed_at);
|
||||||
|
|
||||||
expect(wrapper.find('.environment-created-date-timeago').text()).toContain(formatedDate);
|
expect(wrapper.find('.environment-created-date-timeago').text()).toContain(formattedDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('With user information', () => {
|
describe('With user information', () => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe('Erased block', () => {
|
||||||
|
|
||||||
const erasedAt = '2016-11-07T11:11:16.525Z';
|
const erasedAt = '2016-11-07T11:11:16.525Z';
|
||||||
const timeago = getTimeago();
|
const timeago = getTimeago();
|
||||||
const formatedDate = timeago.format(erasedAt);
|
const formattedDate = timeago.format(erasedAt);
|
||||||
|
|
||||||
const createComponent = props => {
|
const createComponent = props => {
|
||||||
wrapper = mount(ErasedBlock, {
|
wrapper = mount(ErasedBlock, {
|
||||||
|
@ -41,7 +41,7 @@ describe('Erased block', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders erasedAt', () => {
|
it('renders erasedAt', () => {
|
||||||
expect(wrapper.text().trim()).toContain(formatedDate);
|
expect(wrapper.text().trim()).toContain(formattedDate);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ describe('Erased block', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders erasedAt', () => {
|
it('renders erasedAt', () => {
|
||||||
expect(wrapper.text().trim()).toContain(formatedDate);
|
expect(wrapper.text().trim()).toContain(formattedDate);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -68,7 +68,7 @@ describe('table registry', () => {
|
||||||
expect(tds.at(2).html()).toContain(repoPropsData.list[0].shortRevision);
|
expect(tds.at(2).html()).toContain(repoPropsData.list[0].shortRevision);
|
||||||
expect(tds.at(3).html()).toContain(repoPropsData.list[0].layers);
|
expect(tds.at(3).html()).toContain(repoPropsData.list[0].layers);
|
||||||
expect(tds.at(3).html()).toContain(repoPropsData.list[0].size);
|
expect(tds.at(3).html()).toContain(repoPropsData.list[0].size);
|
||||||
expect(tds.at(4).html()).toContain(wrapper.vm.timeFormated(repoPropsData.list[0].createdAt));
|
expect(tds.at(4).html()).toContain(wrapper.vm.timeFormatted(repoPropsData.list[0].createdAt));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a label called Image ID', () => {
|
it('should have a label called Image ID', () => {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
jest.mock('~/vue_shared/mixins/timeago', () => ({
|
jest.mock('~/vue_shared/mixins/timeago', () => ({
|
||||||
methods: {
|
methods: {
|
||||||
timeFormated() {
|
timeFormatted() {
|
||||||
return '7 fortnightes ago';
|
return '7 fortnights ago';
|
||||||
},
|
},
|
||||||
tooltipTitle() {
|
tooltipTitle() {
|
||||||
return 'February 30, 2401';
|
return 'February 30, 2401';
|
||||||
|
@ -82,7 +82,7 @@ describe('Release block footer', () => {
|
||||||
|
|
||||||
it('renders the author and creation time info', () => {
|
it('renders the author and creation time info', () => {
|
||||||
expect(trimText(authorDateInfoSection().text())).toBe(
|
expect(trimText(authorDateInfoSection().text())).toBe(
|
||||||
`Created 7 fortnightes ago by ${releaseClone.author.username}`,
|
`Created 7 fortnights ago by ${releaseClone.author.username}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ describe('Release block footer', () => {
|
||||||
beforeEach(() => factory({ author: undefined }));
|
beforeEach(() => factory({ author: undefined }));
|
||||||
|
|
||||||
it('renders the release date without the author name', () => {
|
it('renders the release date without the author name', () => {
|
||||||
expect(trimText(authorDateInfoSection().text())).toBe('Created 7 fortnightes ago');
|
expect(trimText(authorDateInfoSection().text())).toBe('Created 7 fortnights ago');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ describe('Release block', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders release date', () => {
|
it('renders release date', () => {
|
||||||
expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormated(release.released_at));
|
expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders number of assets provided', () => {
|
it('renders number of assets provided', () => {
|
||||||
|
|
|
@ -90,11 +90,11 @@ describe('RelatedIssuableItem', () => {
|
||||||
|
|
||||||
it('renders state title', () => {
|
it('renders state title', () => {
|
||||||
const stateTitle = tokenState.attributes('title');
|
const stateTitle = tokenState.attributes('title');
|
||||||
const formatedCreateDate = formatDate(props.createdAt);
|
const formattedCreateDate = formatDate(props.createdAt);
|
||||||
|
|
||||||
expect(stateTitle).toContain('<span class="bold">Opened</span>');
|
expect(stateTitle).toContain('<span class="bold">Opened</span>');
|
||||||
|
|
||||||
expect(stateTitle).toContain(`<span class="text-tertiary">${formatedCreateDate}</span>`);
|
expect(stateTitle).toContain(`<span class="text-tertiary">${formattedCreateDate}</span>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders aria label', () => {
|
it('renders aria label', () => {
|
||||||
|
|
|
@ -41,6 +41,7 @@ describe('diffs/components/app', () => {
|
||||||
changesEmptyStateIllustration: '',
|
changesEmptyStateIllustration: '',
|
||||||
dismissEndpoint: '',
|
dismissEndpoint: '',
|
||||||
showSuggestPopover: true,
|
showSuggestPopover: true,
|
||||||
|
useSingleDiffStyle: false,
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
store,
|
store,
|
||||||
|
|
|
@ -75,6 +75,7 @@ describe('DiffsStoreActions', () => {
|
||||||
const projectPath = '/root/project';
|
const projectPath = '/root/project';
|
||||||
const dismissEndpoint = '/-/user_callouts';
|
const dismissEndpoint = '/-/user_callouts';
|
||||||
const showSuggestPopover = false;
|
const showSuggestPopover = false;
|
||||||
|
const useSingleDiffStyle = false;
|
||||||
|
|
||||||
testAction(
|
testAction(
|
||||||
setBaseConfig,
|
setBaseConfig,
|
||||||
|
@ -85,6 +86,7 @@ describe('DiffsStoreActions', () => {
|
||||||
projectPath,
|
projectPath,
|
||||||
dismissEndpoint,
|
dismissEndpoint,
|
||||||
showSuggestPopover,
|
showSuggestPopover,
|
||||||
|
useSingleDiffStyle,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
|
@ -93,6 +95,7 @@ describe('DiffsStoreActions', () => {
|
||||||
projectPath: '',
|
projectPath: '',
|
||||||
dismissEndpoint: '',
|
dismissEndpoint: '',
|
||||||
showSuggestPopover: true,
|
showSuggestPopover: true,
|
||||||
|
useSingleDiffStyle: true,
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -104,6 +107,7 @@ describe('DiffsStoreActions', () => {
|
||||||
projectPath,
|
projectPath,
|
||||||
dismissEndpoint,
|
dismissEndpoint,
|
||||||
showSuggestPopover,
|
showSuggestPopover,
|
||||||
|
useSingleDiffStyle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -10,11 +10,13 @@ describe('DiffsStoreMutations', () => {
|
||||||
const state = {};
|
const state = {};
|
||||||
const endpoint = '/diffs/endpoint';
|
const endpoint = '/diffs/endpoint';
|
||||||
const projectPath = '/root/project';
|
const projectPath = '/root/project';
|
||||||
|
const useSingleDiffStyle = false;
|
||||||
|
|
||||||
mutations[types.SET_BASE_CONFIG](state, { endpoint, projectPath });
|
mutations[types.SET_BASE_CONFIG](state, { endpoint, projectPath, useSingleDiffStyle });
|
||||||
|
|
||||||
expect(state.endpoint).toEqual(endpoint);
|
expect(state.endpoint).toEqual(endpoint);
|
||||||
expect(state.projectPath).toEqual(projectPath);
|
expect(state.projectPath).toEqual(projectPath);
|
||||||
|
expect(state.useSingleDiffStyle).toEqual(useSingleDiffStyle);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -721,6 +721,28 @@ describe('common_utils', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('convertObjectPropsToSnakeCase', () => {
|
||||||
|
it('converts each object key to snake case', () => {
|
||||||
|
const obj = {
|
||||||
|
some: 'some',
|
||||||
|
'cool object': 'cool object',
|
||||||
|
likeThisLongOne: 'likeThisLongOne',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(commonUtils.convertObjectPropsToSnakeCase(obj)).toEqual({
|
||||||
|
some: 'some',
|
||||||
|
cool_object: 'cool object',
|
||||||
|
like_this_long_one: 'likeThisLongOne',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty object if there are no keys', () => {
|
||||||
|
['', {}, [], null].forEach(badObj => {
|
||||||
|
expect(commonUtils.convertObjectPropsToSnakeCase(badObj)).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('with options', () => {
|
describe('with options', () => {
|
||||||
const objWithoutChildren = {
|
const objWithoutChildren = {
|
||||||
project_name: 'GitLab CE',
|
project_name: 'GitLab CE',
|
||||||
|
|
Loading…
Reference in New Issue