Commit 026cc147 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into stateful_deployments-ee

parents c5dd5ce2 8c31b2f4
......@@ -167,7 +167,7 @@ export default {
<button
v-if="shouldShowCommentButton"
type="button"
class="add-diff-note js-add-diff-note-button"
class="add-diff-note js-add-diff-note-button qa-diff-comment"
title="Add a comment to this line"
@click="handleCommentButton"
>
......
......@@ -102,7 +102,7 @@ export default {
:line-type="newLineType"
:is-bottom="isBottom"
:is-hover="isHover"
class="diff-line-num new_line"
class="diff-line-num new_line qa-new-diff-line"
/>
<td
:class="line.type"
......
<script>
import { s__, sprintf } from '~/locale';
import { formatTime } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
import tooltip from '../../vue_shared/directives/tooltip';
......@@ -28,10 +30,24 @@ export default {
},
},
methods: {
onClickAction(endpoint) {
onClickAction(action) {
if (action.scheduledAt) {
const confirmationMessage = sprintf(
s__(
"DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes.",
),
{ jobName: action.name },
);
// https://gitlab.com/gitlab-org/gitlab-ce/issues/52156
// eslint-disable-next-line no-alert
if (!window.confirm(confirmationMessage)) {
return;
}
}
this.isLoading = true;
eventHub.$emit('postAction', { endpoint });
eventHub.$emit('postAction', { endpoint: action.playPath });
},
isActionDisabled(action) {
......@@ -41,6 +57,11 @@ export default {
return !action.playable;
},
remainingTime(action) {
const remainingMilliseconds = new Date(action.scheduledAt).getTime() - Date.now();
return formatTime(Math.max(0, remainingMilliseconds));
},
},
};
</script>
......@@ -54,7 +75,7 @@ export default {
:aria-label="title"
:disabled="isLoading"
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
class="dropdown btn btn-default dropdown-new js-environment-actions-dropdown"
data-container="body"
data-toggle="dropdown"
>
......@@ -75,12 +96,19 @@ export default {
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
class="js-manual-action-link no-btn btn d-flex align-items-center"
@click="onClickAction(action)"
>
<span>
<span class="flex-fill">
{{ action.name }}
</span>
<span
v-if="action.scheduledAt"
class="text-secondary"
>
<icon name="clock" />
{{ remainingTime(action) }}
</span>
</button>
</li>
</ul>
......
......@@ -13,6 +13,7 @@ import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
/**
* Environment Item Component
......@@ -73,21 +74,6 @@ export default {
return false;
},
/**
* Verifies is the given environment has manual actions.
* Used to verify if we should render them or nor.
*
* @returns {Boolean|Undefined}
*/
hasManualActions() {
return (
this.model &&
this.model.last_deployment &&
this.model.last_deployment.manual_actions &&
this.model.last_deployment.manual_actions.length > 0
);
},
/**
* Checkes whether the environment is protected.
* (`is_protected` currently only set in EE)
......@@ -154,23 +140,20 @@ export default {
return '';
},
/**
* Returns the manual actions with the name parsed.
*
* @returns {Array.<Object>|Undefined}
*/
manualActions() {
if (this.hasManualActions) {
return this.model.last_deployment.manual_actions.map(action => {
const parsedAction = {
name: humanize(action.name),
play_path: action.play_path,
playable: action.playable,
};
return parsedAction;
});
actions() {
if (!this.model || !this.model.last_deployment || !this.canCreateDeployment) {
return [];
}
return [];
const { manualActions, scheduledActions } = convertObjectPropsToCamelCase(
this.model.last_deployment,
{ deep: true },
);
const combinedActions = (manualActions || []).concat(scheduledActions || []);
return combinedActions.map(action => ({
...action,
name: humanize(action.name),
}));
},
/**
......@@ -443,7 +426,7 @@ export default {
displayEnvironmentActions() {
return (
this.hasManualActions ||
this.actions.length > 0 ||
this.externalURL ||
this.monitoringUrl ||
this.canStopEnvironment ||
......@@ -641,8 +624,8 @@ export default {
/>
<actions-component
v-if="hasManualActions && canCreateDeployment"
:actions="manualActions"
v-if="actions.length > 0"
:actions="actions"
/>
<terminal-button-component
......
<script>
import { GlLink } from '@gitlab-org/gitlab-ui';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
components: {
TimeagoTooltip,
GlLink,
},
mixins: [timeagoMixin],
props: {
......@@ -53,16 +55,16 @@ export default {
class="btn-group d-flex"
role="group"
>
<a
<gl-link
v-if="artifact.keep_path"
:href="artifact.keep_path"
class="js-keep-artifacts btn btn-sm btn-default"
data-method="post"
>
{{ s__('Job|Keep') }}
</a>
</gl-link>
<a
<gl-link
v-if="artifact.download_path"
:href="artifact.download_path"
class="js-download-artifacts btn btn-sm btn-default"
......@@ -70,15 +72,15 @@ export default {
rel="nofollow"
>
{{ s__('Job|Download') }}
</a>
</gl-link>
<a
<gl-link
v-if="artifact.browse_path"
:href="artifact.browse_path"
class="js-browse-artifacts btn btn-sm btn-default"
>
{{ s__('Job|Browse') }}
</a>
</gl-link>
</div>
</div>
</template>
<script>
import { GlLink } from '@gitlab-org/gitlab-ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
components: {
ClipboardButton,
GlLink,
},
props: {
commit: {
......@@ -31,10 +33,10 @@ export default {
<p>
{{ __('Commit') }}
<a
<gl-link
:href="commit.commit_path"
class="js-commit-sha commit-sha link-commit"
>{{ commit.short_id }}</a>
>{{ commit.short_id }}</gl-link>
<clipboard-button
:text="commit.short_id"
......@@ -42,11 +44,11 @@ export default {
css-class="btn btn-clipboard btn-transparent"
/>
<a
<gl-link
v-if="mergeRequest"
:href="mergeRequest.path"
class="js-link-commit link-commit"
>!{{ mergeRequest.iid }}</a>
>!{{ mergeRequest.iid }}</gl-link>
</p>
<p class="build-light-text append-bottom-0">
......
<script>
import { GlLink } from '@gitlab-org/gitlab-ui';
export default {
components: {
GlLink,
},
props: {
illustrationPath: {
type: String,
......@@ -62,13 +67,13 @@ export default {
v-if="action"
class="text-center"
>
<a
<gl-link
:href="action.path"
:data-method="action.method"
class="js-job-empty-state-action btn btn-primary"
>
{{ action.button_title }}
</a>
</gl-link>
</div>
</div>
</div>
......
<script>
import _ from 'underscore';
import { GlLink } from '@gitlab-org/gitlab-ui';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
components: {
TimeagoTooltip,
GlLink,
},
props: {
user: {
......@@ -29,9 +31,9 @@ export default {
<div class="erased alert alert-warning">
<template v-if="isErasedByUser">
{{ s__("Job|Job has been erased by") }}
<a :href="user.web_url">
<gl-link :href="user.web_url">
{{ user.username }}
</a>
</gl-link>
</template>
<template v-else>
{{ s__("Job|Job has been erased") }}
......
<script>
import _ from 'underscore';
import { mapGetters, mapState, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
......@@ -26,6 +27,7 @@ export default {
EmptyState,
EnvironmentsBlock,
ErasedBlock,
GlLoadingIcon,
Log,
LogTopBar,
StuckBlock,
......
<script>
import { GlTooltipDirective, GlLink } from '@gitlab-org/gitlab-ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
components: {
CiIcon,
Icon,
GlLink,
},
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
props: {
job: {
......@@ -37,11 +38,10 @@ export default {
active: isActive
}"
>
<a
v-tooltip
<gl-link
v-gl-tooltip
:href="job.status.details_path"
:title="tooltipText"
data-container="body"
data-boundary="viewport"
class="js-job-link"
>
......@@ -60,6 +60,6 @@ export default {
name="retry"
class="js-retry-icon"
/>
</a>
</gl-link>
</div>
</template>
<script>
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab-org/gitlab-ui';
import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { sprintf } from '~/locale';
import scrollDown from '../svg/scroll_down.svg';
......@@ -9,9 +9,11 @@ import scrollDown from '../svg/scroll_down.svg';
export default {
components: {
Icon,
GlLink,
GlButton,
},
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
scrollDown,
props: {
......@@ -73,76 +75,70 @@ export default {
<template v-if="isTraceSizeVisible">
{{ jobLogSize }}
<a
<gl-link
v-if="rawPath"
:href="rawPath"
class="js-raw-link raw-link"
>
{{ s__("Job|Complete Raw") }}
</a>
</gl-link>
</template>
</div>
<!-- eo truncate information -->
<div class="controllers float-right">
<!-- links -->
<a
<gl-link
v-if="rawPath"
v-tooltip
v-gl-tooltip.body
:title="s__('Job|Show complete raw')"
:href="rawPath"
class="js-raw-link-controller controllers-buttons"
data-container="body"
>
<icon name="doc-text" />
</a>
</gl-link>
<a
<gl-link
v-if="erasePath"
v-tooltip
v-gl-tooltip.body
:title="s__('Job|Erase job log')"
:href="erasePath"
:data-confirm="__('Are you sure you want to erase this build?')"
class="js-erase-link controllers-buttons"
data-container="body"
data-method="post"
>
<icon name="remove" />
</a>
</gl-link>
<!-- eo links -->
<!-- scroll buttons -->
<div
v-tooltip
v-gl-tooltip
:title="s__('Job|Scroll to top')"
class="controllers-buttons"
data-container="body"
>
<button
<gl-button
:disabled="isScrollTopDisabled"
type="button"
class="js-scroll-top btn-scroll btn-transparent btn-blank"
@click="handleScrollToTop"
>
<icon name="scroll_up"/>
</button>
<icon name="scroll_up" />
</gl-button>
</div>
<div
v-tooltip
v-gl-tooltip
:title="s__('Job|Scroll to bottom')"
class="controllers-buttons"
data-container="body"
>
<button
<gl-button
:disabled="isScrollBottomDisabled"
type="button"
class="js-scroll-bottom btn-scroll btn-transparent btn-blank"
:class="{ animate: isScrollingDown }"
@click="handleScrollToBottom"
v-html="$options.scrollDown"
>
</button>
/>
</div>
<!-- eo scroll buttons -->
</div>
......
<script>
import { GlLink } from '@gitlab-org/gitlab-ui';
export default {
name: 'SidebarDetailRow',
components: {
GlLink,
},
props: {
title: {
type: String,
......@@ -41,7 +46,7 @@ export default {
v-if="hasHelpURL"
class="help-button float-right"
>
<a
<gl-link
:href="helpUrl"
target="_blank"
rel="noopener noreferrer nofollow"
......@@ -50,7 +55,7 @@ export default {
class="fa fa-question-circle"
aria-hidden="true"
></i>
</a>
</gl-link>
</span>
</p>
</template>
<script>
import { GlLink } from '@gitlab-org/gitlab-ui';
/**
* Renders Stuck Runners block for job's view.
*/
export default {
components: {
GlLink,
},
props: {
hasNoRunnersForProject: {
type: Boolean,
......@@ -52,12 +56,12 @@ export default {
</p>
{{ __("Go to") }}
<a
<gl-link
v-if="runnersPath"
:href="runnersPath"
class="js-runners-path"
>
{{ __("Runners page") }}
</a>
</gl-link>
</div>
</template>
......@@ -390,7 +390,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
:disabled="isSubmitButtonDisabled"
name="button"
type="button"
class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle"
class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
data-display="static"
data-toggle="dropdown"
aria-label="Open comment type dropdown">
......@@ -422,7 +422,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<li :class="{ 'droplab-item-selected': noteType === 'discussion' }">
<button
type="button"
class="btn btn-transparent"
class="btn btn-transparent qa-discussion-option"
@click.prevent="setNoteType('discussion')">
<i
aria-hidden="true"
......
<script>
import $ from 'jquery';
import Icon from '~/vue_shared/components/icon.vue';
import { mapGetters, mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants';
export default {
components: {
......@@ -12,14 +13,17 @@ export default {
type: Array,
required: true,
},
defaultValue: {
selectedValue: {
type: Number,
default: null,
required: false,
},
},
data() {
return { currentValue: this.defaultValue };
return {
currentValue: this.selectedValue,
defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE,
};
},
computed: {
...mapGetters(['getNotesDataByProp']),
......@@ -28,8 +32,11 @@ export default {
return this.filters.find(filter => filter.value === this.currentValue);
},
},
mounted() {
this.toggleCommentsForm();
},
methods: {
...mapActions(['filterDiscussion']),
...mapActions(['filterDiscussion', 'setCommentsDisabled']),
selectFilter(value) {
const filter = parseInt(value, 10);
......@@ -39,6 +46,10 @@ export default {
if (filter === this.currentValue) return;
this.currentValue = filter;
this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
this.toggleCommentsForm();
},
toggleCommentsForm() {
this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE);
},
},
};
......@@ -73,6 +84,10 @@ export default {
>
{{ filter.title }}
</button>
<div
v-if="filter.value === defaultValue"
class="dropdown-divider"
></div>
</li>
</ul>
</div>
......
......@@ -191,7 +191,7 @@ export default {
:data-supports-quick-actions="!isEditing"
name="note[note]"
class="note-textarea js-gfm-input js-note-text
js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
@keydown.meta.enter="handleUpdate()"
......@@ -216,6 +216,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
<input
v-model="isUnresolving"
type="checkbox"
class="qa-unresolve-review-discussion"
/>
{{ __('Unresolve discussion') }}
</template>
......@@ -225,6 +226,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
<input
v-model="isResolving"
type="checkbox"
class="qa-resolve-review-discussion"
/>
{{ __('Resolve discussion') }}
</template>
......@@ -234,7 +236,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
<button
:disabled="isDisabled"
type="button"
class="btn btn-success"
class="btn btn-success qa-start-review"
@click="handleAddToReview()">
<template v-if="hasDrafts">
{{ __('Add to review') }}
......@@ -246,7 +248,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
<button
:disabled="isDisabled"
type="button"
class="btn"
class="btn qa-comment-now"
@click="handleUpdate()">
{{ __('Add comment now') }}
</button>
......
......@@ -385,7 +385,7 @@ Please check your network connection and try again.`;
role="group">
<button
type="button"
class="js-vue-discussion-reply btn btn-text-field mr-2"
class="js-vue-discussion-reply btn btn-text-field mr-2 qa-discussion-reply"
title="Add a reply"
@click="showReplyForm">Reply...</button>
</div>
......
......@@ -60,6 +60,7 @@ export default {
'getNotesDataByProp',
'discussionCount',
'isLoading',
'commentsDisabled',
]),
noteableType() {
return this.noteableData.noteableType;
......@@ -206,6 +207,7 @@ export default {
</ul>
<comment-form
v-if="!commentsDisabled"
:noteable-type="noteableType"
:markdown-version="markdownVersion"
/>
......
......@@ -15,6 +15,8 @@ export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post';
export const DESCRIPTION_TYPE = 'changed the description';
export const HISTORY_ONLY_FILTER_VALUE = 2;
export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE,
......
......@@ -6,7 +6,7 @@ export default store => {
if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry => ({
title: entry,
......@@ -24,7 +24,7 @@ export default store => {
return createElement('discussion-filter', {
props: {
filters,
defaultValue,
selectedValue,
},
});
},
......
......@@ -364,5 +364,9 @@ export const filterDiscussion = ({ dispatch }, { path, filter }) => {
});
};
export const setCommentsDisabled = ({ commit }, data) => {
commit(types.DISABLE_COMMENTS, data);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -195,5 +195,7 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
export const getDiscussion = state => discussionId =>
state.discussions.find(discussion => discussion.id === discussionId);
export const commentsDisabled = state => state.commentsDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -21,6 +21,7 @@ export default () => ({
noteableData: {
current_user: {},
},
commentsDisabled: false,
},
actions,
getters,
......
......@@ -15,6 +15,7 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
......
......@@ -225,4 +225,8 @@ export default {
discussion.truncated_diff_lines = diffLines;
},
[types.DISABLE_COMMENTS](state, value) {
state.commentsDisabled = value;
},
};
......@@ -29,7 +29,7 @@ export default {
if (action.scheduled_at) {
const confirmationMessage = sprintf(
s__(
"DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes.",
"DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes.",
),
{ jobName: action.name },
);
......
......@@ -322,15 +322,15 @@
width: $contextual-sidebar-width - 1px;
transition: width $sidebar-transition-duration;
position: fixed;
height: $toggle-sidebar-height;
bottom: 0;
padding: $gl-padding;
padding: 0 $gl-padding;
background-color: $gray-light;
border: 0;
border-top: 1px solid $border-color;
color: $gl-text-color-secondary;
display: flex;
align-items: center;
line-height: 1;
svg {
margin-right: 8px;
......
......@@ -291,7 +291,7 @@
/*
* Mixin that handles the position of the controls placed on the top bar
*/
@mixin build-controllers($control-font-size, $flex-direction, $with-grow, $flex-grow-size) {
@mixin build-controllers($control-font-size, $flex-direction, $with-grow, $flex-grow-size, $svg-display: 'block', $svg-top: '2px') {
display: flex;
font-size: $control-font-size;
justify-content: $flex-direction;
......@@ -304,8 +304,9 @@
svg {
width: 15px;
height: 15px;
display: block;
display: $svg-display;
fill: $gl-text-color;
top: $svg-top;
}
.controllers-buttons {
......
......@@ -10,6 +10,7 @@ $sidebar-breakpoint: 1024px;
$default-transition-duration: 0.15s;
$contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px;
$toggle-sidebar-height: 48px;
/*
* Color schema
......
......@@ -57,10 +57,6 @@
.top-bar {
@include build-trace-top-bar(35px);
&.sidebar-expanded {
margin-right: calc(#{$gutter-width} - 16px);
}
.truncated-info {
.truncated-info-size {
margin: 0 5px;
......@@ -74,7 +70,7 @@
}
.controllers {
@include build-controllers(15px, center, false, 0);
@include build-controllers(15px, center, false, 0, inline, 0);
}
}
......
......@@ -44,11 +44,6 @@
margin: 0;
}
.icon-play {
height: 13px;
width: 12px;
}
.external-url,
.dropdown-new {
color: $gl-text-color-secondary;
......@@ -526,7 +521,7 @@
}
.arrow-shadow {
content: "";
content: '';
position: absolute;
width: 7px;
height: 7px;
......
......@@ -116,6 +116,7 @@ module ApplicationSettingsHelper
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
:archive_builds_in_human_readable,
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
......
......@@ -109,6 +109,8 @@ module IconsHelper
def file_type_icon_class(type, mode, name)
if type == 'folder'
icon_class = 'folder'
elsif type == 'archive'
icon_class = 'archive'
elsif mode == '120000'
icon_class = 'share'
else
......
......@@ -31,11 +31,21 @@ module TreeHelper
# mode - File unix mode
# name - File name
def tree_icon(type, mode, name)
icon("#{file_type_icon_class(type, mode, name)} fw")
icon([file_type_icon_class(type, mode, name), 'fw'])
end
def tree_hex_class(content)
"file_#{hexdigest(content.name)}"
# Using Rails `*_path` methods can be slow, especially when generating
# many paths, as with a repository tree that has thousands of items.
def fast_project_blob_path(project, blob_path)
Addressable::URI.escape(
File.join(relative_url_root, project.path_with_namespace, 'blob', blob_path)
)
end
def fast_project_tree_path(project, tree_path)
Addressable::URI.escape(
File.join(relative_url_root, project.path_with_namespace, 'tree', tree_path)
)
end
# Simple shortcut to File.join
......@@ -142,4 +152,8 @@ module TreeHelper
def selected_branch
@branch_name || tree_edit_branch
end
def relative_url_root
Gitlab.config.gitlab.relative_url_root.presence || '/'
end
end
......@@ -5,6 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
include CacheMarkdownField
include TokenAuthenticatable
include IgnorableColumn
include ChronicDurationAttribute
prepend EE::ApplicationSetting
add_authentication_token_field :runners_registration_token
......@@ -46,6 +47,8 @@ class ApplicationSetting < ActiveRecord::Base
default_value_for :id, 1
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
validates :uuid, presence: true
validates :session_expire_delay,
......@@ -185,6 +188,10 @@ class ApplicationSetting < ActiveRecord::Base
validates :user_default_internal_regex, js_regex: true, allow_nil: true
validates :archive_builds_in_seconds,
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
......@@ -442,6 +449,10 @@ class ApplicationSetting < ActiveRecord::Base
latest_terms
end
def archive_builds_older_than
archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
end
private
def ensure_uuid!
......
......@@ -258,17 +258,37 @@ module Ci
.fabricate!
end
def other_actions
def other_manual_actions
pipeline.manual_actions.where.not(name: name)
end
def other_scheduled_actions
pipeline.scheduled_actions.where.not(name: name)
end
def pages_generator?
Gitlab.config.pages.enabled &&
self.name == 'pages'
end
# degenerated build is one that cannot be run by Runner
def degenerated?
self.options.nil?
end
def degenerate!
self.update!(options: nil, yaml_variables: nil, commands: nil)
end
def archived?
return true if degenerated?
archive_builds_older_than = Gitlab::CurrentSettings.current_application_settings.archive_builds_older_than
archive_builds_older_than.present? && created_at < archive_builds_older_than
end
def playable?
action? && (manual? || scheduled? || retryable?)
action? && !archived? && (manual? || scheduled? || retryable?)
end
def schedulable?
......@@ -296,7 +316,7 @@ module Ci
end
def retryable?
success? || failed? || canceled?
!archived? && (success? || failed? || canceled?)
end
def retries_count
......@@ -304,7 +324,7 @@ module Ci
end
def retries_max
self.options.fetch(:retry, 0).to_i
self.options.to_h.fetch(:retry, 0).to_i
end
def latest?
......
......@@ -53,7 +53,8 @@ class CommitStatus < ActiveRecord::Base
missing_dependency_failure: 5,
runner_unsupported: 6,
stale_schedule: 7,
job_execution_timeout: 8
job_execution_timeout: 8,
archived_failure: 9
}.merge(EE_FAILURE_REASONS)
##
......@@ -169,16 +170,18 @@ class CommitStatus < ActiveRecord::Base
false
end
# To be overridden when inherrited from
def retryable?
false
end
# To be overridden when inherrited from
def cancelable?
false
end
def archived?
false
end
def stuck?
false
end
......
......@@ -89,7 +89,11 @@ class Deployment < ActiveRecord::Base
end
def manual_actions
@manual_actions ||= deployable.try(:other_actions)
@manual_actions ||= deployable.try(:other_manual_actions)
end
def scheduled_actions
@scheduled_actions ||= deployable.try(:other_scheduled_actions)
end
def includes_commit?(commit)
......
......@@ -119,6 +119,8 @@ class Note < ActiveRecord::Base
case notes_filter
when UserPreference::NOTES_FILTERS[:only_comments]
user
when UserPreference::NOTES_FILTERS[:only_activity]
system
else
all
end
......
......@@ -4,7 +4,7 @@ class UserPreference < ActiveRecord::Base
# We could use enums, but Rails 4 doesn't support multiple
# enum options with same name for multiple fields, also it creates
# extra methods that aren't really needed here.
NOTES_FILTERS = { all_notes: 0, only_comments: 1 }.freeze
NOTES_FILTERS = { all_notes: 0, only_comments: 1, only_activity: 2 }.freeze
belongs_to :user
......@@ -14,7 +14,8 @@ class UserPreference < ActiveRecord::Base
def notes_filters
{
s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes],
s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments]
s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments],
s_('Notes|Show history only') => NOTES_FILTERS[:only_activity]
}
end
end
......
......@@ -22,12 +22,17 @@ module Ci
@subject.project.branch_allows_collaboration?(@user, @subject.ref)
end
condition(:archived, scope: :subject) do
@subject.archived?
end
condition(:terminal, scope: :subject) do
@subject.has_terminal?
end
rule { protected_ref }.policy do
rule { protected_ref | archived }.policy do
prevent :update_build
prevent :update_commit_status
prevent :erase_build
end
......
......@@ -2,4 +2,13 @@
class DeploymentPolicy < BasePolicy
delegate { @subject.project }
condition(:can_retry_deployable) do
can?(:update_build, @subject.deployable)
end
rule { ~can_retry_deployable }.policy do
prevent :create_deployment
prevent :update_deployment
end
end
......@@ -9,7 +9,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
missing_dependency_failure: 'There has been a missing dependency failure',
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
job_execution_timeout: 'The script exceeded the maximum execution time set for the job'
job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
archived_failure: 'The job is archived and cannot be run'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
......@@ -31,6 +32,6 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
end
def unrecoverable?
script_failure? || missing_dependency_failure?
script_failure? || missing_dependency_failure? || archived_failure?
end
end
......@@ -25,4 +25,5 @@ class DeploymentEntity < Grape::Entity
expose :commit, using: CommitEntity
expose :deployable, using: JobEntity
expose :manual_actions, using: JobEntity
expose :scheduled_actions, using: JobEntity
end
......@@ -7,6 +7,7 @@ class JobEntity < Grape::Entity
expose :name
expose :started?, as: :started
expose :archived?, as: :archived
expose :build_path do |build|
build_path(build)
......
......@@ -7,4 +7,8 @@ class UserPreferenceEntity < Grape::Entity
expose :notes_filters do |user_preference|
UserPreference.notes_filters
end
expose :default_notes_filter do |user_preference|
UserPreference::NOTES_FILTERS[:all_notes]
end
end
......@@ -84,6 +84,11 @@ module Ci
return false
end
if build.archived?
build.drop!(:archived_failure)
return false
end
build.run!
true
end
......
......@@ -44,5 +44,13 @@
The default unit is in seconds, but you can define an alternative. For example:
<code>4 mins 2 sec</code>, <code>2h42min</code>.
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
.form-group
= f.label :archive_builds_in_human_readable, 'Archive builds in', class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
.form-text.text-muted
Set the duration when build gonna be considered old. Archived builds cannot be retried.
Make it empty to never expire builds. It has to be larger than 1 day.
The default unit is in seconds, but you can define an alternative. For example:
<code>4 mins 2 sec</code>, <code>2h42min</code>.
= f.submit 'Save changes', class: "btn btn-success"
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
- if can?(current_user, :create_deployment, deployment)
- tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment')
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do
- if deployment.last?
......
......@@ -34,7 +34,7 @@
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.merge-request-tabs.nav-tabs.nav.nav-links.scrolling-tabs
%li.notes-tab
%li.notes-tab.qa-notes-tab
= tab_link_for @merge_request, :show, force_link: @commit.present? do
Discussion
%span.badge.badge-pill= @merge_request.related_notes.user.count
......@@ -48,7 +48,7 @@
= tab_link_for @merge_request, :pipelines do
Pipelines
%span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
%li.diffs-tab
%li.diffs-tab.qa-diffs-tab
= tab_link_for @merge_request, :diffs do
Changes
%span.badge.badge-pill= @merge_request.diff_size
......
......@@ -61,12 +61,14 @@
%td.responsive-table-cell.build-failure{ data: { column: _("Failure")} }
= build.present.callout_failure_message
%td.responsive-table-cell.build-actions
= link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
= icon('repeat')
%tr.build-trace-row.responsive-table-border-end
%td
%td.responsive-table-cell.build-trace-container{ colspan: 4 }
%pre.build-trace.build-trace-rounded
%code.bash.js-build-output
= build_summary(build)
- if can?(current_user, :update_build, job)
= link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
= icon('repeat')
- if can?(current_user, :read_build, job)
%tr.build-trace-row.responsive-table-border-end
%td
%td.responsive-table-cell.build-trace-container{ colspan: 4 }
%pre.build-trace.build-trace-rounded
%code.bash.js-build-output
= build_summary(build)
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
- is_lfs_blob = @lfs_blob_ids.include?(blob_item.id)
%tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
%td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name
= link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do
%span= file_name
- if is_lfs_blob
%span.badge.label-lfs.prepend-left-5 LFS
%td.d-none.d-sm-table-cell.tree-commit
%td.tree-time-ago.cgray.text-right
= render 'projects/tree/spinner'
%span.log_loading.hide
%i.fa.fa-spinner.fa-spin
Loading commit data...
%tr.tree-item
%td.tree-item-file-name
%i.fa.fa-archive.fa-fw
= submodule_link(submodule_item, @ref)
%td
%td.d-none.d-sm-table-cell
%tr{ class: "tree-item #{tree_hex_class(tree_item)}" }
%td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(@path, tree_item)
= link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), class: 'str-truncated', title: path do
%span= path
%td.d-none.d-sm-table-cell.tree-commit
%td.tree-time-ago.text-right
= render 'projects/tree/spinner'
- if tree_row.type == :tree
= render partial: 'projects/tree/tree_item', object: tree_row, as: 'tree_item', locals: { type: 'folder' }
- elsif tree_row.type == :blob
= render partial: 'projects/tree/blob_item', object: tree_row, as: 'blob_item', locals: { type: 'file' }
- elsif tree_row.type == :commit
= render partial: 'projects/tree/submodule_item', object: tree_row, as: 'submodule_item'
- tree_row_name = tree_row.name
- tree_row_type = tree_row.type
%tr{ class: "tree-item file_#{hexdigest(tree_row_name)}" }
%td.tree-item-file-name
- if tree_row_type == :tree
= tree_icon('folder', tree_row.mode, tree_row.name)
- path = flatten_tree(@path, tree_row)
%a.str-truncated{ href: fast_project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path }
%span= path
- elsif tree_row_type == :blob
= tree_icon('file', tree_row.mode, tree_row_name)
%a.str-truncated{ href: fast_project_blob_path(@project, tree_join(@id || @commit.id, tree_row_name)), title: tree_row_name }
%span= tree_row_name
- if @lfs_blob_ids.include?(tree_row.id)
%span.badge.label-lfs.prepend-left-5 LFS
- elsif tree_row_type == :commit
= tree_icon('archive', tree_row.mode, tree_row.name)
= submodule_link(tree_row, @ref)
%td.d-none.d-sm-table-cell.tree-commit
%td.tree-time-ago.text-right
%span.log_loading.hide
%i.fa.fa-spinner.fa-spin
Loading commit data...
---
title: Uses gitlab-ui components in jobs components
merge_request:
author:
type: other
---
title: Bump KUBERNETES_VERSION for Auto DevOps to latest 1.10 series
merge_request: 22757
author:
type: other
---
title: Soft-archive old jobs
merge_request:
author:
type: added
---
title: Improve performance of tree rendering in repositories with lots of items
merge_request:
author:
type: performance
---
title: Enable frozen string for remaining lib/gitlab/ci/**/*.rb
merge_request:
author: gfyoung
type: performance
---
title: Add 'only history' option to notes filter
merge_request:
author:
type: changed
---
title: Add the Play button for delayed jobs in environment page
merge_request: 22106
author:
type: added
---
title: Align toggle sidebar button across all browsers and OSs
merge_request: 22771
author:
type: fixed
---
title: Bump Gitaly to 0.128.0
merge_request:
author:
type: added
# frozen_string_literal: true
class AddArchiveBuildsDurationToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column(:application_settings, :archive_builds_in_seconds, :integer, allow_null: true)
end
end
......@@ -211,6 +211,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
t.integer "usage_stats_set_by_user_id"
t.integer "receive_max_input_size"
t.integer "diff_max_patch_bytes", default: 102400, null: false
t.integer "archive_builds_in_seconds"
end
create_table "approvals", force: :cascade do |t|
......
......@@ -16,7 +16,7 @@ query.
## Can I git push to a secondary node?
Yes, you can push changes to a **secondary** node. The push will be proxied to the **primary** node.
Yes! Pushing directly to a **secondary** node (for both HTTP and SSH, including git-lfs) was [introduced](https://about.gitlab.com/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3.
## How long does it take to have a commit replicated to a secondary node?
......
This diff is collapsed.
......@@ -2,22 +2,17 @@
# Using a Geo Server
After you set up the [database replication and configure the Geo nodes][req],
there are a few things to consider:
After you set up the [database replication and configure the Geo nodes][req], use your closest GitLab node as you would a normal standalone GitLab instance.
1. Users need an extra step to be able to fetch code from the secondary and push
to primary:
Pushing directly to a **secondary** node (for both HTTP, SSH including git-lfs) was [introduced](https://about.gitlab.com/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3.
1. Clone the repository as you would normally do, but from the secondary node:
Example of the output you will see when pushing to a **secondary** node:
```bash
git clone git@secondary.gitlab.example.com:user/repo.git
```
1. Change the remote push URL to always push to primary, following this example:
```bash
git remote set-url --push origin git@primary.gitlab.example.com:user/repo.git
```
```bash
$ git push
> GitLab: You're pushing to a Geo secondary.
> GitLab: We'll help you by proxying this request to the primary: ssh://git@primary.geo/user/repo.git
Everything up-to-date
```
[req]: index.md#setup-instructions
......@@ -7,14 +7,14 @@ in GitLab 11.3. To learn how to use
When the GitLab Maven Repository is enabled, every project in GitLab will have
its own space to store [Maven](https://maven.apache.org/) packages.
To learn how to use it, see the [user documentation](../user/project/packages/maven.md).
To learn how to use it, see the [user documentation](../user/project/packages/maven_repository.md).
## Enabling the Maven Repository
NOTE: **Note:**
Once enabled, newly created projects will have the Packages feature enabled by
default. Existing projects will need to
[explicitly enabled it](../user/project/packages/maven.md#enabling-the-packages-repository).
[explicitly enabled it](../user/project/packages/maven_repository.md#enabling-the-packages-repository).
To enable the Maven repository:
......
......@@ -26,6 +26,7 @@ Gets all epics of the requested group and its subgroups.
GET /groups/:id/epics
GET /groups/:id/epics?author_id=5
GET /groups/:id/epics?labels=bug,reproduced
GET /groups/:id/epics?state=opened
```
| Attribute | Type | Required | Description |
......@@ -36,6 +37,7 @@ GET /groups/:id/epics?labels=bug,reproduced
| `order_by` | string | no | Return epics ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return epics sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search epics against their `title` and `description` |
| `state` | string | no | Search epics against their `state`, possible filters: `opened`, `closed` and `all`, default: `all` |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/1/epics
......
......@@ -94,6 +94,7 @@ The following table depicts the various user permission levels in a project.
| Manage GitLab Pages | | | | ✓ | ✓ |
| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
| Remove GitLab Pages | | | | | ✓ |
| View GitLab Pages protected by [access control](../administration/pages/index.md#access-control) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Manage clusters | | | | ✓ | ✓ |
| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ |
......@@ -205,7 +206,7 @@ They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all
other cases.
An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
......@@ -216,7 +217,7 @@ by an administrator under **Admin > Application Settings**.
### Default internal users
The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
New users whose email address matches the regex pattern will be set to internal by default rather than an external collaborator.
......
......@@ -66,7 +66,7 @@ export default {
<button
ref="dropdown"
type="button"
class="btn btn-success review-preview-dropdown-toggle"
class="btn btn-success review-preview-dropdown-toggle qa-review-preview-toggle"
@click="toggleReviewDropdown"
>
{{ __('Finish review') }}
......
......@@ -44,7 +44,7 @@ export default {
<template>
<loading-button
:loading="isPublishing"
container-class="btn btn-success js-publish-draft-button"
container-class="btn btn-success js-publish-draft-button qa-submit-review"
@click="onClick"
>
<span>
......
......@@ -46,13 +46,13 @@ export default {
<template>
<div v-show="draftsCount > 0">
<nav class="review-bar-component">
<div class="review-bar-content">
<div class="review-bar-content qa-review-bar">
<preview-dropdown />
<loading-button
v-gl-modal="$options.modalId"
:loading="isDiscarding"
:label="__('Discard review')"
class="float-right"
class="qa-discard-review float-right"
/>
</div>
</nav>
......@@ -61,7 +61,7 @@ export default {
:ok-title="s__('BatchComments|Delete all pending comments')"
:modal-id="$options.modalId"
title-tag="h4"
ok-variant="danger"
ok-variant="danger qa-modal-delete-pending-comments"
@ok="discardReview"
>
<p v-html="$options.text">
......
import $ from 'jquery';
import Mousetrap from 'mousetrap';
import Cookies from 'js-cookie';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
export default class ShortcutsEpic extends ShortcutsIssuable {
constructor() {
super();
const $issuableSidebar = $('.js-issuable-update');
Mousetrap.bind('l', () =>
ShortcutsEpic.openSidebarDropdown($issuableSidebar.find('.js-labels-block')),
);
Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText);
Mousetrap.bind('e', ShortcutsIssuable.editIssue);
}
static openSidebarDropdown($block) {
if (Cookies.get('collapsed_gutter') === 'true') {
document.dispatchEvent(new Event('toggleSidebarRevealLabelsDropdown'));
} else {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
}
}
}
import $ from 'jquery';
import Mousetrap from 'mousetrap';
import Cookies from 'js-cookie';
import bp from '~/breakpoints';
......@@ -7,10 +6,6 @@ export default class SidebarContext {
constructor() {
const $issuableSidebar = $('.js-issuable-update');
Mousetrap.bind('l', () =>
SidebarContext.openSidebarDropdown($issuableSidebar.find('.js-labels-block')),
);
$issuableSidebar
.off('click', '.js-sidebar-dropdown-toggle')
.on('click', '.js-sidebar-dropdown-toggle', function onClickEdit(e) {
......@@ -46,8 +41,4 @@ export default class SidebarContext {
}
});
}
static openSidebarDropdown($block) {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
}
}
......@@ -226,9 +226,17 @@ export default {
},
mounted() {
eventHub.$on('toggleSidebar', this.toggleSidebar);
document.addEventListener(
'toggleSidebarRevealLabelsDropdown',
this.toggleSidebarRevealLabelsDropdown,
);
},
beforeDestroy() {
eventHub.$off('toggleSidebar', this.toggleSidebar);
document.removeEventListener(
'toggleSidebarRevealLabelsDropdown',
this.toggleSidebarRevealLabelsDropdown,
);
},
methods: {
getDateValidity(startDate, endDate) {
......
......@@ -72,10 +72,10 @@ export default {
<commit
:commit-ref="commitRef"
:short-sha="project.last_deployment.commit.short_id"
:commit-url="project.last_deployment.commit.web_url"
:commit-url="project.last_deployment.commit.commit_url"
:title="project.last_deployment.commit.title"
:author="author"
:tag="project.last_deployment.commit.tag"
:tag="project.last_deployment.tag"
/>
</div>
<div
......
import ZenMode from '~/zen_mode';
import initEpicShow from 'ee/epics/epic_show/epic_show_bundle';
import ShortcutsEpic from 'ee/behaviors/shortcuts/shortcuts_epic';
import '~/notes/index';
document.addEventListener('DOMContentLoaded', () => {
new ZenMode(); // eslint-disable-line no-new
initEpicShow();
new ShortcutsEpic(); // eslint-disable-line no-new
});
......@@ -90,6 +90,7 @@ export default {
>{{ vulnerability.name }}</span>
<vulnerability-issue-link
v-if="hasIssue"
class="prepend-left-10"
:issue="vulnerability.issue_feedback"
:project-name="vulnerability.project.name"
/>
......
<script>
import { mapGetters, mapState } from 'vuex';
import VulnerabilityCount from './vulnerability_count.vue';
import { CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN } from '../store/modules/vulnerabilities/constants';
import { CRITICAL, HIGH, MEDIUM, LOW } from '../store/modules/vulnerabilities/constants';
const SEVERITIES = [CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN];
const SEVERITIES = [CRITICAL, HIGH, MEDIUM, LOW];
export default {
name: 'VulnerabilityCountList',
......
......@@ -32,7 +32,7 @@ export default {
<div class="d-inline">
<icon
v-gl-tooltip
name="issues"
name="issue-created"
css-classes="text-success vertical-align-middle"
:title="s__('Security Dashboard|Issue Created')"
/>
......
......@@ -2,4 +2,3 @@ export const CRITICAL = 'critical';
export const HIGH = 'high';
export const MEDIUM = 'medium';
export const LOW = 'low';
export const UNKNOWN = 'unknown';
......@@ -70,13 +70,13 @@
.form-group.reset-approvals-on-push
.form-check
= form.check_box :reset_approvals_on_push, class: 'form-check-input'
= form.label :reset_approvals_on_push do
= form.label :reset_approvals_on_push, class: 'form-check-label' do
%strong Remove all approvals in a merge request when new commits are pushed to its source branch
.form-group.self-approval
.form-check
= form.check_box :merge_requests_author_approval, class: 'form-check-input'
= form.label :merge_requests_author_approval do
= form.label :merge_requests_author_approval, class: 'form-check-label' do
%strong Enable self approval of merge requests
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals",
anchor: 'allowing-merge-request-authors-to-approve-their-own-merge-requests'), target: '_blank'
......
---
title: Add 'l', 'r' and 'e' keyboard shortcuts support in Epic
merge_request: 8203
author:
type: added
---
title: Removes extra rigth margin from job page
merge_request:
author:
type: fixed
---
title: Filter epics by state in API
merge_request: 8179
author:
type: added
---
title: Link project short SHA to commit url
merge_request: 8214
author:
type: fixed
......@@ -58,6 +58,8 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return epics sorted in `asc` or `desc` order.'
optional :search, type: String, desc: 'Search epics for text present in the title or description'
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all epics'
optional :author_id, type: Integer, desc: 'Return epics which are authored by the user with the given ID'
optional :labels, type: String, desc: 'Comma-separated list of label names'
end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Epic shortcuts', :js do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:label) { create(:group_label, group: group, title: 'bug') }
let(:note_text) { 'I got this!' }
let(:markdown) do
<<-MARKDOWN.strip_heredoc
This is a task list:
- [ ] Incomplete entry 1
MARKDOWN
end
let(:epic) { create(:epic, group: group, title: 'make tea', description: markdown) }
before do
group.add_developer(user)
stub_licensed_features(epics: true)
sign_in(user)
visit group_epic_path(group, epic)
end
describe 'pressing "l"' do
it "opens labels dropdown for editing" do
find('body').native.send_key('l')
expect(find('.js-labels-block')).to have_selector('.dropdown-menu-labels.show')
end
end
describe 'pressing "r"' do
before do
create(:note, noteable: epic, note: note_text)
visit group_epic_path(group, epic)
wait_for_requests
end
it "quotes the selected text" do
select_element('.note-text')
find('body').native.send_key('r')
expect(find('.js-main-target-form .js-vue-comment-form').value).to include(note_text)
end
end
describe 'pressing "e"' do
it "starts editing mode for epic" do
find('body').native.send_key('e')
expect(find('.detail-page-description')).to have_selector('form input#issuable-title')
expect(find('.detail-page-description')).to have_selector('form textarea#issue-description')
end
end
end
......@@ -45,8 +45,8 @@ describe 'Environments page', :js do
end
it 'shows an enabled play button' do
find('.js-dropdown-play-icon-container').click
play_button = %q{button[class="js-manual-action-link no-btn btn"]}
find('.js-environment-actions-dropdown').click
play_button = %q{button.js-manual-action-link.no-btn.btn}
expect(page).to have_selector(play_button)
end
......@@ -129,8 +129,8 @@ describe 'Environments page', :js do
end
it 'show a disabled play button' do
find('.js-dropdown-play-icon-container').click
disabled_play_button = %q{button[class="js-manual-action-link no-btn btn disabled"]}
find('.js-environment-actions-dropdown').click
disabled_play_button = %q{button.js-manual-action-link.no-btn.btn.disabled}
expect(page).to have_selector(disabled_play_button)
end
......
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import store from 'ee/operations/store/index';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import Dashboard from 'ee/operations/components/dashboard/dashboard.vue';
......@@ -12,24 +14,28 @@ describe('dashboard component', () => {
const ProjectSearchComponent = Vue.extend(ProjectSearch);
const DashboardProjectComponent = Vue.extend(DashboardProject);
const projectTokens = mockProjectData(1);
const mockListPath = 'mock-listPath';
const mount = () =>
mountComponentWithStore(DashboardComponent, {
store,
props: {
addPath: 'mock-addPath',
listPath: 'mock-listPath',
listPath: mockListPath,
emptyDashboardSvgPath: '/assets/illustrations/operations-dashboard_empty.svg',
},
});
let vm;
let mockAxios;
beforeEach(() => {
vm = mount();
mockAxios = new MockAdapter(axios);
});
afterEach(() => {
vm.$destroy();
clearState(store);
mockAxios.restore();
});
it('renders dashboard title', () => {
......@@ -94,11 +100,9 @@ describe('dashboard component', () => {
});
describe('empty state', () => {
beforeAll(done => {
vm.$store
.dispatch('requestProjects')
.then(() => vm.$nextTick(done))
.catch(done.fail);
beforeEach(() => {
mockAxios.onGet(mockListPath).replyOnce(200, { data: [] });
vm = mount();
});
it('renders empty state svg after requesting projects with no results', () => {
......
......@@ -74,8 +74,8 @@ describe('project component', () => {
expect(commit.shortSha).toBe(vm.project.last_deployment.commit.short_id);
});
it('binds web_url to commitUrl', () => {
expect(commit.commitUrl).toBe(vm.project.last_deployment.commit.web_url);
it('binds commitUrl', () => {
expect(commit.commitUrl).toBe(vm.project.last_deployment.commit.commit_url);
});
it('binds title', () => {
......@@ -87,7 +87,7 @@ describe('project component', () => {
});
it('binds tag', () => {
expect(commit.tag).toBe(vm.project.last_deployment.commit.tag);
expect(commit.tag).toBe(vm.project.last_deployment.tag);
});
});
......
......@@ -34,10 +34,10 @@ export function mockProjectData(
created_at: deployTimeStamp,
commit: {
short_id: 'mock-short_id',
tag: isTag,
title: 'mock-title',
web_url: 'https://mock-web_url/',
commit_url: 'https://mock-commit_url/',
},
tag: isTag,
user: {
avatar_url: null,
path: 'mock-path',
......
......@@ -92,6 +92,7 @@ describe API::Epics do
let!(:epic) do
create(:epic,
group: group,
state: :closed,
created_at: 3.days.ago,
updated_at: 2.days.ago)
end
......@@ -135,6 +136,18 @@ describe API::Epics do
expect_array_response([epic2.id])
end
it 'returns epics matching given status' do
get api(url, user), state: :opened
expect_array_response([epic2.id])
end
it 'returns all epics when state set to all' do
get api(url, user), state: :all
expect_array_response([epic2.id, epic.id])
end
it 'sorts by created_at descending by default' do
get api(url, user)
......
# frozen_string_literal: true
module Gitlab
module Ci
module Status
......
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