Merge branch 'master' into ide-file-finder
This commit is contained in:
commit
f1719de385
78 changed files with 959 additions and 474 deletions
|
@ -1,4 +1,4 @@
|
|||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git-2.17-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
|
||||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6"
|
||||
|
||||
.dedicated-runner: &dedicated-runner
|
||||
retry: 1
|
||||
|
|
6
Gemfile
6
Gemfile
|
@ -82,7 +82,7 @@ gem 'net-ldap'
|
|||
|
||||
# Git Wiki
|
||||
# Required manually in config/initializers/gollum.rb to control load order
|
||||
gem 'gitlab-gollum-lib', '~> 4.2'
|
||||
gem 'gitlab-gollum-lib', '~> 4.2', require: false
|
||||
|
||||
gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
|
||||
|
||||
|
@ -415,7 +415,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.94.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly'
|
||||
gem 'grpc', '~> 1.10.0'
|
||||
|
||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||
|
@ -434,5 +434,3 @@ gem 'grape_logging', '~> 1.7'
|
|||
|
||||
# Asset synchronization
|
||||
gem 'asset_sync', '~> 2.2.0'
|
||||
|
||||
gem 'goldiloader', '~> 2.0'
|
||||
|
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -290,9 +290,9 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.94.0)
|
||||
gitaly-proto (0.97.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
grpc (~> 1.10)
|
||||
github-linguist (5.3.3)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
escape_utils (~> 1.1.0)
|
||||
|
@ -331,9 +331,6 @@ GEM
|
|||
rubyntlm (~> 0.5)
|
||||
globalid (0.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
goldiloader (2.0.1)
|
||||
activerecord (>= 4.2, < 5.2)
|
||||
activesupport (>= 4.2, < 5.2)
|
||||
gollum-grit_adapter (1.0.1)
|
||||
gitlab-grit (~> 2.7, >= 2.7.1)
|
||||
gon (6.1.0)
|
||||
|
@ -1064,7 +1061,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 0.94.0)
|
||||
gitaly-proto (~> 0.97.0)
|
||||
github-linguist (~> 5.3.3)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-gollum-lib (~> 4.2)
|
||||
|
@ -1072,7 +1069,6 @@ DEPENDENCIES
|
|||
gitlab-markup (~> 1.6.2)
|
||||
gitlab-styles (~> 2.3)
|
||||
gitlab_omniauth-ldap (~> 2.0.4)
|
||||
goldiloader (~> 2.0)
|
||||
gon (~> 6.1.0)
|
||||
google-api-client (~> 0.19.8)
|
||||
google-protobuf (= 3.5.1)
|
||||
|
|
|
@ -30,10 +30,10 @@ export default class IssuableContext {
|
|||
const $selectbox = $block.find('.selectbox');
|
||||
if ($selectbox.is(':visible')) {
|
||||
$selectbox.hide();
|
||||
$block.find('.value').show();
|
||||
$block.find('.value:not(.dont-hide)').show();
|
||||
} else {
|
||||
$selectbox.show();
|
||||
$block.find('.value').hide();
|
||||
$block.find('.value:not(.dont-hide)').hide();
|
||||
}
|
||||
|
||||
if ($selectbox.is(':visible')) {
|
||||
|
|
|
@ -1,82 +1,94 @@
|
|||
<script>
|
||||
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import callout from '../../vue_shared/components/callout.vue';
|
||||
|
||||
export default {
|
||||
name: 'JobHeaderSection',
|
||||
components: {
|
||||
ciHeader,
|
||||
loadingIcon,
|
||||
export default {
|
||||
name: 'JobHeaderSection',
|
||||
components: {
|
||||
ciHeader,
|
||||
loadingIcon,
|
||||
callout,
|
||||
},
|
||||
props: {
|
||||
job: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
job: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: this.getActions(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: this.getActions(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
return this.job && this.job.status;
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
return this.job && this.job.status;
|
||||
},
|
||||
shouldRenderContent() {
|
||||
return !this.isLoading && Object.keys(this.job).length;
|
||||
},
|
||||
/**
|
||||
* When job has not started the key will be `false`
|
||||
* When job started the key will be a string with a date.
|
||||
*/
|
||||
jobStarted() {
|
||||
return !this.job.started === false;
|
||||
},
|
||||
shouldRenderContent() {
|
||||
return !this.isLoading && Object.keys(this.job).length;
|
||||
},
|
||||
watch: {
|
||||
job() {
|
||||
this.actions = this.getActions();
|
||||
},
|
||||
shouldRenderReason() {
|
||||
return !!(this.job.status && this.job.callout_message);
|
||||
},
|
||||
methods: {
|
||||
getActions() {
|
||||
const actions = [];
|
||||
/**
|
||||
* When job has not started the key will be `false`
|
||||
* When job started the key will be a string with a date.
|
||||
*/
|
||||
jobStarted() {
|
||||
return !this.job.started === false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
job() {
|
||||
this.actions = this.getActions();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getActions() {
|
||||
const actions = [];
|
||||
|
||||
if (this.job.new_issue_path) {
|
||||
actions.push({
|
||||
label: 'New issue',
|
||||
path: this.job.new_issue_path,
|
||||
cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block',
|
||||
type: 'link',
|
||||
});
|
||||
}
|
||||
return actions;
|
||||
},
|
||||
if (this.job.new_issue_path) {
|
||||
actions.push({
|
||||
label: 'New issue',
|
||||
path: this.job.new_issue_path,
|
||||
cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block',
|
||||
type: 'link',
|
||||
});
|
||||
}
|
||||
return actions;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="js-build-header build-header top-area">
|
||||
<ci-header
|
||||
v-if="shouldRenderContent"
|
||||
:status="status"
|
||||
item-name="Job"
|
||||
:item-id="job.id"
|
||||
:time="job.created_at"
|
||||
:user="job.user"
|
||||
:actions="actions"
|
||||
:has-sidebar-button="true"
|
||||
:should-render-triggered-label="jobStarted"
|
||||
<header>
|
||||
<div class="js-build-header build-header top-area">
|
||||
<ci-header
|
||||
v-if="shouldRenderContent"
|
||||
:status="status"
|
||||
item-name="Job"
|
||||
:item-id="job.id"
|
||||
:time="job.created_at"
|
||||
:user="job.user"
|
||||
:actions="actions"
|
||||
:has-sidebar-button="true"
|
||||
:should-render-triggered-label="jobStarted"
|
||||
/>
|
||||
<loading-icon
|
||||
v-if="isLoading"
|
||||
size="2"
|
||||
class="prepend-top-default append-bottom-default"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<callout
|
||||
v-if="shouldRenderReason"
|
||||
:message="job.callout_message"
|
||||
/>
|
||||
<loading-icon
|
||||
v-if="isLoading"
|
||||
size="2"
|
||||
class="prepend-top-default append-bottom-default"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
|
|
@ -1,80 +1,119 @@
|
|||
<script>
|
||||
import detailRow from './sidebar_detail_row.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import timeagoMixin from '../../vue_shared/mixins/timeago';
|
||||
import { timeIntervalInWords } from '../../lib/utils/datetime_utility';
|
||||
import detailRow from './sidebar_detail_row.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import timeagoMixin from '../../vue_shared/mixins/timeago';
|
||||
import { timeIntervalInWords } from '../../lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
name: 'SidebarDetailsBlock',
|
||||
components: {
|
||||
detailRow,
|
||||
loadingIcon,
|
||||
export default {
|
||||
name: 'SidebarDetailsBlock',
|
||||
components: {
|
||||
detailRow,
|
||||
loadingIcon,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
job: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mixins: [
|
||||
timeagoMixin,
|
||||
],
|
||||
props: {
|
||||
job: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
runnerHelpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
shouldRenderContent() {
|
||||
return !this.isLoading && Object.keys(this.job).length > 0;
|
||||
},
|
||||
coverage() {
|
||||
return `${this.job.coverage}%`;
|
||||
},
|
||||
duration() {
|
||||
return timeIntervalInWords(this.job.duration);
|
||||
},
|
||||
queued() {
|
||||
return timeIntervalInWords(this.job.queued);
|
||||
},
|
||||
runnerId() {
|
||||
return `#${this.job.runner.id}`;
|
||||
},
|
||||
hasTimeout() {
|
||||
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
|
||||
},
|
||||
timeout() {
|
||||
if (this.job.metadata == null) {
|
||||
return '';
|
||||
}
|
||||
canUserRetry: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
runnerHelpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
shouldRenderContent() {
|
||||
return !this.isLoading && Object.keys(this.job).length > 0;
|
||||
},
|
||||
coverage() {
|
||||
return `${this.job.coverage}%`;
|
||||
},
|
||||
duration() {
|
||||
return timeIntervalInWords(this.job.duration);
|
||||
},
|
||||
queued() {
|
||||
return timeIntervalInWords(this.job.queued);
|
||||
},
|
||||
runnerId() {
|
||||
return `#${this.job.runner.id}`;
|
||||
},
|
||||
retryButtonClass() {
|
||||
let className = 'js-retry-button pull-right btn btn-retry visible-md-block visible-lg-block';
|
||||
className +=
|
||||
this.job.status && this.job.recoverable
|
||||
? ' btn-primary'
|
||||
: ' btn-inverted-secondary';
|
||||
return className;
|
||||
},
|
||||
hasTimeout() {
|
||||
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
|
||||
},
|
||||
timeout() {
|
||||
if (this.job.metadata == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let t = this.job.metadata.timeout_human_readable;
|
||||
if (this.job.metadata.timeout_source !== '') {
|
||||
t += ` (from ${this.job.metadata.timeout_source})`;
|
||||
}
|
||||
let t = this.job.metadata.timeout_human_readable;
|
||||
if (this.job.metadata.timeout_source !== '') {
|
||||
t += ` (from ${this.job.metadata.timeout_source})`;
|
||||
}
|
||||
|
||||
return t;
|
||||
},
|
||||
renderBlock() {
|
||||
return this.job.merge_request ||
|
||||
this.job.duration ||
|
||||
this.job.finished_data ||
|
||||
this.job.erased_at ||
|
||||
this.job.queued ||
|
||||
this.job.runner ||
|
||||
this.job.coverage ||
|
||||
this.job.tags.length ||
|
||||
this.job.cancel_path;
|
||||
},
|
||||
return t;
|
||||
},
|
||||
};
|
||||
renderBlock() {
|
||||
return (
|
||||
this.job.merge_request ||
|
||||
this.job.duration ||
|
||||
this.job.finished_data ||
|
||||
this.job.erased_at ||
|
||||
this.job.queued ||
|
||||
this.job.runner ||
|
||||
this.job.coverage ||
|
||||
this.job.tags.length ||
|
||||
this.job.cancel_path
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="block">
|
||||
<strong class="inline prepend-top-8">
|
||||
{{ job.name }}
|
||||
</strong>
|
||||
<a
|
||||
v-if="canUserRetry"
|
||||
:class="retryButtonClass"
|
||||
:href="job.retry_path"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
>
|
||||
{{ __('Retry') }}
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="__('Toggle Sidebar')"
|
||||
class="btn btn-blank gutter-toggle pull-right
|
||||
visible-xs-block visible-sm-block js-sidebar-build-toggle"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
data-hidden="true"
|
||||
class="fa fa-angle-double-right"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="shouldRenderContent">
|
||||
<div
|
||||
class="block retry-link"
|
||||
|
@ -85,16 +124,16 @@
|
|||
class="js-new-issue btn btn-new btn-inverted"
|
||||
:href="job.new_issue_path"
|
||||
>
|
||||
New issue
|
||||
{{ __('New issue') }}
|
||||
</a>
|
||||
<a
|
||||
v-if="job.retry_path"
|
||||
v-if="canUserRetry"
|
||||
class="js-retry-job btn btn-inverted-secondary"
|
||||
:href="job.retry_path"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
>
|
||||
Retry
|
||||
{{ __('Retry') }}
|
||||
</a>
|
||||
</div>
|
||||
<div :class="{block : renderBlock }">
|
||||
|
@ -103,7 +142,7 @@
|
|||
v-if="job.merge_request"
|
||||
>
|
||||
<span class="build-light-text">
|
||||
Merge Request:
|
||||
{{ __('Merge Request:') }}
|
||||
</span>
|
||||
<a :href="job.merge_request.path">
|
||||
!{{ job.merge_request.iid }}
|
||||
|
@ -158,7 +197,7 @@
|
|||
v-if="job.tags.length"
|
||||
>
|
||||
<span class="build-light-text">
|
||||
Tags:
|
||||
{{ __('Tags:') }}
|
||||
</span>
|
||||
<span
|
||||
v-for="(tag, i) in job.tags"
|
||||
|
@ -178,7 +217,7 @@
|
|||
data-method="post"
|
||||
rel="nofollow"
|
||||
>
|
||||
Cancel
|
||||
{{ __('Cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -35,9 +35,11 @@ export default () => {
|
|||
});
|
||||
|
||||
// Sidebar information block
|
||||
const detailsBlockElement = document.getElementById('js-details-block-vue');
|
||||
const detailsBlockDataset = detailsBlockElement.dataset;
|
||||
// eslint-disable-next-line
|
||||
new Vue({
|
||||
el: '#js-details-block-vue',
|
||||
el: detailsBlockElement,
|
||||
components: {
|
||||
detailsBlock,
|
||||
},
|
||||
|
@ -50,6 +52,7 @@ export default () => {
|
|||
return createElement('details-block', {
|
||||
props: {
|
||||
isLoading: this.mediator.state.isLoading,
|
||||
canUserRetry: !!('canUserRetry' in detailsBlockDataset),
|
||||
job: this.mediator.store.state.job,
|
||||
runnerHelpUrl: dataset.runnerHelpUrl,
|
||||
},
|
||||
|
|
27
app/assets/javascripts/vue_shared/components/callout.vue
Normal file
27
app/assets/javascripts/vue_shared/components/callout.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
const calloutVariants = ['danger', 'success', 'info', 'warning'];
|
||||
|
||||
export default {
|
||||
props: {
|
||||
category: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: calloutVariants[0],
|
||||
validator: value => calloutVariants.includes(value),
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="`bs-callout bs-callout-${category}`"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
>
|
||||
{{ message }}
|
||||
</div>
|
||||
</template>
|
|
@ -16,7 +16,7 @@
|
|||
.nav-header-btn {
|
||||
padding: 10px $gl-sidebar-padding;
|
||||
color: inherit;
|
||||
transition-duration: .3s;
|
||||
transition-duration: 0.3s;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
|
@ -137,6 +137,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.issuable-sidebar .labels {
|
||||
.value.dont-hide ~ .selectbox {
|
||||
padding-top: $gl-padding-8;
|
||||
}
|
||||
}
|
||||
|
||||
.pikaday-container {
|
||||
.pika-single {
|
||||
margin-top: 2px;
|
||||
|
@ -151,4 +157,3 @@
|
|||
.sidebar-collapsed-icon .sidebar-collapsed-value {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +1,56 @@
|
|||
@keyframes fade-out-status {
|
||||
0%, 50% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
0%,
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blinking-dots {
|
||||
0% {
|
||||
background-color: rgba($white-light, 1);
|
||||
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
|
||||
24px 0 0 0 rgba($white-light, 0.2);
|
||||
24px 0 0 0 rgba($white-light, 0.2);
|
||||
}
|
||||
|
||||
25% {
|
||||
background-color: rgba($white-light, 0.4);
|
||||
box-shadow: 12px 0 0 0 rgba($white-light, 2),
|
||||
24px 0 0 0 rgba($white-light, 0.2);
|
||||
24px 0 0 0 rgba($white-light, 0.2);
|
||||
}
|
||||
|
||||
75% {
|
||||
background-color: rgba($white-light, 0.4);
|
||||
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
|
||||
24px 0 0 0 rgba($white-light, 1);
|
||||
24px 0 0 0 rgba($white-light, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: rgba($white-light, 1);
|
||||
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
|
||||
24px 0 0 0 rgba($white-light, 0.2);
|
||||
24px 0 0 0 rgba($white-light, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blinking-scroll-button {
|
||||
0% { opacity: 0.2; }
|
||||
25% { opacity: 0.5; }
|
||||
50% { opacity: 0.7; }
|
||||
100% { opacity: 1; }
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
25% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.build-page {
|
||||
|
@ -125,12 +142,12 @@
|
|||
.btn-scroll.animate {
|
||||
.first-triangle {
|
||||
animation: blinking-scroll-button 1s ease infinite;
|
||||
animation-delay: .3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.second-triangle {
|
||||
animation: blinking-scroll-button 1s ease infinite;
|
||||
animation-delay: .2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.third-triangle {
|
||||
|
|
|
@ -78,6 +78,8 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
result.merge!(trace.to_h)
|
||||
end
|
||||
|
||||
result[:html] = result[:html].presence || 'No job log'
|
||||
|
||||
render json: result
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,8 +29,7 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
else
|
||||
return render('empty') unless can?(current_user, :create_wiki, @project)
|
||||
|
||||
@page = WikiPage.new(@project_wiki)
|
||||
@page.title = params[:id]
|
||||
@page = build_page(title: params[:id])
|
||||
|
||||
render 'edit'
|
||||
end
|
||||
|
@ -54,7 +53,7 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
else
|
||||
render 'edit'
|
||||
end
|
||||
rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
|
||||
rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
|
||||
@error = e
|
||||
render 'edit'
|
||||
end
|
||||
|
@ -70,6 +69,11 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
else
|
||||
render action: "edit"
|
||||
end
|
||||
rescue Gitlab::Git::Wiki::OperationError => e
|
||||
@page = build_page(wiki_params)
|
||||
@error = e
|
||||
|
||||
render 'edit'
|
||||
end
|
||||
|
||||
def history
|
||||
|
@ -94,6 +98,9 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
redirect_to project_wiki_path(@project, :home),
|
||||
status: 302,
|
||||
notice: "Page was successfully deleted"
|
||||
rescue Gitlab::Git::Wiki::OperationError => e
|
||||
@error = e
|
||||
render 'edit'
|
||||
end
|
||||
|
||||
def git_access
|
||||
|
@ -116,4 +123,10 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
def wiki_params
|
||||
params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
|
||||
end
|
||||
|
||||
def build_page(args)
|
||||
WikiPage.new(@project_wiki).tap do |page|
|
||||
page.update_attributes(args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -134,10 +134,8 @@ class GroupDescendantsFinder
|
|||
end
|
||||
|
||||
def direct_child_projects
|
||||
GroupProjectsFinder.new(group: parent_group,
|
||||
current_user: current_user,
|
||||
options: { only_owned: true },
|
||||
params: params).execute
|
||||
GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params)
|
||||
.execute
|
||||
end
|
||||
|
||||
# Finds all projects nested under `parent_group` or any of its descendant
|
||||
|
|
|
@ -28,7 +28,7 @@ module NavHelper
|
|||
end
|
||||
elsif current_path?('jobs#show')
|
||||
%w[page-gutter build-sidebar right-sidebar-expanded]
|
||||
elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access')
|
||||
elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access', 'destroy')
|
||||
%w[page-gutter wiki-sidebar right-sidebar-expanded]
|
||||
else
|
||||
[]
|
||||
|
|
|
@ -611,7 +611,7 @@ module Ci
|
|||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||
variables.append(key: 'CI', value: 'true')
|
||||
variables.append(key: 'GITLAB_CI', value: 'true')
|
||||
variables.append(key: 'GITLAB_FEATURES', value: project.namespace.features.join(','))
|
||||
variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(','))
|
||||
variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
|
||||
variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
|
||||
variables.append(key: 'CI_SERVER_REVISION', value: Gitlab::REVISION)
|
||||
|
|
|
@ -13,7 +13,7 @@ module Ci
|
|||
|
||||
has_many :builds
|
||||
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :projects, -> { auto_include(false) }, through: :runner_projects
|
||||
has_many :projects, through: :runner_projects
|
||||
|
||||
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ module Clusters
|
|||
belongs_to :user
|
||||
|
||||
has_many :cluster_projects, class_name: 'Clusters::Project'
|
||||
has_many :projects, -> { auto_include(false) }, through: :cluster_projects, class_name: '::Project'
|
||||
has_many :projects, through: :cluster_projects, class_name: '::Project'
|
||||
|
||||
# we force autosave to happen when we save `Cluster` model
|
||||
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
|
||||
|
|
|
@ -37,7 +37,20 @@ module GroupDescendant
|
|||
parent ||= preloaded.detect { |possible_parent| possible_parent.is_a?(Group) && possible_parent.id == child.parent_id }
|
||||
|
||||
if parent.nil? && !child.parent_id.nil?
|
||||
raise ArgumentError.new('parent was not preloaded')
|
||||
parent = child.parent
|
||||
|
||||
exception = ArgumentError.new <<~MSG
|
||||
parent: [GroupDescendant: #{parent.inspect}] was not preloaded for [#{child.inspect}]")
|
||||
This error is not user facing, but causes a +1 query.
|
||||
MSG
|
||||
extras = {
|
||||
parent: parent,
|
||||
child: child,
|
||||
preloaded: preloaded.map(&:full_path)
|
||||
}
|
||||
issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785'
|
||||
|
||||
Gitlab::Sentry.track_exception(exception, issue_url: issue_url, extra: extras)
|
||||
end
|
||||
|
||||
if parent.nil? && hierarchy_top.present?
|
||||
|
|
|
@ -48,7 +48,7 @@ module Issuable
|
|||
end
|
||||
|
||||
has_many :label_links, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :labels, -> { auto_include(false) }, through: :label_links
|
||||
has_many :labels, through: :label_links
|
||||
has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_one :metrics
|
||||
|
|
|
@ -102,7 +102,7 @@ module ResolvableDiscussion
|
|||
yield(notes_relation)
|
||||
|
||||
# Set the notes array to the updated notes
|
||||
@notes = notes_relation.fresh.auto_include(false).to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
@notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
self.class.memoized_values.each do |name|
|
||||
clear_memoization(name)
|
||||
|
|
|
@ -2,7 +2,7 @@ class DeployKey < Key
|
|||
include IgnorableColumn
|
||||
|
||||
has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :projects, -> { auto_include(false) }, through: :deploy_keys_projects
|
||||
has_many :projects, through: :deploy_keys_projects
|
||||
|
||||
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
|
||||
scope :are_public, -> { where(public: true) }
|
||||
|
|
|
@ -8,7 +8,7 @@ class DeployToken < ActiveRecord::Base
|
|||
default_value_for(:expires_at) { Forever.date }
|
||||
|
||||
has_many :project_deploy_tokens, inverse_of: :deploy_token
|
||||
has_many :projects, -> { auto_include(false) }, through: :project_deploy_tokens
|
||||
has_many :projects, through: :project_deploy_tokens
|
||||
|
||||
validate :ensure_at_least_one_scope
|
||||
before_save :ensure_token
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class ForkNetwork < ActiveRecord::Base
|
||||
belongs_to :root_project, class_name: 'Project'
|
||||
has_many :fork_network_members
|
||||
has_many :projects, -> { auto_include(false) }, through: :fork_network_members
|
||||
has_many :projects, through: :fork_network_members
|
||||
|
||||
after_create :add_root_as_member, if: :root_project
|
||||
|
||||
|
|
|
@ -12,9 +12,9 @@ class Group < Namespace
|
|||
|
||||
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
|
||||
alias_method :members, :group_members
|
||||
has_many :users, -> { auto_include(false) }, through: :group_members
|
||||
has_many :users, through: :group_members
|
||||
has_many :owners,
|
||||
-> { where(members: { access_level: Gitlab::Access::OWNER }).auto_include(false) },
|
||||
-> { where(members: { access_level: Gitlab::Access::OWNER }) },
|
||||
through: :group_members,
|
||||
source: :user
|
||||
|
||||
|
@ -23,7 +23,7 @@ class Group < Namespace
|
|||
|
||||
has_many :milestones
|
||||
has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :shared_projects, -> { auto_include(false) }, through: :project_group_links, source: :project
|
||||
has_many :shared_projects, through: :project_group_links, source: :project
|
||||
has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :labels, class_name: 'GroupLabel'
|
||||
has_many :variables, class_name: 'Ci::GroupVariable'
|
||||
|
|
|
@ -34,7 +34,7 @@ class Issue < ActiveRecord::Base
|
|||
dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :issue_assignees
|
||||
has_many :assignees, -> { auto_include(false) }, class_name: "User", through: :issue_assignees
|
||||
has_many :assignees, class_name: "User", through: :issue_assignees
|
||||
|
||||
validates :project, presence: true
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ class Label < ActiveRecord::Base
|
|||
has_many :lists, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :priorities, class_name: 'LabelPriority'
|
||||
has_many :label_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :issues, -> { auto_include(false) }, through: :label_links, source: :target, source_type: 'Issue'
|
||||
has_many :merge_requests, -> { auto_include(false) }, through: :label_links, source: :target, source_type: 'MergeRequest'
|
||||
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
|
||||
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
|
||||
|
||||
before_validation :strip_whitespace_from_title_and_color
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ class LfsObject < ActiveRecord::Base
|
|||
include ObjectStorage::BackgroundMove
|
||||
|
||||
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :projects, -> { auto_include(false) }, through: :lfs_objects_projects
|
||||
has_many :projects, through: :lfs_objects_projects
|
||||
|
||||
scope :with_files_stored_locally, -> { where(file_store: [nil, LfsObjectUploader::Store::LOCAL]) }
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class Milestone < ActiveRecord::Base
|
|||
belongs_to :group
|
||||
|
||||
has_many :issues
|
||||
has_many :labels, -> { distinct.reorder('labels.title').auto_include(false) }, through: :issues
|
||||
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
|
||||
has_many :merge_requests
|
||||
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
|
|
|
@ -248,10 +248,6 @@ class Namespace < ActiveRecord::Base
|
|||
all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
|
||||
end
|
||||
|
||||
def features
|
||||
[]
|
||||
end
|
||||
|
||||
def refresh_project_authorizations
|
||||
owner.refresh_authorized_projects
|
||||
end
|
||||
|
|
|
@ -138,11 +138,11 @@ class Project < ActiveRecord::Base
|
|||
has_one :packagist_service
|
||||
|
||||
# TODO: replace these relations with the fork network versions
|
||||
has_one :forked_project_link, foreign_key: "forked_to_project_id"
|
||||
has_one :forked_from_project, -> { auto_include(false) }, through: :forked_project_link
|
||||
has_one :forked_project_link, foreign_key: "forked_to_project_id"
|
||||
has_one :forked_from_project, through: :forked_project_link
|
||||
|
||||
has_many :forked_project_links, foreign_key: "forked_from_project_id"
|
||||
has_many :forks, -> { auto_include(false) }, through: :forked_project_links, source: :forked_to_project
|
||||
has_many :forks, through: :forked_project_links, source: :forked_to_project
|
||||
# TODO: replace these relations with the fork network versions
|
||||
|
||||
has_one :root_of_fork_network,
|
||||
|
@ -150,7 +150,7 @@ class Project < ActiveRecord::Base
|
|||
inverse_of: :root_project,
|
||||
class_name: 'ForkNetwork'
|
||||
has_one :fork_network_member
|
||||
has_one :fork_network, -> { auto_include(false) }, through: :fork_network_member
|
||||
has_one :fork_network, through: :fork_network_member
|
||||
|
||||
# Merge Requests for target project should be removed with it
|
||||
has_many :merge_requests, foreign_key: 'target_project_id'
|
||||
|
@ -167,27 +167,27 @@ class Project < ActiveRecord::Base
|
|||
has_many :protected_tags
|
||||
|
||||
has_many :project_authorizations
|
||||
has_many :authorized_users, -> { auto_include(false) }, through: :project_authorizations, source: :user, class_name: 'User'
|
||||
has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
|
||||
has_many :project_members, -> { where(requested_at: nil) },
|
||||
as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
alias_method :members, :project_members
|
||||
has_many :users, -> { auto_include(false) }, through: :project_members
|
||||
has_many :users, through: :project_members
|
||||
|
||||
has_many :requesters, -> { where.not(requested_at: nil) },
|
||||
as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :members_and_requesters, as: :source, class_name: 'ProjectMember'
|
||||
|
||||
has_many :deploy_keys_projects
|
||||
has_many :deploy_keys, -> { auto_include(false) }, through: :deploy_keys_projects
|
||||
has_many :deploy_keys, through: :deploy_keys_projects
|
||||
has_many :users_star_projects
|
||||
has_many :starrers, -> { auto_include(false) }, through: :users_star_projects, source: :user
|
||||
has_many :starrers, through: :users_star_projects, source: :user
|
||||
has_many :releases
|
||||
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :lfs_objects, -> { auto_include(false) }, through: :lfs_objects_projects
|
||||
has_many :lfs_objects, through: :lfs_objects_projects
|
||||
has_many :lfs_file_locks
|
||||
has_many :project_group_links
|
||||
has_many :invited_groups, -> { auto_include(false) }, through: :project_group_links, source: :group
|
||||
has_many :invited_groups, through: :project_group_links, source: :group
|
||||
has_many :pages_domains
|
||||
has_many :todos
|
||||
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
@ -199,7 +199,7 @@ class Project < ActiveRecord::Base
|
|||
has_one :statistics, class_name: 'ProjectStatistics'
|
||||
|
||||
has_one :cluster_project, class_name: 'Clusters::Project'
|
||||
has_many :clusters, -> { auto_include(false) }, through: :cluster_project, class_name: 'Clusters::Cluster'
|
||||
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
|
||||
|
||||
# Container repositories need to remove data from the container registry,
|
||||
# which is not managed by the DB. Hence we're still using dependent: :destroy
|
||||
|
@ -216,16 +216,16 @@ class Project < ActiveRecord::Base
|
|||
has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
|
||||
has_many :runner_projects, class_name: 'Ci::RunnerProject'
|
||||
has_many :runners, -> { auto_include(false) }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
|
||||
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
|
||||
has_many :variables, class_name: 'Ci::Variable'
|
||||
has_many :triggers, class_name: 'Ci::Trigger'
|
||||
has_many :environments
|
||||
has_many :deployments
|
||||
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
|
||||
has_many :project_deploy_tokens
|
||||
has_many :deploy_tokens, -> { auto_include(false) }, through: :project_deploy_tokens
|
||||
has_many :deploy_tokens, through: :project_deploy_tokens
|
||||
|
||||
has_many :active_runners, -> { active.auto_include(false) }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
|
||||
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
|
||||
|
||||
has_one :auto_devops, class_name: 'ProjectAutoDevops'
|
||||
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
|
||||
|
@ -1875,6 +1875,10 @@ class Project < ActiveRecord::Base
|
|||
memoized_results[cache_key]
|
||||
end
|
||||
|
||||
def licensed_features
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def storage
|
||||
|
|
|
@ -179,7 +179,11 @@ class ProjectWiki
|
|||
def commit_details(action, message = nil, title = nil)
|
||||
commit_message = message || default_message(action, title)
|
||||
|
||||
Gitlab::Git::Wiki::CommitDetails.new(@user.name, @user.email, commit_message)
|
||||
Gitlab::Git::Wiki::CommitDetails.new(@user.id,
|
||||
@user.username,
|
||||
@user.name,
|
||||
@user.email,
|
||||
commit_message)
|
||||
end
|
||||
|
||||
def default_message(action, title)
|
||||
|
|
|
@ -22,7 +22,7 @@ class Todo < ActiveRecord::Base
|
|||
belongs_to :author, class_name: "User"
|
||||
belongs_to :note
|
||||
belongs_to :project
|
||||
belongs_to :target, -> { auto_include(false) }, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
belongs_to :target, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
belongs_to :user
|
||||
|
||||
delegate :name, :email, to: :author, prefix: true, allow_nil: true
|
||||
|
|
|
@ -96,23 +96,23 @@ class User < ActiveRecord::Base
|
|||
# Groups
|
||||
has_many :members
|
||||
has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
|
||||
has_many :groups, -> { auto_include(false) }, through: :group_members
|
||||
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }).auto_include(false) }, through: :group_members, source: :group
|
||||
has_many :masters_groups, -> { where(members: { access_level: Gitlab::Access::MASTER }).auto_include(false) }, through: :group_members, source: :group
|
||||
has_many :groups, through: :group_members
|
||||
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
|
||||
has_many :masters_groups, -> { where(members: { access_level: Gitlab::Access::MASTER }) }, through: :group_members, source: :group
|
||||
|
||||
# Projects
|
||||
has_many :groups_projects, -> { auto_include(false) }, through: :groups, source: :projects
|
||||
has_many :personal_projects, -> { auto_include(false) }, through: :namespace, source: :projects
|
||||
has_many :groups_projects, through: :groups, source: :projects
|
||||
has_many :personal_projects, through: :namespace, source: :projects
|
||||
has_many :project_members, -> { where(requested_at: nil) }
|
||||
has_many :projects, -> { auto_include(false) }, through: :project_members
|
||||
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
|
||||
has_many :projects, through: :project_members
|
||||
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
|
||||
has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :starred_projects, -> { auto_include(false) }, through: :users_star_projects, source: :project
|
||||
has_many :starred_projects, through: :users_star_projects, source: :project
|
||||
has_many :project_authorizations
|
||||
has_many :authorized_projects, -> { auto_include(false) }, through: :project_authorizations, source: :project
|
||||
has_many :authorized_projects, through: :project_authorizations, source: :project
|
||||
|
||||
has_many :user_interacted_projects
|
||||
has_many :project_interactions, -> { auto_include(false) }, through: :user_interacted_projects, source: :project, class_name: 'Project'
|
||||
has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
|
||||
|
||||
has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
@ -132,7 +132,7 @@ class User < ActiveRecord::Base
|
|||
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :issue_assignees
|
||||
has_many :assigned_issues, -> { auto_include(false) }, class_name: "Issue", through: :issue_assignees, source: :issue
|
||||
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
|
||||
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :custom_attributes, class_name: 'UserCustomAttribute'
|
||||
|
|
|
@ -265,6 +265,15 @@ class WikiPage
|
|||
title.present? && self.class.unhyphenize(@page.url_path) != title
|
||||
end
|
||||
|
||||
# Updates the current @attributes hash by merging a hash of params
|
||||
def update_attributes(attrs)
|
||||
attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
|
||||
|
||||
attrs.slice!(:content, :format, :message, :title)
|
||||
|
||||
@attributes.merge!(attrs)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Process and format the title based on the user input.
|
||||
|
@ -290,15 +299,6 @@ class WikiPage
|
|||
File.join(components)
|
||||
end
|
||||
|
||||
# Updates the current @attributes hash by merging a hash of params
|
||||
def update_attributes(attrs)
|
||||
attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
|
||||
|
||||
attrs.slice!(:content, :format, :message, :title)
|
||||
|
||||
@attributes.merge!(attrs)
|
||||
end
|
||||
|
||||
def set_attributes
|
||||
attributes[:slug] = @page.url_path
|
||||
attributes[:title] = @page.title
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
module Ci
|
||||
class BuildPresenter < Gitlab::View::Presenter::Delegated
|
||||
CALLOUT_FAILURE_MESSAGES = {
|
||||
unknown_failure: 'There is an unknown failure, please try again',
|
||||
script_failure: 'There has been a script failure. Check the job log for more information',
|
||||
api_failure: 'There has been an API failure, please try again',
|
||||
stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
|
||||
runner_system_failure: 'There has been a runner system failure, please try again',
|
||||
missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
|
||||
}.freeze
|
||||
|
||||
presents :build
|
||||
|
||||
def erased_by_user?
|
||||
|
@ -35,6 +44,14 @@ module Ci
|
|||
"#{subject.name} - #{detailed_status.status_tooltip}"
|
||||
end
|
||||
|
||||
def callout_failure_message
|
||||
CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym]
|
||||
end
|
||||
|
||||
def recoverable?
|
||||
failed? && !unrecoverable?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tooltip_for_badge
|
||||
|
@ -44,5 +61,9 @@ module Ci
|
|||
def detailed_status
|
||||
@detailed_status ||= subject.detailed_status(user)
|
||||
end
|
||||
|
||||
def unrecoverable?
|
||||
script_failure? || missing_dependency_failure?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,8 @@ class JobEntity < Grape::Entity
|
|||
expose :created_at
|
||||
expose :updated_at
|
||||
expose :detailed_status, as: :status, with: StatusEntity
|
||||
expose :callout_message, if: -> (*) { failed? }
|
||||
expose :recoverable, if: -> (*) { failed? }
|
||||
|
||||
private
|
||||
|
||||
|
@ -50,4 +52,20 @@ class JobEntity < Grape::Entity
|
|||
def path_to(route, build)
|
||||
send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
def failed?
|
||||
build.failed?
|
||||
end
|
||||
|
||||
def callout_message
|
||||
build_presenter.callout_failure_message
|
||||
end
|
||||
|
||||
def recoverable
|
||||
build_presenter.recoverable?
|
||||
end
|
||||
|
||||
def build_presenter
|
||||
@build_presenter ||= build.present
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
builds.auto_include(false).find do |build|
|
||||
builds.find do |build|
|
||||
next unless runner.can_pick?(build)
|
||||
|
||||
begin
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<%= yield -%>
|
||||
|
||||
---
|
||||
-- <%# signature marker %>
|
||||
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%= yield -%>
|
||||
|
||||
---
|
||||
-- <%# signature marker %>
|
||||
<% if @target_url -%>
|
||||
<% if @reply_by_email -%>
|
||||
<%= "Reply to this email directly or view it on GitLab: #{@target_url}" -%>
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
|
||||
.sidebar-container
|
||||
.blocks-container
|
||||
.block
|
||||
%strong.inline.prepend-top-8
|
||||
= @build.name
|
||||
- if can?(current_user, :update_build, @build) && @build.retryable?
|
||||
= link_to "Retry", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'js-retry-button pull-right btn btn-inverted-secondary btn-retry visible-md-block visible-lg-block', method: :post
|
||||
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' }
|
||||
= icon('angle-double-right')
|
||||
|
||||
#js-details-block-vue
|
||||
#js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
|
||||
|
||||
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
|
||||
.block
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
- if can_admin_issue?
|
||||
= icon("spinner spin", class: "block-loading")
|
||||
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
|
||||
.value.issuable-show-labels
|
||||
.value.issuable-show-labels.dont-hide
|
||||
%span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" }
|
||||
None
|
||||
%a{ href: "#",
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
|
||||
- if can_edit_issuable
|
||||
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
|
||||
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
|
||||
.value.issuable-show-labels.dont-hide.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
|
||||
- if selected_labels.any?
|
||||
- selected_labels.each do |label|
|
||||
= link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Keep current labels visible when editing them in the sidebar
|
||||
merge_request:
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/43617-mailsig.yml
Normal file
5
changelogs/unreleased/43617-mailsig.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use RFC 3676 mail signature delimiters
|
||||
merge_request: 17979
|
||||
author: Enrico Scholz
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show shared projects on group page
|
||||
merge_request: 18390
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Add Goldiloader to fix N+1 issues when calculating email recipients
|
||||
merge_request:
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Triggering custom hooks by Wiki UI edit
|
||||
merge_request: 18251
|
||||
author:
|
||||
type: fixed
|
|
@ -17,7 +17,11 @@ codequality:
|
|||
- docker:stable-dind
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||
- docker run
|
||||
--env SOURCE_CODE="$PWD"
|
||||
--volume "$PWD":/code
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
```
|
||||
|
|
|
@ -42,9 +42,9 @@ dast:
|
|||
allow_failure: true
|
||||
script:
|
||||
- mkdir /zap/wrk/
|
||||
- /zap/zap-baseline.py -J gl-dast-report.json -t $website \
|
||||
--auth-url $login_url \
|
||||
--auth-username "john.doe@example.com" \
|
||||
- /zap/zap-baseline.py -J gl-dast-report.json -t $website
|
||||
--auth-url $login_url
|
||||
--auth-username "john.doe@example.com"
|
||||
--auth-password "john-doe-password" || true
|
||||
- cp /zap/wrk/gl-dast-report.json .
|
||||
artifacts:
|
||||
|
|
|
@ -75,7 +75,7 @@ cancel the job, retry it, or erase the job trace.
|
|||
|
||||
## Seeing the failure reason for jobs
|
||||
|
||||
> [Introduced][ce-5742] in GitLab 10.7.
|
||||
> [Introduced][ce-17782] in GitLab 10.7.
|
||||
|
||||
When a pipeline fails or is allowed to fail, there are several places where you
|
||||
can quickly check the reason it failed:
|
||||
|
@ -88,6 +88,8 @@ In any case, if you hover over the failed job you can see the reason it failed.
|
|||
|
||||
![Pipeline detail](img/job_failure_reason.png)
|
||||
|
||||
From [GitLab 10.8][ce-17814] you can also see the reason it failed on the Job detail page.
|
||||
|
||||
## Pipeline graphs
|
||||
|
||||
> [Introduced][ce-5742] in GitLab 8.11.
|
||||
|
@ -279,4 +281,5 @@ runners will not use regular runners, they must be tagged accordingly.
|
|||
[ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931
|
||||
[ce-9760]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760
|
||||
[ce-17782]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782
|
||||
[ce-17814]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17814
|
||||
[regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
|
||||
|
|
|
@ -133,11 +133,19 @@ roughly be as follows:
|
|||
1. Release B:
|
||||
1. Deploy code so that the application starts using the new column and stops
|
||||
scheduling jobs for newly created data.
|
||||
1. In a post-deployment migration you'll need to ensure no jobs remain. To do
|
||||
so you can use `Gitlab::BackgroundMigration.steal` to process any remaining
|
||||
jobs before continuing.
|
||||
1. In a post-deployment migration you'll need to ensure no jobs remain.
|
||||
1. Use `Gitlab::BackgroundMigration.steal` to process any remaining
|
||||
jobs in Sidekiq.
|
||||
1. Reschedule the migration to be run directly (i.e. not through Sidekiq)
|
||||
on any rows that weren't migrated by Sidekiq. This can happen if, for
|
||||
instance, Sidekiq received a SIGKILL, or if a particular batch failed
|
||||
enough times to be marked as dead.
|
||||
1. Remove the old column.
|
||||
|
||||
This may also require a bump to the [import/export version][import-export], if
|
||||
importing a project from a prior version of GitLab requires the data to be in
|
||||
the new format.
|
||||
|
||||
## Example
|
||||
|
||||
To explain all this, let's use the following example: the table `services` has a
|
||||
|
@ -296,3 +304,4 @@ for more details.
|
|||
[migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
|
||||
[issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
|
||||
[reliable-sidekiq]: https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
|
||||
[import-export]: ../user/project/settings/import_export.md
|
||||
|
|
|
@ -2,10 +2,11 @@ module Gitlab
|
|||
module Git
|
||||
class Wiki
|
||||
DuplicatePageError = Class.new(StandardError)
|
||||
OperationError = Class.new(StandardError)
|
||||
|
||||
CommitDetails = Struct.new(:name, :email, :message) do
|
||||
CommitDetails = Struct.new(:user_id, :username, :name, :email, :message) do
|
||||
def to_h
|
||||
{ name: name, email: email, message: message }
|
||||
{ user_id: user_id, username: username, name: name, email: email, message: message }
|
||||
end
|
||||
end
|
||||
PageBlob = Struct.new(:name)
|
||||
|
@ -140,6 +141,10 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def gollum_wiki
|
||||
@gollum_wiki ||= Gollum::Wiki.new(@repository.path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# options:
|
||||
|
@ -158,10 +163,6 @@ module Gitlab
|
|||
offset: options[:offset])
|
||||
end
|
||||
|
||||
def gollum_wiki
|
||||
@gollum_wiki ||= Gollum::Wiki.new(@repository.path)
|
||||
end
|
||||
|
||||
def gollum_page_by_path(page_path)
|
||||
page_name = Gollum::Page.canonicalize_filename(page_path)
|
||||
page_dir = File.split(page_path).first
|
||||
|
@ -201,12 +202,12 @@ module Gitlab
|
|||
assert_type!(format, Symbol)
|
||||
assert_type!(commit_details, CommitDetails)
|
||||
|
||||
filename = File.basename(name)
|
||||
dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir
|
||||
with_committer_with_hooks(commit_details) do |committer|
|
||||
filename = File.basename(name)
|
||||
dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir
|
||||
|
||||
gollum_wiki.write_page(filename, format, content, commit_details.to_h, dir)
|
||||
|
||||
nil
|
||||
gollum_wiki.write_page(filename, format, content, { committer: committer }, dir)
|
||||
end
|
||||
rescue Gollum::DuplicatePageError => e
|
||||
raise Gitlab::Git::Wiki::DuplicatePageError, e.message
|
||||
end
|
||||
|
@ -214,24 +215,23 @@ module Gitlab
|
|||
def gollum_delete_page(page_path, commit_details)
|
||||
assert_type!(commit_details, CommitDetails)
|
||||
|
||||
gollum_wiki.delete_page(gollum_page_by_path(page_path), commit_details.to_h)
|
||||
nil
|
||||
with_committer_with_hooks(commit_details) do |committer|
|
||||
gollum_wiki.delete_page(gollum_page_by_path(page_path), committer: committer)
|
||||
end
|
||||
end
|
||||
|
||||
def gollum_update_page(page_path, title, format, content, commit_details)
|
||||
assert_type!(format, Symbol)
|
||||
assert_type!(commit_details, CommitDetails)
|
||||
|
||||
page = gollum_page_by_path(page_path)
|
||||
committer = Gollum::Committer.new(page.wiki, commit_details.to_h)
|
||||
|
||||
# Instead of performing two renames if the title has changed,
|
||||
# the update_page will only update the format and content and
|
||||
# the rename_page will do anything related to moving/renaming
|
||||
gollum_wiki.update_page(page, page.name, format, content, committer: committer)
|
||||
gollum_wiki.rename_page(page, title, committer: committer)
|
||||
committer.commit
|
||||
nil
|
||||
with_committer_with_hooks(commit_details) do |committer|
|
||||
page = gollum_page_by_path(page_path)
|
||||
# Instead of performing two renames if the title has changed,
|
||||
# the update_page will only update the format and content and
|
||||
# the rename_page will do anything related to moving/renaming
|
||||
gollum_wiki.update_page(page, page.name, format, content, committer: committer)
|
||||
gollum_wiki.rename_page(page, title, committer: committer)
|
||||
end
|
||||
end
|
||||
|
||||
def gollum_find_page(title:, version: nil, dir: nil)
|
||||
|
@ -288,6 +288,20 @@ module Gitlab
|
|||
Gitlab::Git::WikiPage.new(wiki_page, version)
|
||||
end
|
||||
end
|
||||
|
||||
def committer_with_hooks(commit_details)
|
||||
Gitlab::Wiki::CommitterWithHooks.new(self, commit_details.to_h)
|
||||
end
|
||||
|
||||
def with_committer_with_hooks(commit_details, &block)
|
||||
committer = committer_with_hooks(commit_details)
|
||||
|
||||
yield committer
|
||||
|
||||
committer.commit
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -200,6 +200,8 @@ module Gitlab
|
|||
|
||||
def gitaly_commit_details(commit_details)
|
||||
Gitaly::WikiCommitDetails.new(
|
||||
user_id: commit_details.user_id,
|
||||
user_name: encode_binary(commit_details.username),
|
||||
name: encode_binary(commit_details.name),
|
||||
email: encode_binary(commit_details.email),
|
||||
message: encode_binary(commit_details.message)
|
||||
|
|
|
@ -2,10 +2,14 @@ module Gitlab
|
|||
module GlId
|
||||
def self.gl_id(user)
|
||||
if user.present?
|
||||
"user-#{user.id}"
|
||||
gl_id_from_id_value(user.id)
|
||||
else
|
||||
""
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def self.gl_id_from_id_value(id)
|
||||
"user-#{id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,10 @@ module Gitlab
|
|||
subject
|
||||
end
|
||||
|
||||
def present(**attributes)
|
||||
self
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def presenter?
|
||||
true
|
||||
|
|
39
lib/gitlab/wiki/committer_with_hooks.rb
Normal file
39
lib/gitlab/wiki/committer_with_hooks.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
module Gitlab
|
||||
module Wiki
|
||||
class CommitterWithHooks < Gollum::Committer
|
||||
attr_reader :gl_wiki
|
||||
|
||||
def initialize(gl_wiki, options = {})
|
||||
@gl_wiki = gl_wiki
|
||||
super(gl_wiki.gollum_wiki, options)
|
||||
end
|
||||
|
||||
def commit
|
||||
result = Gitlab::Git::OperationService.new(git_user, gl_wiki.repository).with_branch(
|
||||
@wiki.ref,
|
||||
start_branch_name: @wiki.ref
|
||||
) do |start_commit|
|
||||
super(false)
|
||||
end
|
||||
|
||||
result[:newrev]
|
||||
rescue Gitlab::Git::HooksService::PreReceiveError => e
|
||||
message = "Custom Hook failed: #{e.message}"
|
||||
raise Gitlab::Git::Wiki::OperationError, message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def git_user
|
||||
@git_user ||= Gitlab::Git::User.new(@options[:username],
|
||||
@options[:name],
|
||||
@options[:email],
|
||||
gitlab_id)
|
||||
end
|
||||
|
||||
def gitlab_id
|
||||
Gitlab::GlId.gl_id_from_id_value(@options[:user_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,45 +0,0 @@
|
|||
require 'gitlab/styles/rubocop/model_helpers'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
class HasManyThroughScope < RuboCop::Cop::Cop
|
||||
include ::Gitlab::Styles::Rubocop::ModelHelpers
|
||||
|
||||
MSG = 'Always provide an explicit scope calling auto_include(false) when using has_many :through'.freeze
|
||||
|
||||
def_node_search :through?, <<~PATTERN
|
||||
(pair (sym :through) _)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :has_many_through?, <<~PATTERN
|
||||
(send nil? :has_many ... #through?)
|
||||
PATTERN
|
||||
|
||||
def_node_search :disables_auto_include?, <<~PATTERN
|
||||
(send _ :auto_include false)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :scope_disables_auto_include?, <<~PATTERN
|
||||
(block (send nil? :lambda) _ #disables_auto_include?)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless in_model?(node)
|
||||
return unless has_many_through?(node)
|
||||
|
||||
target = node
|
||||
scope_argument = node.children[3]
|
||||
|
||||
if scope_argument.children[0].children.last == :lambda
|
||||
return if scope_disables_auto_include?(scope_argument)
|
||||
|
||||
target = scope_argument
|
||||
end
|
||||
|
||||
add_offense(target, location: :expression)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,7 @@
|
|||
# rubocop:disable Naming/FileName
|
||||
require_relative 'cop/gitlab/has_many_through_scope'
|
||||
require_relative 'cop/gitlab/httparty'
|
||||
require_relative 'cop/gitlab/module_with_instance_variables'
|
||||
require_relative 'cop/gitlab/predicate_memoization'
|
||||
require_relative 'cop/gitlab/httparty'
|
||||
require_relative 'cop/include_sidekiq_worker'
|
||||
require_relative 'cop/avoid_return_from_blocks'
|
||||
require_relative 'cop/avoid_break_from_strong_memoize'
|
||||
|
|
|
@ -190,7 +190,10 @@ describe Projects::JobsController do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['id']).to eq job.id
|
||||
expect(json_response['status']).to eq job.status
|
||||
expect(json_response['html']).to be_nil
|
||||
end
|
||||
|
||||
it 'returns no job log message' do
|
||||
expect(json_response['html']).to eq('No job log')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -243,5 +243,10 @@ FactoryBot.define do
|
|||
failed
|
||||
failure_reason 1
|
||||
end
|
||||
|
||||
trait :api_failure do
|
||||
failed
|
||||
failure_reason 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -237,6 +237,22 @@ describe 'Issue Boards', :js do
|
|||
end
|
||||
|
||||
context 'labels' do
|
||||
it 'shows current labels when editing' do
|
||||
click_card(card)
|
||||
|
||||
page.within('.labels') do
|
||||
click_link 'Edit'
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within('.value') do
|
||||
expect(page).to have_selector('.label', count: 2)
|
||||
expect(page).to have_content(development.title)
|
||||
expect(page).to have_content(stretch.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds a single label' do
|
||||
click_card(card)
|
||||
|
||||
|
@ -296,7 +312,9 @@ describe 'Issue Boards', :js do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
click_link stretch.title
|
||||
within('.dropdown-menu-labels') do
|
||||
click_link stretch.title
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ feature 'Issue Sidebar' do
|
|||
|
||||
let(:group) { create(:group, :nested) }
|
||||
let(:project) { create(:project, :public, namespace: group) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
let!(:user) { create(:user)}
|
||||
let!(:label) { create(:label, project: project, title: 'bug') }
|
||||
let(:issue) { create(:labeled_issue, project: project, labels: [label]) }
|
||||
let!(:xss_label) { create(:label, project: project, title: '<script>alert("xss");</script>') }
|
||||
|
||||
before do
|
||||
|
@ -112,11 +112,18 @@ feature 'Issue Sidebar' do
|
|||
|
||||
context 'editing issue labels', :js do
|
||||
before do
|
||||
issue.update_attributes(labels: [label])
|
||||
page.within('.block.labels') do
|
||||
find('.edit-link').click
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the current set of labels' do
|
||||
page.within('.issuable-show-labels') do
|
||||
expect(page).to have_content label.title
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows option to create a project label' do
|
||||
page.within('.block.labels') do
|
||||
expect(page).to have_content 'Create project'
|
||||
|
|
|
@ -491,16 +491,18 @@ feature 'Jobs' do
|
|||
end
|
||||
end
|
||||
|
||||
describe "POST /:project/jobs/:id/retry" do
|
||||
describe "POST /:project/jobs/:id/retry", :js do
|
||||
context "Job from project", :js do
|
||||
before do
|
||||
job.run!
|
||||
job.cancel!
|
||||
visit project_job_path(project, job)
|
||||
find('.js-cancel-job').click()
|
||||
wait_for_requests
|
||||
|
||||
find('.js-retry-button').click
|
||||
end
|
||||
|
||||
it 'shows the right status and buttons', :js do
|
||||
it 'shows the right status and buttons' do
|
||||
page.within('aside.right-sidebar') do
|
||||
expect(page).to have_content 'Cancel'
|
||||
end
|
||||
|
|
|
@ -35,15 +35,6 @@ describe GroupDescendantsFinder do
|
|||
expect(finder.execute).to contain_exactly(project)
|
||||
end
|
||||
|
||||
it 'does not include projects shared with the group' do
|
||||
project = create(:project, namespace: group)
|
||||
other_project = create(:project)
|
||||
other_project.project_group_links.create(group: group,
|
||||
group_access: ProjectGroupLink::MASTER)
|
||||
|
||||
expect(finder.execute).to contain_exactly(project)
|
||||
end
|
||||
|
||||
context 'when archived is `true`' do
|
||||
let(:params) { { archived: 'true' } }
|
||||
|
||||
|
|
|
@ -36,14 +36,28 @@ describe('Job details header', () => {
|
|||
},
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
vm = mountComponent(HeaderComponent, props);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('job reason', () => {
|
||||
it('should not render the reason when reason is absent', () => {
|
||||
vm = mountComponent(HeaderComponent, props);
|
||||
|
||||
expect(vm.shouldRenderReason).toBe(false);
|
||||
});
|
||||
|
||||
it('should render the reason when reason is present', () => {
|
||||
props.job.callout_message = 'There is an unknown failure, please try again';
|
||||
|
||||
vm = mountComponent(HeaderComponent, props);
|
||||
|
||||
expect(vm.shouldRenderReason).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('triggered job', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(HeaderComponent, props);
|
||||
|
@ -51,14 +65,17 @@ describe('Job details header', () => {
|
|||
|
||||
it('should render provided job information', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
|
||||
vm.$el
|
||||
.querySelector('.header-main-content')
|
||||
.textContent.replace(/\s+/g, ' ')
|
||||
.trim(),
|
||||
).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
|
||||
});
|
||||
|
||||
it('should render new issue link', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.js-new-issue').getAttribute('href'),
|
||||
).toEqual(props.job.new_issue_path);
|
||||
expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
|
||||
props.job.new_issue_path,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -68,7 +85,10 @@ describe('Job details header', () => {
|
|||
vm = mountComponent(HeaderComponent, props);
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
|
||||
vm.$el
|
||||
.querySelector('.header-main-content')
|
||||
.textContent.replace(/\s+/g, ' ')
|
||||
.trim(),
|
||||
).toEqual('failed Job #123 created 3 weeks ago by Foo');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,10 +31,25 @@ describe('Sidebar details block', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("when user can't retry", () => {
|
||||
it('should not render a retry button', () => {
|
||||
vm = new SidebarComponent({
|
||||
propsData: {
|
||||
job: {},
|
||||
canUserRetry: false,
|
||||
isLoading: true,
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm = new SidebarComponent({
|
||||
propsData: {
|
||||
job,
|
||||
canUserRetry: true,
|
||||
isLoading: false,
|
||||
},
|
||||
}).$mount();
|
||||
|
@ -42,7 +57,9 @@ describe('Sidebar details block', () => {
|
|||
|
||||
describe('actions', () => {
|
||||
it('should render link to new issue', () => {
|
||||
expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(job.new_issue_path);
|
||||
expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
|
||||
job.new_issue_path,
|
||||
);
|
||||
expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue');
|
||||
});
|
||||
|
||||
|
@ -57,43 +74,35 @@ describe('Sidebar details block', () => {
|
|||
|
||||
describe('information', () => {
|
||||
it('should render merge request link', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-mr')),
|
||||
).toEqual('Merge Request: !2');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-mr'))).toEqual('Merge Request: !2');
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.js-job-mr a').getAttribute('href'),
|
||||
).toEqual(job.merge_request.path);
|
||||
expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual(
|
||||
job.merge_request.path,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render job duration', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-duration')),
|
||||
).toEqual('Duration: 6 seconds');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-duration'))).toEqual(
|
||||
'Duration: 6 seconds',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render erased date', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-erased')),
|
||||
).toEqual('Erased: 3 weeks ago');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-erased'))).toEqual('Erased: 3 weeks ago');
|
||||
});
|
||||
|
||||
it('should render finished date', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-finished')),
|
||||
).toEqual('Finished: 3 weeks ago');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-finished'))).toEqual(
|
||||
'Finished: 3 weeks ago',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render queued date', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-queued')),
|
||||
).toEqual('Queued: 9 seconds');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-queued'))).toEqual('Queued: 9 seconds');
|
||||
});
|
||||
|
||||
it('should render runner ID', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-runner')),
|
||||
).toEqual('Runner: #1');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual('Runner: #1');
|
||||
});
|
||||
|
||||
it('should render timeout information', () => {
|
||||
|
@ -103,15 +112,11 @@ describe('Sidebar details block', () => {
|
|||
});
|
||||
|
||||
it('should render coverage', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-coverage')),
|
||||
).toEqual('Coverage: 20%');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-coverage'))).toEqual('Coverage: 20%');
|
||||
});
|
||||
|
||||
it('should render tags', () => {
|
||||
expect(
|
||||
trimWhitespace(vm.$el.querySelector('.js-job-tags')),
|
||||
).toEqual('Tags: tag');
|
||||
expect(trimWhitespace(vm.$el.querySelector('.js-job-tags'))).toEqual('Tags: tag');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
45
spec/javascripts/vue_shared/components/callout_spec.js
Normal file
45
spec/javascripts/vue_shared/components/callout_spec.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import Vue from 'vue';
|
||||
import callout from '~/vue_shared/components/callout.vue';
|
||||
import createComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Callout Component', () => {
|
||||
let CalloutComponent;
|
||||
let vm;
|
||||
const exampleMessage = 'This is a callout message!';
|
||||
|
||||
beforeEach(() => {
|
||||
CalloutComponent = Vue.extend(callout);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('should render the appropriate variant of callout', () => {
|
||||
vm = createComponent(CalloutComponent, {
|
||||
category: 'info',
|
||||
message: exampleMessage,
|
||||
});
|
||||
|
||||
expect(vm.$el.getAttribute('class')).toEqual('bs-callout bs-callout-info');
|
||||
|
||||
expect(vm.$el.tagName).toEqual('DIV');
|
||||
});
|
||||
|
||||
it('should render accessibility attributes', () => {
|
||||
vm = createComponent(CalloutComponent, {
|
||||
message: exampleMessage,
|
||||
});
|
||||
|
||||
expect(vm.$el.getAttribute('role')).toEqual('alert');
|
||||
expect(vm.$el.getAttribute('aria-live')).toEqual('assertive');
|
||||
});
|
||||
|
||||
it('should render the provided message', () => {
|
||||
vm = createComponent(CalloutComponent, {
|
||||
message: exampleMessage,
|
||||
});
|
||||
|
||||
expect(vm.$el.innerHTML.trim()).toEqual(exampleMessage);
|
||||
});
|
||||
});
|
|
@ -30,7 +30,7 @@ describe Gitlab::Git::Wiki do
|
|||
end
|
||||
|
||||
def commit_details(name)
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "created page #{name}")
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "created page #{name}")
|
||||
end
|
||||
|
||||
def destroy_page(title, dir = '')
|
||||
|
|
|
@ -48,4 +48,11 @@ describe Gitlab::View::Presenter::Base do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#present' do
|
||||
it 'returns self' do
|
||||
presenter = presenter_class.new(build_stubbed(:project))
|
||||
expect(presenter.present).to eq(presenter)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
154
spec/lib/gitlab/wiki/committer_with_hooks_spec.rb
Normal file
154
spec/lib/gitlab/wiki/committer_with_hooks_spec.rb
Normal file
|
@ -0,0 +1,154 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Wiki::CommitterWithHooks, seed_helper: true do
|
||||
shared_examples 'calling wiki hooks' do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { project.owner }
|
||||
let(:project_wiki) { ProjectWiki.new(project, user) }
|
||||
let(:wiki) { project_wiki.wiki }
|
||||
let(:options) do
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
message: 'commit message'
|
||||
}
|
||||
end
|
||||
|
||||
subject { described_class.new(wiki, options) }
|
||||
|
||||
before do
|
||||
project_wiki.create_page('home', 'test content')
|
||||
end
|
||||
|
||||
shared_examples 'failing pre-receive hook' do
|
||||
before do
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, ''])
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update')
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
|
||||
end
|
||||
|
||||
it 'raises exception' do
|
||||
expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
|
||||
end
|
||||
|
||||
it 'does not create a new commit inside the repository' do
|
||||
current_rev = find_current_rev
|
||||
|
||||
expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
|
||||
|
||||
expect(current_rev).to eq find_current_rev
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'failing update hook' do
|
||||
before do
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, ''])
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
|
||||
end
|
||||
|
||||
it 'raises exception' do
|
||||
expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
|
||||
end
|
||||
|
||||
it 'does not create a new commit inside the repository' do
|
||||
current_rev = find_current_rev
|
||||
|
||||
expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
|
||||
|
||||
expect(current_rev).to eq find_current_rev
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'failing post-receive hook' do
|
||||
before do
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, ''])
|
||||
expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, ''])
|
||||
end
|
||||
|
||||
it 'does not raise exception' do
|
||||
expect { subject.commit }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'creates the commit' do
|
||||
current_rev = find_current_rev
|
||||
|
||||
subject.commit
|
||||
|
||||
expect(current_rev).not_to eq find_current_rev
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'when hooks call succceeds' do
|
||||
let(:hook) { double(:hook) }
|
||||
|
||||
it 'calls the three hooks' do
|
||||
expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
|
||||
expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil])
|
||||
|
||||
subject.commit
|
||||
end
|
||||
|
||||
it 'creates the commit' do
|
||||
current_rev = find_current_rev
|
||||
|
||||
subject.commit
|
||||
|
||||
expect(current_rev).not_to eq find_current_rev
|
||||
end
|
||||
end
|
||||
|
||||
context 'when creating a page' do
|
||||
before do
|
||||
project_wiki.create_page('index', 'test content')
|
||||
end
|
||||
|
||||
it_behaves_like 'failing pre-receive hook'
|
||||
it_behaves_like 'failing update hook'
|
||||
it_behaves_like 'failing post-receive hook'
|
||||
it_behaves_like 'when hooks call succceeds'
|
||||
end
|
||||
|
||||
context 'when updating a page' do
|
||||
before do
|
||||
project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown)
|
||||
end
|
||||
|
||||
it_behaves_like 'failing pre-receive hook'
|
||||
it_behaves_like 'failing update hook'
|
||||
it_behaves_like 'failing post-receive hook'
|
||||
it_behaves_like 'when hooks call succceeds'
|
||||
end
|
||||
|
||||
context 'when deleting a page' do
|
||||
before do
|
||||
project_wiki.delete_page(find_page('home'))
|
||||
end
|
||||
|
||||
it_behaves_like 'failing pre-receive hook'
|
||||
it_behaves_like 'failing update hook'
|
||||
it_behaves_like 'failing post-receive hook'
|
||||
it_behaves_like 'when hooks call succceeds'
|
||||
end
|
||||
|
||||
def find_current_rev
|
||||
wiki.gollum_wiki.repo.commits.first&.sha
|
||||
end
|
||||
|
||||
def find_page(name)
|
||||
wiki.page(title: name)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Uncomment once Gitaly updates the ruby vendor code
|
||||
# context 'when Gitaly is enabled' do
|
||||
# it_behaves_like 'calling wiki hooks'
|
||||
# end
|
||||
|
||||
context 'when Gitaly is disabled', :skip_gitaly_mock do
|
||||
it_behaves_like 'calling wiki hooks'
|
||||
end
|
||||
end
|
|
@ -1472,7 +1472,7 @@ describe Ci::Build do
|
|||
{ key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
|
||||
{ key: 'CI', value: 'true', public: true },
|
||||
{ key: 'GITLAB_CI', value: 'true', public: true },
|
||||
{ key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true },
|
||||
{ key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true },
|
||||
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
|
||||
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
|
||||
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
|
||||
|
|
|
@ -79,9 +79,24 @@ describe GroupDescendant, :nested_groups do
|
|||
expect(described_class.build_hierarchy(groups)).to eq(expected_hierarchy)
|
||||
end
|
||||
|
||||
it 'tracks the exception when a parent was not preloaded' do
|
||||
expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
|
||||
|
||||
expect { GroupDescendant.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'recovers if a parent was not reloaded by querying for the parent' do
|
||||
expected_hierarchy = { parent => { subgroup => subsub_group } }
|
||||
|
||||
# this does not raise in production, so stubbing it here.
|
||||
allow(Gitlab::Sentry).to receive(:track_exception)
|
||||
|
||||
expect(GroupDescendant.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
|
||||
end
|
||||
|
||||
it 'raises an error if not all elements were preloaded' do
|
||||
expect { described_class.build_hierarchy([subsub_group]) }
|
||||
.to raise_error('parent was not preloaded')
|
||||
.to raise_error(/was not preloaded/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -377,7 +377,7 @@ describe ProjectWiki do
|
|||
end
|
||||
|
||||
def commit_details
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test commit")
|
||||
end
|
||||
|
||||
def create_page(name, content)
|
||||
|
|
|
@ -561,7 +561,7 @@ describe WikiPage do
|
|||
end
|
||||
|
||||
def commit_details
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test commit")
|
||||
end
|
||||
|
||||
def create_page(name, content)
|
||||
|
|
|
@ -217,4 +217,39 @@ describe Ci::BuildPresenter do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#callout_failure_message' do
|
||||
let(:build) { create(:ci_build, :failed, :script_failure) }
|
||||
|
||||
it 'returns a verbose failure reason' do
|
||||
description = subject.callout_failure_message
|
||||
expect(description).to eq('There has been a script failure. Check the job log for more information')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#recoverable?' do
|
||||
let(:build) { create(:ci_build, :failed, :script_failure) }
|
||||
|
||||
context 'when is a script or missing dependency failure' do
|
||||
let(:failure_reasons) { %w(script_failure missing_dependency_failure) }
|
||||
|
||||
it 'should return false' do
|
||||
failure_reasons.each do |failure_reason|
|
||||
build.update_attribute(:failure_reason, failure_reason)
|
||||
expect(presenter.recoverable?).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is any other failure type' do
|
||||
let(:failure_reasons) { %w(unknown_failure api_failure stuck_or_timeout_failure runner_system_failure) }
|
||||
|
||||
it 'should return true' do
|
||||
failure_reasons.each do |failure_reason|
|
||||
build.update_attribute(:failure_reason, failure_reason)
|
||||
expect(presenter.recoverable?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require 'rubocop'
|
||||
require 'rubocop/rspec/support'
|
||||
|
||||
require_relative '../../../../rubocop/cop/gitlab/has_many_through_scope'
|
||||
|
||||
describe RuboCop::Cop::Gitlab::HasManyThroughScope do # rubocop:disable RSpec/FilePath
|
||||
include CopHelper
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
context 'in a model file' do
|
||||
before do
|
||||
allow(cop).to receive(:in_model?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when the model does not use has_many :through' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses(<<-RUBY)
|
||||
class User < ActiveRecord::Base
|
||||
has_many :tags, source: 'UserTag'
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the model uses has_many :through' do
|
||||
context 'when the association has no scope defined' do
|
||||
it 'registers an offense on the association' do
|
||||
expect_offense(<<-RUBY)
|
||||
class User < ActiveRecord::Base
|
||||
has_many :tags, through: :user_tags
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the association has a scope defined' do
|
||||
context 'when the scope does not disable auto-loading' do
|
||||
it 'registers an offense on the scope' do
|
||||
expect_offense(<<-RUBY)
|
||||
class User < ActiveRecord::Base
|
||||
has_many :tags, -> { where(active: true) }, through: :user_tags
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the scope has auto_include(false)' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses(<<-RUBY)
|
||||
class User < ActiveRecord::Base
|
||||
has_many :tags, -> { where(active: true).auto_include(false).reorder(nil) }, through: :user_tags
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'outside of a migration spec file' do
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses(<<-RUBY)
|
||||
class User < ActiveRecord::Base
|
||||
has_many :tags, through: :user_tags
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
|
@ -133,22 +133,65 @@ describe JobEntity do
|
|||
context 'when job failed' do
|
||||
let(:job) { create(:ci_build, :script_failure) }
|
||||
|
||||
describe 'status' do
|
||||
it 'should contain the failure reason inside label' do
|
||||
expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
|
||||
expect(subject[:status][:label]).to eq('failed')
|
||||
expect(subject[:status][:tooltip]).to eq('failed <br> (script failure)')
|
||||
end
|
||||
it 'contains details' do
|
||||
expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
|
||||
end
|
||||
|
||||
it 'states that it failed' do
|
||||
expect(subject[:status][:label]).to eq('failed')
|
||||
end
|
||||
|
||||
it 'should indicate the failure reason on tooltip' do
|
||||
expect(subject[:status][:tooltip]).to eq('failed <br> (script failure)')
|
||||
end
|
||||
|
||||
it 'should include a callout message with a verbose output' do
|
||||
expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information')
|
||||
end
|
||||
|
||||
it 'should state that it is not recoverable' do
|
||||
expect(subject[:recoverable]).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job is allowed to fail' do
|
||||
let(:job) { create(:ci_build, :allowed_to_fail, :script_failure) }
|
||||
|
||||
it 'contains details' do
|
||||
expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
|
||||
end
|
||||
|
||||
it 'states that it failed' do
|
||||
expect(subject[:status][:label]).to eq('failed (allowed to fail)')
|
||||
end
|
||||
|
||||
it 'should indicate the failure reason on tooltip' do
|
||||
expect(subject[:status][:tooltip]).to eq('failed <br> (script failure) (allowed to fail)')
|
||||
end
|
||||
|
||||
it 'should include a callout message with a verbose output' do
|
||||
expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information')
|
||||
end
|
||||
|
||||
it 'should state that it is not recoverable' do
|
||||
expect(subject[:recoverable]).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job failed and is recoverable' do
|
||||
let(:job) { create(:ci_build, :api_failure) }
|
||||
|
||||
it 'should state it is recoverable' do
|
||||
expect(subject[:recoverable]).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job passed' do
|
||||
let(:job) { create(:ci_build, :success) }
|
||||
|
||||
describe 'status' do
|
||||
it 'should not contain the failure reason inside label' do
|
||||
expect(subject[:status][:label]).to eq('passed')
|
||||
end
|
||||
it 'should not include callout message or recoverable keys' do
|
||||
expect(subject).not_to include('callout_message')
|
||||
expect(subject).not_to include('recoverable')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -315,7 +315,9 @@ production:
|
|||
mv clair-scanner_linux_amd64 clair-scanner
|
||||
chmod +x clair-scanner
|
||||
touch clair-whitelist.yml
|
||||
while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
|
||||
retries=0
|
||||
echo "Waiting for clair daemon to start"
|
||||
while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
|
||||
./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue