Commit de24e9b8 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 3c727ede e861af40
......@@ -4,7 +4,12 @@ include:
- local: /lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
.dedicated-runner: &dedicated-runner
retry: 1
retry:
max: 1 # This is confusing but this means "2 runs at max".
when:
- unknown_failure
- api_failure
- runner_system_failure
tags:
- gitlab-org
......@@ -557,7 +562,7 @@ rspec-mysql:
parallel: 50
.rspec-quarantine: &rspec-quarantine
retry: 0
<<: *only-schedules-master
script:
- export CACHE_CLASSES=true
- scripts/gitaly-test-spawn
......@@ -588,7 +593,7 @@ static-analysis:
- tmp/rubocop_cache
# Documentation checks:
# - Check validity of relative links
# - Check validity of relative links, and anchors
# - Make sure cURL examples in API docs use the full switches
docs lint:
<<: *dedicated-runner
......@@ -607,6 +612,8 @@ docs lint:
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
downtime_check:
<<: *rake-exec
......@@ -1168,4 +1175,3 @@ schedule:review-performance:
<<: *review-schedules-only
script:
- wait_for_job_to_be_done "schedule:review-deploy"
......@@ -50,7 +50,7 @@ export default {
},
modalText() {
const linkStart = `<a class="commit-sha" href="${_.escape(this.commitUrl)}">`;
const linkStart = `<a class="commit-sha mr-0" href="${_.escape(this.commitUrl)}">`;
const commitId = _.escape(this.commitShortSha);
const linkEnd = '</a>';
const name = _.escape(this.name);
......
......@@ -504,22 +504,28 @@ export default {
class="table-section section-10 deployment-column d-none d-sm-none d-md-block"
role="gridcell"
>
<span v-if="shouldRenderDeploymentID"> {{ deploymentInternalId }} </span>
<span v-if="shouldRenderDeploymentID" class="text-break-word">
{{ deploymentInternalId }}
</span>
<span v-if="!model.isFolder && deploymentHasUser">
<span v-if="!model.isFolder && deploymentHasUser" class="text-break-word">
by
<user-avatar-link
:link-href="deploymentUser.web_url"
:img-src="deploymentUser.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="deploymentUser.username"
class="js-deploy-user-container"
class="js-deploy-user-container float-none"
/>
</span>
</div>
<div class="table-section section-15 d-none d-sm-none d-md-block" role="gridcell">
<a v-if="shouldRenderBuildName" :href="buildPath" class="build-link flex-truncate-parent">
<a
v-if="shouldRenderBuildName"
:href="buildPath"
class="build-link cgray flex-truncate-parent"
>
<span class="flex-truncate-child">{{ buildName }}</span>
</a>
</div>
......
......@@ -72,10 +72,9 @@ export default {
<gl-button
v-gl-tooltip
v-gl-modal.confirm-rollback-modal
variant="secondary"
:disabled="isLoading"
:title="title"
class="d-none d-md-block"
class="d-none d-md-block text-secondary"
@click="onClick"
>
<icon v-if="isLastDeployment" name="repeat" /> <icon v-else name="redo" />
......
......@@ -39,7 +39,7 @@ export default {
:aria-label="title"
:href="terminalPath"
:class="{ disabled: disabled }"
class="btn terminal-button d-none d-sm-none d-md-block"
class="btn terminal-button d-none d-sm-none d-md-block text-secondary"
>
<icon name="terminal" />
</a>
......
......@@ -81,9 +81,6 @@ export default {
const formData = {
update: {
state_event: this.form.find('input[name="update[state_event]"]').val(),
// For Merge Requests
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
// For Issues
assignee_ids: [this.form.find('input[name="update[assignee_ids][]"]').val()],
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
......
......@@ -18,5 +18,3 @@ export const timeWindows = {
threeDays: __('3 days'),
oneWeek: __('1 week'),
};
export const msPerMinute = 60000;
import { timeWindows, msPerMinute } from './constants';
import { timeWindows } from './constants';
/**
* method that converts a predetermined time window to minutes
......@@ -6,27 +6,26 @@ import { timeWindows, msPerMinute } from './constants';
* @param {String} timeWindow - The time window to convert to minutes
* @returns {number} The time window in minutes
*/
const getTimeDifferenceMinutes = timeWindow => {
const getTimeDifferenceSeconds = timeWindow => {
switch (timeWindow) {
case timeWindows.thirtyMinutes:
return 30;
return 60 * 30;
case timeWindows.threeHours:
return 60 * 3;
return 60 * 60 * 3;
case timeWindows.oneDay:
return 60 * 24 * 1;
return 60 * 60 * 24 * 1;
case timeWindows.threeDays:
return 60 * 24 * 3;
return 60 * 60 * 24 * 3;
case timeWindows.oneWeek:
return 60 * 24 * 7 * 1;
return 60 * 60 * 24 * 7 * 1;
default:
return 60 * 8;
return 60 * 60 * 8;
}
};
export const getTimeDiff = selectedTimeWindow => {
const end = Date.now();
const timeDifferenceMinutes = getTimeDifferenceMinutes(selectedTimeWindow);
const start = new Date(end - timeDifferenceMinutes * msPerMinute).getTime();
const end = Date.now() / 1000; // convert milliseconds to seconds
const start = end - getTimeDifferenceSeconds(selectedTimeWindow);
return { start, end };
};
......
......@@ -115,8 +115,11 @@ export default {
author() {
return this.getUserData;
},
canUpdateIssue() {
return this.getNoteableData.current_user.can_update;
canToggleIssueState() {
return (
this.getNoteableData.current_user.can_update &&
this.getNoteableData.state !== constants.MERGED
);
},
endpoint() {
return this.getNoteableData.create_note_path;
......@@ -415,7 +418,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
</div>
<loading-button
v-if="canUpdateIssue"
v-if="canToggleIssueState"
:loading="isToggleStateButtonLoading"
:container-class="[
actionButtonClassNames,
......
......@@ -4,6 +4,7 @@ import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { s__, sprintf } from '../../locale';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
......@@ -23,7 +24,7 @@ export default {
noteBody,
TimelineEntryItem,
},
mixins: [noteable, resolvable],
mixins: [noteable, resolvable, draftMixin],
props: {
note: {
type: Object,
......@@ -73,9 +74,6 @@ export default {
'is-editable': this.note.current_user.can_edit,
};
},
canResolve() {
return this.note.resolvable && !!this.getUserData.id;
},
canReportAsAbuse() {
return !!this.note.report_abuse_path && this.author.id !== this.getUserData.id;
},
......@@ -156,12 +154,16 @@ export default {
this.$refs.noteBody.resetAutoSave();
this.$emit('updateSuccess');
},
formUpdateHandler(noteText, parentElement, callback) {
formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) {
this.$emit('handleUpdateNote', {
note: this.note,
noteText,
resolveDiscussion,
callback: () => this.updateSuccess(),
});
if (this.isDraft) return;
const data = {
endpoint: this.note.path,
note: {
......@@ -234,6 +236,7 @@ export default {
<div class="timeline-content">
<div class="note-header">
<note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id">
<slot slot="note-header-info" name="note-header-info"></slot>
<span v-if="commit" v-html="actionText"></span>
<span v-else class="d-none d-sm-inline">&middot;</span>
</note-header>
......@@ -247,12 +250,15 @@ export default {
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
:can-resolve="note.current_user.can_resolve"
:can-resolve="canResolve"
:report-abuse-path="note.report_abuse_path"
:resolvable="note.resolvable"
:is-resolved="note.resolved"
:resolvable="note.resolvable || note.isDraft"
:is-resolved="note.resolved || note.resolve_discussion"
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
:is-draft="note.isDraft"
:resolve-discussion="note.isDraft && note.resolve_discussion"
:discussion-id="discussionId"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
......
......@@ -7,6 +7,7 @@ export const COMMENT = 'comment';
export const OPENED = 'opened';
export const REOPENED = 'reopened';
export const CLOSED = 'closed';
export const MERGED = 'merged';
export const EMOJI_THUMBSUP = 'thumbsup';
export const EMOJI_THUMBSDOWN = 'thumbsdown';
export const ISSUE_NOTEABLE_TYPE = 'issue';
......
export default {
computed: {
isDraft: () => false,
canResolve() {
return this.note.current_user.can_resolve;
},
},
};
......@@ -84,10 +84,7 @@ export default {
</div>
</div>
<div>
<div
v-if="isFetchingMergeRequests"
class="related-related-merge-requests-icon qa-related-merge-requests-loading-icon"
>
<div v-if="isFetchingMergeRequests" class="qa-related-merge-requests-loading-icon">
<gl-loading-icon label="Fetching related merge requests" class="py-2" />
</div>
<ul v-else class="content-list related-items-list">
......
......@@ -74,8 +74,7 @@ export default {
}
if (!this.users.length) {
const emptyTooltipLabel =
this.issuableType === 'issue' ? __('Assignee(s)') : __('Assignee');
const emptyTooltipLabel = __('Assignee(s)');
names.push(emptyTooltipLabel);
}
......@@ -90,6 +89,27 @@ export default {
return counter;
},
mergeNotAllowedTooltipMessage() {
const assigneesCount = this.users.length;
if (this.issuableType !== 'merge_request' || assigneesCount === 0) {
return null;
}
const cannotMergeCount = this.users.filter(u => u.can_merge === false).length;
const canMergeCount = assigneesCount - cannotMergeCount;
if (canMergeCount === assigneesCount) {
// Everyone can merge
return null;
} else if (cannotMergeCount === assigneesCount && assigneesCount > 1) {
return 'No one can merge';
} else if (assigneesCount === 1) {
return 'Cannot merge';
}
return `${canMergeCount}/${assigneesCount} can merge`;
},
},
methods: {
assignSelf() {
......@@ -154,6 +174,15 @@ export default {
</button>
</div>
<div class="value hide-collapsed">
<span
v-if="mergeNotAllowedTooltipMessage"
v-tooltip
:title="mergeNotAllowedTooltipMessage"
data-placement="left"
class="float-right cannot-be-merged"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-exclamation-triangle"></i>
</span>
<template v-if="hasNoUsers">
<span class="assign-yourself no-value">
No assignee
......
......@@ -162,7 +162,7 @@ export default {
</template>
<icon name="commit" class="commit-icon js-commit-icon" />
<gl-link :href="commitUrl" class="commit-sha"> {{ shortSha }} </gl-link>
<gl-link :href="commitUrl" class="commit-sha mr-0"> {{ shortSha }} </gl-link>
<div class="commit-title flex-truncate-parent">
<span v-if="title" class="flex-truncate-child">
......
......@@ -25,6 +25,18 @@ $item-weight-max-width: 48px;
flex-grow: 1;
}
.issue-token-state-icon-open {
color: $green-500;
}
.issue-token-state-icon-closed {
color: $blue-500;
}
.merge-request-status.closed {
color: $red-500;
}
.issue-token-state-icon-open,
.issue-token-state-icon-closed,
.confidential-icon,
......
......@@ -12,34 +12,6 @@
.environments-container {
.ci-table {
.deployment-column {
> span {
word-break: break-all;
}
.avatar {
float: none;
}
}
.btn-group {
> .btn:not(.btn-danger) {
color: $gl-text-color-secondary;
}
svg path {
fill: $gl-text-color-secondary;
}
.dropdown {
outline: none;
}
}
.btn .text-center {
display: inline;
}
.commit-title {
margin: 0;
}
......@@ -49,47 +21,16 @@
color: $gl-text-color-secondary;
}
.dropdown-menu {
.fa {
margin-right: 6px;
color: $gl-text-color-secondary;
}
}
.build-link,
.ref-name {
color: $gl-text-color;
}
.stop-env-link,
.external-url {
color: $gl-text-color-secondary;
.stop-env-icon {
font-size: 14px;
}
}
.deployment .build-column {
.build-link {
color: $gl-text-color;
}
.avatar {
float: none;
margin-right: 0;
}
}
.folder-icon {
margin-right: 3px;
color: $gl-text-color-secondary;
display: inline-block;
vertical-align: text-top;
.fa:nth-child(1) {
margin-right: 3px;
}
}
.folder-name {
......@@ -103,12 +44,6 @@
text-align: center;
}
.branch-commit {
.commit-sha {
margin-right: 0;
}
}
.no-btn {
border: 0;
background: none;
......@@ -168,11 +103,6 @@
opacity: 0.25;
}
.prometheus-graph-overlay {
fill: none;
opacity: 0;
pointer-events: all;
}
.rect-text-metric {
fill: $white-light;
......@@ -203,276 +133,10 @@
stroke: $gray-darkest;
}
.prometheus-graphs {
.dropdowns {
.dropdown-menu-toggle {
svg {
position: absolute;
right: 5%;
top: 25%;
}
}
.dropdown-menu-toggle,
.dropdown-menu {
width: 240px;
}
}
}
.environments-actions {
.external-url,
.monitoring-url,
.terminal-button,
.stop-env-link {
.terminal-button {
width: 38px;
}
}
.prometheus-panel {
margin-top: 20px;
}
.prometheus-graph-group {
display: flex;
flex-wrap: wrap;
padding: $gl-padding / 2;
}
.prometheus-graph {
padding: $gl-padding / 2;
}
.prometheus-graph-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $gl-padding-8;
h5 {
font-size: $gl-font-size-large;
margin: 0;
}
}
.prometheus-graph-cursor {
position: absolute;
background: $gray-600;
width: 1px;
}
.prometheus-graph-flag {
display: block;
min-width: 160px;
border: 0;
box-shadow: 0 1px 4px 0 $black-transparent;