Commit b1e7c4c6 authored by John T Skarbek's avatar John T Skarbek

Merge remote-tracking branch 'origin/master'

parents 6900142d 10900207
1.35.0 1.36.0
\ No newline at end of file
...@@ -285,7 +285,7 @@ gem 'gettext_i18n_rails', '~> 1.8.0' ...@@ -285,7 +285,7 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3' gem 'gettext_i18n_rails_js', '~> 1.3'
gem 'gettext', '~> 3.2.2', require: false, group: :development gem 'gettext', '~> 3.2.2', require: false, group: :development
gem 'batch-loader', '~> 1.2.2' gem 'batch-loader', '~> 1.4.0'
# Perf bar # Perf bar
gem 'peek', '~> 1.0.1' gem 'peek', '~> 1.0.1'
...@@ -309,7 +309,7 @@ group :development do ...@@ -309,7 +309,7 @@ group :development do
gem 'foreman', '~> 0.84.0' gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false gem 'brakeman', '~> 4.2', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.4'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler # Better errors handler
...@@ -417,7 +417,7 @@ group :ed25519 do ...@@ -417,7 +417,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 1.22.0', require: 'gitaly' gem 'gitaly-proto', '~> 1.26.0', require: 'gitaly'
gem 'grpc', '~> 1.19.0' gem 'grpc', '~> 1.19.0'
......
...@@ -76,7 +76,7 @@ GEM ...@@ -76,7 +76,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2) base32 (0.3.2)
batch-loader (1.2.2) batch-loader (1.4.0)
bcrypt (3.1.12) bcrypt (3.1.12)
bcrypt_pbkdf (1.0.0) bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
...@@ -283,7 +283,7 @@ GEM ...@@ -283,7 +283,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (1.22.0) gitaly-proto (1.26.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-default_value_for (3.1.1) gitlab-default_value_for (3.1.1)
...@@ -444,9 +444,9 @@ GEM ...@@ -444,9 +444,9 @@ GEM
rest-client (~> 2.0) rest-client (~> 2.0)
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.4.1) letter_opener (1.7.0)
launchy (~> 2.2) launchy (~> 2.2)
letter_opener_web (1.3.0) letter_opener_web (1.3.4)
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
...@@ -999,7 +999,7 @@ DEPENDENCIES ...@@ -999,7 +999,7 @@ DEPENDENCIES
awesome_print awesome_print
babosa (~> 1.0.2) babosa (~> 1.0.2)
base32 (~> 0.3.0) base32 (~> 0.3.0)
batch-loader (~> 1.2.2) batch-loader (~> 1.4.0)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
better_errors (~> 2.5.0) better_errors (~> 2.5.0)
...@@ -1056,7 +1056,7 @@ DEPENDENCIES ...@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.22.0) gitaly-proto (~> 1.26.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1) gitlab-default_value_for (~> 3.1.1)
gitlab-labkit (~> 0.1.2) gitlab-labkit (~> 0.1.2)
...@@ -1093,7 +1093,7 @@ DEPENDENCIES ...@@ -1093,7 +1093,7 @@ DEPENDENCIES
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.17) knapsack (~> 1.17)
kubeclient (~> 4.2.2) kubeclient (~> 4.2.2)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.4)
license_finder (~> 5.4) license_finder (~> 5.4)
licensee (~> 8.9) licensee (~> 8.9)
lograge (~> 0.5) lograge (~> 0.5)
......
import Flash from '../flash'; import Flash from '../flash';
import BalsamiqViewer from './balsamiq/balsamiq_viewer'; import BalsamiqViewer from './balsamiq/balsamiq_viewer';
import { __ } from '~/locale';
function onError() { function onError() {
const flash = new Flash('Balsamiq file could not be loaded.'); const flash = new Flash(__('Balsamiq file could not be loaded.'));
return flash; return flash;
} }
......
...@@ -5,6 +5,7 @@ import Dropzone from 'dropzone'; ...@@ -5,6 +5,7 @@ import Dropzone from 'dropzone';
import { visitUrl } from '../lib/utils/url_utility'; import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants'; import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf'; import csrf from '../lib/utils/csrf';
import { sprintf, __ } from '~/locale';
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false;
...@@ -73,7 +74,7 @@ export default class BlobFileDropzone { ...@@ -73,7 +74,7 @@ export default class BlobFileDropzone {
.html(errorMessage) .html(errorMessage)
.text(); .text();
$('.dropzone-alerts') $('.dropzone-alerts')
.html(`Error uploading file: "${stripped}"`) .html(sprintf(__('Error uploading file: %{stripped}'), { stripped }))
.show(); .show();
this.removeFile(file); this.removeFile(file);
}, },
...@@ -84,7 +85,7 @@ export default class BlobFileDropzone { ...@@ -84,7 +85,7 @@ export default class BlobFileDropzone {
e.stopPropagation(); e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) { if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
alert('Please select a file'); alert(__('Please select a file'));
return false; return false;
} }
toggleLoading(submitButton, submitButtonLoadingIcon, true); toggleLoading(submitButton, submitButtonLoadingIcon, true);
......
import JSZip from 'jszip'; import JSZip from 'jszip';
import JSZipUtils from 'jszip-utils'; import JSZipUtils from 'jszip-utils';
import { __ } from '~/locale';
export default class SketchLoader { export default class SketchLoader {
constructor(container) { constructor(container) {
...@@ -56,10 +57,10 @@ export default class SketchLoader { ...@@ -56,10 +57,10 @@ export default class SketchLoader {
const errorMsg = document.createElement('p'); const errorMsg = document.createElement('p');
errorMsg.className = 'prepend-top-default append-bottom-default text-center'; errorMsg.className = 'prepend-top-default append-bottom-default text-center';
errorMsg.textContent = ` errorMsg.textContent = __(`
Cannot show preview. For previews on sketch files, they must have the file format Cannot show preview. For previews on sketch files, they must have the file format
introduced by Sketch version 43 and above. introduced by Sketch version 43 and above.
`; `);
this.container.appendChild(errorMsg); this.container.appendChild(errorMsg);
this.removeLoadingIcon(); this.removeLoadingIcon();
......
import FileTemplateSelector from '../file_template_selector'; import FileTemplateSelector from '../file_template_selector';
import { __ } from '~/locale';
export default class DockerfileSelector extends FileTemplateSelector { export default class DockerfileSelector extends FileTemplateSelector {
constructor({ mediator }) { constructor({ mediator }) {
super(mediator); super(mediator);
this.config = { this.config = {
key: 'dockerfile', key: 'dockerfile',
name: 'Dockerfile', name: __('Dockerfile'),
pattern: /(Dockerfile)/, pattern: /(Dockerfile)/,
type: 'dockerfiles', type: 'dockerfiles',
dropdown: '.js-dockerfile-selector', dropdown: '.js-dockerfile-selector',
......
...@@ -2,6 +2,7 @@ import $ from 'jquery'; ...@@ -2,6 +2,7 @@ import $ from 'jquery';
import Flash from '../../flash'; import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils'; import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import { __ } from '~/locale';
export default class BlobViewer { export default class BlobViewer {
constructor() { constructor() {
...@@ -26,7 +27,7 @@ export default class BlobViewer { ...@@ -26,7 +27,7 @@ export default class BlobViewer {
promise promise
.then(module => module.default(viewer)) .then(module => module.default(viewer))
.catch(error => { .catch(error => {
Flash('Error loading file viewer.'); Flash(__('Error loading file viewer.'));
throw error; throw error;
}); });
...@@ -106,16 +107,19 @@ export default class BlobViewer { ...@@ -106,16 +107,19 @@ export default class BlobViewer {
if (!this.copySourceBtn) return; if (!this.copySourceBtn) return;
if (this.simpleViewer.getAttribute('data-loaded')) { if (this.simpleViewer.getAttribute('data-loaded')) {
this.copySourceBtn.setAttribute('title', 'Copy source to clipboard'); this.copySourceBtn.setAttribute('title', __('Copy source to clipboard'));
this.copySourceBtn.classList.remove('disabled'); this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) { } else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute( this.copySourceBtn.setAttribute(
'title', 'title',
'Wait for the source to load to copy it to the clipboard', __('Wait for the source to load to copy it to the clipboard'),
); );
this.copySourceBtn.classList.add('disabled'); this.copySourceBtn.classList.add('disabled');
} else { } else {
this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard'); this.copySourceBtn.setAttribute(
'title',
__('Switch to the source to copy it to the clipboard'),
);
this.copySourceBtn.classList.add('disabled'); this.copySourceBtn.classList.add('disabled');
} }
...@@ -158,7 +162,7 @@ export default class BlobViewer { ...@@ -158,7 +162,7 @@ export default class BlobViewer {
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}) })
.catch(() => new Flash('Error loading viewer')); .catch(() => new Flash(__('Error loading viewer')));
} }
static loadViewer(viewerParam) { static loadViewer(viewerParam) {
......
...@@ -83,7 +83,7 @@ export default { ...@@ -83,7 +83,7 @@ export default {
}" }"
:index="index" :index="index"
:data-issue-id="issue.id" :data-issue-id="issue.id"
class="board-card position-relative p-3 rounded" class="board-card p-3 rounded"
@mousedown="mouseDown" @mousedown="mouseDown"
@mousemove="mouseMove" @mousemove="mouseMove"
@mouseup="showIssue($event)" @mouseup="showIssue($event)"
......
...@@ -279,14 +279,10 @@ export default class Clusters { ...@@ -279,14 +279,10 @@ export default class Clusters {
this.store.acknowledgeSuccessfulUpdate(appId); this.store.acknowledgeSuccessfulUpdate(appId);
} }
toggleIngressDomainHelpText(ingressPreviousState, ingressNewState) { toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
const { externalIp, status } = ingressNewState; if (externalIp !== newExternalIp) {
const helpTextHidden = status !== APPLICATION_STATUS.INSTALLED || !externalIp; this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
const domainSnippetText = `${externalIp}${INGRESS_DOMAIN_SUFFIX}`; this.ingressDomainSnippet.textContent = `${newExternalIp}${INGRESS_DOMAIN_SUFFIX}`;
if (ingressPreviousState.status !== status) {
this.ingressDomainHelpText.classList.toggle('hide', helpTextHidden);
this.ingressDomainSnippet.textContent = domainSnippetText;
} }
} }
......
...@@ -12,6 +12,7 @@ import stageStagingComponent from './components/stage_staging_component.vue'; ...@@ -12,6 +12,7 @@ import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue'; import stageTestComponent from './components/stage_test_component.vue';
import CycleAnalyticsService from './cycle_analytics_service'; import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store'; import CycleAnalyticsStore from './cycle_analytics_store';
import { __ } from '~/locale';
Vue.use(Translate); Vue.use(Translate);
...@@ -61,7 +62,7 @@ export default () => { ...@@ -61,7 +62,7 @@ export default () => {
methods: { methods: {
handleError() { handleError() {
this.store.setErrorState(true); this.store.setErrorState(true);
return new Flash('There was an error while fetching cycle analytics data.'); return new Flash(__('There was an error while fetching cycle analytics data.'));
}, },
initDropdown() { initDropdown() {
const $dropdown = $('.js-ca-dropdown'); const $dropdown = $('.js-ca-dropdown');
......
...@@ -3,6 +3,7 @@ import VueRouter from 'vue-router'; ...@@ -3,6 +3,7 @@ import VueRouter from 'vue-router';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
import flash from '~/flash'; import flash from '~/flash';
import store from './stores'; import store from './stores';
import { __ } from '~/locale';
Vue.use(VueRouter); Vue.use(VueRouter);
...@@ -94,7 +95,7 @@ router.beforeEach((to, from, next) => { ...@@ -94,7 +95,7 @@ router.beforeEach((to, from, next) => {
}) })
.catch(e => { .catch(e => {
flash( flash(
'Error while loading the project data. Please try again.', __('Error while loading the project data. Please try again.'),
'alert', 'alert',
document, document,
null, null,
......
import { activityBarViews } from '../../../constants'; import { activityBarViews } from '../../../constants';
import { __ } from '~/locale';
export const templateTypes = () => [ export const templateTypes = () => [
{ {
...@@ -10,11 +11,11 @@ export const templateTypes = () => [ ...@@ -10,11 +11,11 @@ export const templateTypes = () => [
key: 'gitignores', key: 'gitignores',
}, },
{ {
name: 'LICENSE', name: __('LICENSE'),
key: 'licenses', key: 'licenses',
}, },
{ {
name: 'Dockerfile', name: __('Dockerfile'),
key: 'dockerfiles', key: 'dockerfiles',
}, },
]; ];
......
...@@ -290,7 +290,7 @@ export default class SSHMirror { ...@@ -290,7 +290,7 @@ export default class SSHMirror {
this.setSSHPublicKey(data.import_data_attributes.ssh_public_key); this.setSSHPublicKey(data.import_data_attributes.ssh_public_key);
}) })
.catch(() => { .catch(() => {
Flash(_('Unable to regenerate public ssh key.')); Flash(__('Unable to regenerate public ssh key.'));
}); });
} }
......
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status'; import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils'; import { backOff } from '../../lib/utils/common_utils';
import { s__ } from '../../locale'; import { s__, __ } from '../../locale';
const MAX_REQUESTS = 3; const MAX_REQUESTS = 3;
...@@ -15,7 +15,7 @@ function backOffRequest(makeRequestCallback) { ...@@ -15,7 +15,7 @@ function backOffRequest(makeRequestCallback) {
if (requestCounter < MAX_REQUESTS) { if (requestCounter < MAX_REQUESTS) {
next(); next();
} else { } else {
stop(new Error('Failed to connect to the prometheus server')); stop(new Error(__('Failed to connect to the prometheus server')));
} }
} else { } else {
stop(resp); stop(resp);
......
import { __ } from '~/locale';
export const mrStates = { export const mrStates = {
merged: 'merged', merged: 'merged',
closed: 'closed', closed: 'closed',
}; };
export const humanMRStates = { export const humanMRStates = {
merged: 'Merged', merged: __('Merged'),
closed: 'Closed', closed: __('Closed'),
open: 'Open', open: __('Open'),
}; };
import $ from 'jquery'; import $ from 'jquery';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils'; import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
import { slugifyWithHyphens } from '../lib/utils/text_utility'; import { slugifyWithHyphens } from '../lib/utils/text_utility';
import { s__ } from '~/locale';
let hasUserDefinedProjectPath = false; let hasUserDefinedProjectPath = false;
...@@ -114,71 +115,71 @@ const bindEvents = () => { ...@@ -114,71 +115,71 @@ const bindEvents = () => {
const value = $(this).val(); const value = $(this).val();
const templates = { const templates = {
rails: { rails: {
text: 'Ruby on Rails', text: s__('ProjectTemplates|Ruby on Rails'),
icon: '.template-option .icon-rails', icon: '.template-option .icon-rails',
}, },
express: { express: {
text: 'NodeJS Express', text: s__('ProjectTemplates|NodeJS Express'),
icon: '.template-option .icon-express', icon: '.template-option .icon-express',
}, },
spring: { spring: {
text: 'Spring', text: s__('ProjectTemplates|Spring'),
icon: '.template-option .icon-spring', icon: '.template-option .icon-spring',
}, },
iosswift: { iosswift: {
text: 'iOS (Swift)', text: s__('ProjectTemplates|iOS (Swift)'),
icon: '.template-option svg.icon-gitlab', icon: '.template-option svg.icon-gitlab',
}, },
dotnetcore: { dotnetcore: {
text: '.NET Core', text: s__('ProjectTemplates|.NET Core'),
icon: '.template-option .icon-dotnet', icon: '.template-option .icon-dotnet',
}, },
android: { android: {
text: 'Android', text: s__('ProjectTemplates|Android'),
icon: '.template-option svg.icon-android', icon: '.template-option svg.icon-android',
}, },
gomicro: { gomicro: {
text: 'Go Micro', text: s__('ProjectTemplates|Go Micro'),
icon: '.template-option .icon-gomicro', icon: '.template-option .icon-gomicro',
}, },
hugo: { hugo: {
text: 'Pages/Hugo', text: s__('ProjectTemplates|Pages/Hugo'),
icon: '.template-option .icon-hugo', icon: '.template-option .icon-hugo',
}, },
jekyll: { jekyll: {
text: 'Pages/Jekyll', text: s__('ProjectTemplates|Pages/Jekyll'),
icon: '.template-option .icon-jekyll', icon: '.template-option .icon-jekyll',
}, },
plainhtml: { plainhtml: {
text: 'Pages/Plain HTML', text: s__('ProjectTemplates|Pages/Plain HTML'),
icon: '.template-option .icon-plainhtml', icon: '.template-option .icon-plainhtml',
}, },
gitbook: { gitbook: {
text: 'Pages/GitBook', text: s__('ProjectTemplates|Pages/GitBook'),
icon: '.template-option .icon-gitbook', icon: '.template-option .icon-gitbook',
}, },
hexo: { hexo: {
text: 'Pages/Hexo', text: s__('ProjectTemplates|Pages/Hexo'),
icon: '.template-option .icon-hexo', icon: '.template-option .icon-hexo',
}, },
nfhugo: { nfhugo: {
text: 'Netlify/Hugo', text: s__('ProjectTemplates|Netlify/Hugo'),
icon: '.template-option .icon-netlify', icon: '.template-option .icon-netlify',
}, },
nfjekyll: { nfjekyll: {
text: 'Netlify/Jekyll', text: s__('ProjectTemplates|Netlify/Jekyll'),
icon: '.template-option .icon-netlify', icon: '.template-option .icon-netlify',
}, },
nfplainhtml: { nfplainhtml: {
text: 'Netlify/Plain HTML', text: s__('ProjectTemplates|Netlify/Plain HTML'),
icon: '.template-option .icon-netlify', icon: '.template-option .icon-netlify',
}, },
nfgitbook: { nfgitbook: {
text: 'Netlify/GitBook', text: s__('ProjectTemplates|Netlify/GitBook'),
icon: '.template-option .icon-netlify', icon: '.template-option .icon-netlify',
}, },
nfhexo: { nfhexo: {
text: 'Netlify/Hexo', text: s__('ProjectTemplates|Netlify/Hexo'),
icon: '.template-option .icon-netlify', icon: '.template-option .icon-netlify',
}, },
}; };
......
...@@ -4,8 +4,11 @@ const index = function index() { ...@@ -4,8 +4,11 @@ const index = function index() {
RavenConfig.init({ RavenConfig.init({
sentryDsn: gon.sentry_dsn, sentryDsn: gon.sentry_dsn,
currentUserId: gon.current_user_id, currentUserId: gon.current_user_id,
whitelistUrls: [gon.gitlab_url], whitelistUrls:
isProduction: process.env.NODE_ENV, process.env.NODE_ENV === 'production'
? [gon.gitlab_url]
: [gon.gitlab_url, 'webpack-internal://'],
environment: gon.sentry_environment,
release: gon.revision, release: gon.revision,
tags: { tags: {
revision: gon.revision, revision: gon.revision,
......
...@@ -61,7 +61,7 @@ const RavenConfig = { ...@@ -61,7 +61,7 @@ const RavenConfig = {
release: this.options.release, release: this.options.release,
tags: this.options.tags, tags: this.options.tags,
whitelistUrls: this.options.whitelistUrls, whitelistUrls: this.options.whitelistUrls,
environment: this.options.isProduction ? 'production' : 'development', environment: this.options.environment,
ignoreErrors: this.IGNORE_ERRORS, ignoreErrors: this.IGNORE_ERRORS,
ignoreUrls: this.IGNORE_URLS, ignoreUrls: this.IGNORE_URLS,
shouldSendCallback: this.shouldSendSample.bind(this), shouldSendCallback: this.shouldSendSample.bind(this),
......
import { __ } from '~/locale';
export default class U2FError { export default class U2FError {
constructor(errorCode, u2fFlowType) { constructor(errorCode, u2fFlowType) {
this.errorCode = errorCode; this.errorCode = errorCode;
...@@ -8,15 +10,17 @@ export default class U2FError { ...@@ -8,15 +10,17 @@ export default class U2FError {
message() { message() {
if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) { if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) {
return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.'; return __(
'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.',
);
} else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) { } else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) {
if (this.u2fFlowType === 'authenticate') { if (this.u2fFlowType === 'authenticate') {
return 'This device has not been registered with us.'; return __('This device has not been registered with us.');
} }
if (this.u2fFlowType === 'register') { if (this.u2fFlowType === 'register') {
return 'This device has already been registered with us.'; return __('This device has already been registered with us.');
} }
} }
return 'There was a problem communicating with your device.'; return __('There was a problem communicating with your device.');
} }
} }
import { __ } from '~/locale';
const viewers = { const viewers = {
image: { image: {
id: 'image', id: 'image',
}, },
markdown: { markdown: {
id: 'markdown', id: 'markdown',
previewTitle: 'Preview Markdown', previewTitle: __('Preview Markdown'),
}, },
}; };
......
import $ from 'jquery'; import $ from 'jquery';
import '~/commons/bootstrap';
export default { export default {
bind(el) { bind(el) {
......
...@@ -210,6 +210,7 @@ ...@@ -210,6 +210,7 @@
border: 1px solid $gray-200; border: 1px solid $gray-200;
box-shadow: 0 1px 2px $issue-boards-card-shadow; box-shadow: 0 1px 2px $issue-boards-card-shadow;
line-height: $gl-padding; line-height: $gl-padding;
list-style: none;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: $gl-padding-8; margin-bottom: $gl-padding-8;
......
...@@ -4,6 +4,7 @@ class Clusters::ApplicationsController < Clusters::BaseController ...@@ -4,6 +4,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
before_action :cluster before_action :cluster
before_action :authorize_create_cluster!, only: [:create] before_action :authorize_create_cluster!, only: [:create]
before_action :authorize_update_cluster!, only: [:update] before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
def create def create
request_handler do request_handler do
...@@ -21,6 +22,14 @@ class Clusters::ApplicationsController < Clusters::BaseController ...@@ -21,6 +22,14 @@ class Clusters::ApplicationsController < Clusters::BaseController
end end
end end
def destroy
request_handler do
Clusters::Applications::DestroyService
.new(@cluster, current_user, cluster_application_destroy_params)
.execute(request)
end
end
private private
def request_handler def request_handler
...@@ -40,4 +49,8 @@ class Clusters::ApplicationsController < Clusters::BaseController ...@@ -40,4 +49,8 @@ class Clusters::ApplicationsController < Clusters::BaseController
def cluster_application_params def cluster_application_params
params.permit(:application, :hostname, :email) params.permit(:application, :hostname, :email)
end end
def cluster_application_destroy_params
params.permit(:application)
end
end end
...@@ -132,18 +132,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -132,18 +132,6 @@ class Projects::IssuesController < Projects::ApplicationController
render_conflict_response render_conflict_response
end end
def referenced_merge_requests
@merge_requests, @closed_by_merge_requests = ::Issues::ReferencedMergeRequestsService.new(project, current_user).execute(issue)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_merge_requests')
}
end
end
end
def related_branches def related_branches
@related_branches = Issues::RelatedBranchesService.new(project, current_user).execute(issue) @related_branches = Issues::RelatedBranchesService.new(project, current_user).execute(issue)
......
...@@ -36,10 +36,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -36,10 +36,10 @@ class ProjectsController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def new def new
namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id] @namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if namespace && !can?(current_user, :create_projects, namespace) return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace)
@project = Project.new(namespace_id: namespace&.id) @project = Project.new(namespace_id: @namespace&.id)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -7,14 +7,14 @@ module Resolvers ...@@ -7,14 +7,14 @@ module Resolvers
prepended do prepended do
argument :full_path, GraphQL::ID_TYPE, argument :full_path, GraphQL::ID_TYPE,
required: true, required: true,
description: 'The full path of the project or namespace, e.g., "gitlab-org/gitlab-ce"' description: 'The full path of the project, group or namespace, e.g., "gitlab-org/gitlab-ce"'
end end
def model_by_full_path(model, full_path) def model_by_full_path(model, full_path)
BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args| BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args|
# `with_route` avoids an N+1 calculating full_path # `with_route` avoids an N+1 calculating full_path
args[:key].where_full_path_in(full_paths).with_route.each do |project| args[:key].where_full_path_in(full_paths).with_route.each do |model_instance|
loader.call(project.full_path, project) loader.call(model_instance.full_path, model_instance)
end end
end end
end end
......
# frozen_string_literal: true
module Resolvers
class GroupResolver < BaseResolver
prepend FullPathResolver
type Types::GroupType, null: true
def resolve(full_path:)
model_by_full_path(Group, full_path)
end
end
end
# frozen_string_literal: true
module Types
class GroupType < NamespaceType
graphql_name 'Group'
authorize :read_group
expose_permissions Types::PermissionTypes::Group
field :web_url, GraphQL::STRING_TYPE, null: true
field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do
group.avatar_url(only_path: false)
end
if ::Group.supports_nested_objects?
field :parent, GroupType, null: true
end
end
end
# frozen_string_literal: true
module Types
class NamespaceType < BaseObject
graphql_name 'Namespace'
field :id, GraphQL::ID_TYPE, null: false
field :name, GraphQL::STRING_TYPE, null: false
field :path, GraphQL::STRING_TYPE, null: false
field :full_name, GraphQL::STRING_TYPE, null: false
field :full_path, GraphQL::ID_TYPE, null: false
field :description, GraphQL::STRING_TYPE, null: true
field :visibility, GraphQL::STRING_TYPE, null: true
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
end
end
# frozen_string_literal: true
module Types
module PermissionTypes
class Group < BasePermissionType
graphql_name 'GroupPermissions'
abilities :read_group
end
end
end
...@@ -66,6 +66,9 @@ module Types ...@@ -66,6 +66,9 @@ module Types
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true
field :namespace, Types::NamespaceType, null: false
field :group, Types::GroupType, null: true
field :merge_requests, field :merge_requests,
Types::MergeRequestType.connection_type, Types::MergeRequestType.connection_type,
null: true, null: true,
......
...@@ -9,6 +9,11 @@ module Types ...@@ -9,6 +9,11 @@ module Types
resolver: Resolvers::ProjectResolver, resolver: Resolvers::ProjectResolver,
description: "Find a project" description: "Find a project"
field :group, Types::GroupType,
null: true,
resolver: Resolvers::GroupResolver,
description: "Find a group"
field :metadata, Types::MetadataType, field :metadata, Types::MetadataType,
null: true, null: true,
resolver: Resolvers::MetadataResolver, resolver: Resolvers::MetadataResolver,
......
...@@ -183,6 +183,22 @@ module ApplicationSettingImplementation ...@@ -183,6 +183,22 @@ module ApplicationSettingImplementation
clientside_sentry_dsn.strip! if clientside_sentry_dsn.present? clientside_sentry_dsn.strip! if clientside_sentry_dsn.present?
end end
def sentry_enabled
Gitlab.config.sentry.enabled || read_attribute(:sentry_enabled)
end
def sentry_dsn
Gitlab.config.sentry.dsn || read_attribute(:sentry_dsn)
end
def clientside_sentry_enabled
Gitlab.config.sentry.enabled || read_attribute(:clientside_sentry_enabled)
end
def clientside_sentry_dsn
Gitlab.config.sentry.dsn || read_attribute(:clientside_sentry_dsn)
end
def performance_bar_allowed_group def performance_bar_allowed_group
Group.find_by_id(performance_bar_allowed_group_id) Group.find_by_id(performance_bar_allowed_group_id)
end end
......
...@@ -4,6 +4,7 @@ module Ci ...@@ -4,6 +4,7 @@ module Ci
class Bridge < CommitStatus class Bridge < CommitStatus
include Ci::Processable include Ci::Processable
include Ci::Contextable include Ci::Contextable
include Ci::PipelineDelegator
include Importable include Importable
include AfterCommitQueue include AfterCommitQueue
include HasRef include HasRef
...@@ -13,8 +14,6 @@ module Ci ...@@ -13,8 +14,6 @@ module Ci
belongs_to :trigger_request belongs_to :trigger_request
validates :ref, presence: true validates :ref, presence: true
delegate :merge_request_event?, to: :pipeline
def self.retry(bridge, current_user) def self.retry(bridge, current_user)
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -6,6 +6,7 @@ module Ci ...@@ -6,6 +6,7 @@ module Ci
include Ci::Processable include Ci::Processable
include Ci::Metadatable include Ci::Metadatable
include Ci::Contextable include Ci::Contextable
include Ci::PipelineDelegator
include TokenAuthenticatable include TokenAuthenticatable
include AfterCommitQueue include AfterCommitQueue
include ObjectStorage::BackgroundMove include ObjectStorage::BackgroundMove
...@@ -49,8 +50,6 @@ module Ci ...@@ -49,8 +50,6 @@ module Ci
delegate :terminal_specification, to: :runner_session, allow_nil: true delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project delegate :gitlab_deploy_token, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true delegate :trigger_short_token, to: :trigger_request, allow_nil: true
delegate :merge_request_event?, :merge_request_ref?,
:legacy_detached_merge_request_pipeline?, to: :pipeline
## ##
# Since Gitlab 11.5, deployments records started being created right after # Since Gitlab 11.5, deployments records started being created right after
......
...@@ -759,6 +759,18 @@ module Ci ...@@ -759,6 +759,18 @@ module Ci
user == current_user user == current_user
end end
def source_ref
if triggered_by_merge_request?
merge_request.source_branch
else
ref
end
end
def source_ref_slug
Gitlab::Utils.slugify(source_ref.to_s)
end
private private
def ci_yaml_from_repo def ci_yaml_from_repo
......
...@@ -24,6 +24,12 @@ module Clusters ...@@ -24,6 +24,12 @@ module Clusters
'stable/cert-manager' 'stable/cert-manager'
end end
# We will implement this in future MRs.
# Need to reverse postinstall step
def allowed_to_uninstall?
false
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'certmanager', name: 'certmanager',
......
...@@ -29,6 +29,13 @@ module Clusters ...@@ -29,6 +29,13 @@ module Clusters
self.status = 'installable' if cluster&.platform_kubernetes_active? self.status = 'installable' if cluster&.platform_kubernetes_active?
end end
# We will implement this in future MRs.
# Basically we need to check all other applications are not installed
# first.
def allowed_to_uninstall?
false
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InitCommand.new( Gitlab::Kubernetes::Helm::InitCommand.new(
name: name, name: name,
......
...@@ -35,6 +35,13 @@ module Clusters ...@@ -35,6 +35,13 @@ module Clusters
'stable/nginx-ingress' 'stable/nginx-ingress'
end end
# We will implement this in future MRs.
# Basically we need to check all dependent applications are not installed
# first.
def allowed_to_uninstall?
false
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name, name: name,
......
...@@ -38,6 +38,12 @@ module Clusters ...@@ -38,6 +38,12 @@ module Clusters
content_values.to_yaml content_values.to_yaml
end end
# Will be addressed in future MRs
# We need to investigate and document what will be permenantly deleted.
def allowed_to_uninstall?
false
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name, name: name,
......
...@@ -51,6 +51,12 @@ module Clusters ...@@ -51,6 +51,12 @@ module Clusters
{ "domain" => hostname }.to_yaml { "domain" => hostname }.to_yaml
end end
# Handled in a new issue:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/59369
def allowed_to_uninstall?
false
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name, name: name,
......
...@@ -16,10 +16,12 @@ module Clusters ...@@ -16,10 +16,12 @@ module Clusters
default_value_for :version, VERSION default_value_for :version, VERSION
after_destroy :disable_prometheus_integration
state_machine :status do state_machine :status do
after_transition any => [:installed] do |application| after_transition any => [:installed] do |application|
application.cluster.projects.each do |project| application.cluster.projects.each do |project|
project.find_or_initialize_service('prometheus').update(active: true) project.find_or_initialize_service('prometheus').update!(active: true)
end end
end end
end end
...@@ -47,6 +49,14 @@ module Clusters ...@@ -47,6 +49,14 @@ module Clusters
) )
end end
def uninstall_command
Gitlab::Kubernetes::Helm::DeleteCommand.new(
name: name,
rbac: cluster.platform_kubernetes_rbac?,
files: files
)
end
def upgrade_command(values) def upgrade_command(values)
::Gitlab::Kubernetes::Helm::InstallCommand.new( ::Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name, name: name,
...@@ -82,6 +92,12 @@ module Clusters ...@@ -82,6 +92,12 @@ module Clusters
private private
def disable_prometheus_integration
cluster.projects.each do |project|
project.prometheus_service&.update!(active: false)
end
end
def kube_client def kube_client
cluster&.kubeclient&.core_client cluster&.kubeclient&.core_client
end end
......
...@@ -29,6 +29,13 @@ module Clusters ...@@ -29,6 +29,13 @@ module Clusters
content_values.to_yaml content_values.to_yaml
end end
# Need to investigate if pipelines run by this runner will stop upon the
# executor pod stopping
# I.e.run a pipeline, and uninstall runner while pipeline is running
def allowed_to_uninstall?
false
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new( Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name, name: name,
......
...@@ -18,6 +18,16 @@ module Clusters ...@@ -18,6 +18,16 @@ module Clusters
self.status = 'installable' if cluster&.application_helm_available? self.status = 'installable' if cluster&.application_helm_available?
end end
def can_uninstall?
allowed_to_uninstall?
end
# All new applications should uninstall by default
# Override if there's dependencies that needs to be uninstalled first
def allowed_to_uninstall?
true
end
def self.application_name def self.application_name
self.to_s.demodulize.underscore self.to_s.demodulize.underscore
end end
......
...@@ -25,9 +25,11 @@ module Clusters ...@@ -25,9 +25,11 @@ module Clusters
state :updating, value: 4 state :updating, value: 4
state :updated, value: 5 state :updated, value: 5
state :update_errored, value: 6 state :update_errored, value: 6
state :uninstalling, value: 7
state :uninstall_errored, value: 8
event :make_scheduled do event :make_scheduled do
transition [:installable, :errored, :installed, :updated, :update_errored] => :scheduled transition [:installable, :errored, :installed, :updated, :update_errored, :uninstall_errored] => :scheduled
end end
event :make_installing do event :make_installing do
...@@ -40,8 +42,9 @@ module Clusters ...@@ -40,8 +42,9 @@ module Clusters
end end
event :make_errored do event :make_errored do
transition any - [:updating] => :errored transition any - [:updating, :uninstalling] => :errored
transition [:updating] => :update_errored transition [:updating] => :update_errored
transition [:uninstalling] => :uninstall_errored
end end
event :make_updating do event :make_updating do
...@@ -52,6 +55,10 @@ module Clusters ...@@ -52,6 +55,10 @@ module Clusters
transition any => :update_errored transition any => :update_errored
end end
event :make_uninstalling do
transition [:scheduled] => :uninstalling
end
before_transition any => [:scheduled] do |app_status, _| before_transition any => [:scheduled] do |app_status, _|
app_status.status_reason = nil app_status.status_reason = nil
end end
...@@ -65,7 +72,7 @@ module Clusters ...@@ -65,7 +72,7 @@ module Clusters
app_status.status_reason = nil app_status.status_reason = nil
end end
before_transition any => [:update_errored] do |app_status, transition| before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition|
status_reason = transition.args.first status_reason = transition.args.first
app_status.status_reason = status_reason if status_reason app_status.status_reason = status_reason if status_reason
end end
......
...@@ -76,6 +76,10 @@ module Clusters ...@@ -76,6 +76,10 @@ module Clusters
end end
end end
def namespace_for(project)
cluster.find_or_initialize_kubernetes_namespace_for_project(project).namespace
end
def predefined_variables(project:) def predefined_variables(project:)
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'KUBE_URL', value: api_url) variables.append(key: 'KUBE_URL', value: api_url)
......
...@@ -91,7 +91,8 @@ module Avatarable ...@@ -91,7 +91,8 @@ module Avatarable
private private
def retrieve_upload_from_batch(identifier) def retrieve_upload_from_batch(identifier)
BatchLoader.for(identifier: identifier, model: self).batch(key: self.class) do |upload_params, loader, args| BatchLoader.for(identifier: identifier, model: self)
.batch(key: self.class, cache: true, replace_methods: false) do |upload_params, loader, args|
model_class = args[:key] model_class = args[:key]
paths = upload_params.flat_map do |params| paths = upload_params.flat_map do |params|
params[:model].upload_paths(params[:identifier]) params[:model].upload_paths(params[:identifier])
......
...@@ -70,8 +70,8 @@ module Ci ...@@ -70,8 +70,8 @@ module Ci
variables.append(key: 'CI_COMMIT_SHA', value: sha) variables.append(key: 'CI_COMMIT_SHA', value: sha)
variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha) variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) variables.append(key: 'CI_COMMIT_REF_NAME', value: source_ref)
variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) variables.append(key: 'CI_COMMIT_REF_SLUG', value: source_ref_slug)
variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
variables.append(key: "CI_JOB_MANUAL", value: 'true') if action? variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
...@@ -85,8 +85,8 @@ module Ci ...@@ -85,8 +85,8 @@ module Ci
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_BUILD_REF', value: sha) variables.append(key: 'CI_BUILD_REF', value: sha)
variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha) variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
variables.append(key: 'CI_BUILD_REF_NAME', value: ref) variables.append(key: 'CI_BUILD_REF_NAME', value: source_ref)
variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug) variables.append(key: 'CI_BUILD_REF_SLUG', value: source_ref_slug)
variables.append(key: 'CI_BUILD_NAME', value: name) variables.append(key: 'CI_BUILD_NAME', value: name)
variables.append(key: 'CI_BUILD_STAGE', value: stage) variables.append(key: 'CI_BUILD_STAGE', value: stage)
variables.append(key: "CI_BUILD_TAG", value: ref) if tag? variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
......
# frozen_string_literal: true
##
# This module is mainly used by child associations of `Ci::Pipeline` that needs to look up
# single source of truth. For example, `Ci::Build` has `git_ref` method, which behaves
# slightly different from `Ci::Pipeline`'s `git_ref`. This is very confusing as
# the system could behave differently time to time.
# We should have a single interface in `Ci::Pipeline` and access the method always.
module Ci
module PipelineDelegator
extend ActiveSupport::Concern
included do
delegate :merge_request_event?,
:merge_request_ref?,
:source_ref,
:source_ref_slug,
:legacy_detached_merge_request_pipeline?, to: :pipeline
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
##
# We will disable `ref` and `sha` attributes in `Ci::Build` in the future
# and remove this module in favor of Ci::PipelineDelegator.
module HasRef module HasRef
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
...@@ -1054,6 +1054,16 @@ class MergeRequest < ApplicationRecord ...@@ -1054,6 +1054,16 @@ class MergeRequest < ApplicationRecord
@environments[current_user] @environments[current_user]
end end
##
# This method is for looking for active environments which created via pipelines for merge requests.
# Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`),
# we cannot look up environments with source branch name.
def environments
return Environment.none unless actual_head_pipeline&.triggered_by_merge_request?
actual_head_pipeline.environments
end
def state_human_name def state_human_name
if merged? if merged?
"Merged" "Merged"
......
...@@ -38,6 +38,8 @@ class PagesDomain < ApplicationRecord ...@@ -38,6 +38,8 @@ class PagesDomain < ApplicationRecord
where(verified_at.eq(nil).or(enabled_until.eq(nil).or(enabled_until.lt(threshold)))) where(verified_at.eq(nil).or(enabled_until.eq(nil).or(enabled_until.lt(threshold))))
end end
scope :for_removal, -> { where("remove_at < ?", Time.now) }
def verified? def verified?
!!verified_at !!verified_at
end end
......
...@@ -94,6 +94,10 @@ class KubernetesService < DeploymentService ...@@ -94,6 +94,10 @@ class KubernetesService < DeploymentService
end end
end end
def namespace_for(project)
actual_namespace
end
# Check we can connect to the Kubernetes API # Check we can connect to the Kubernetes API
def test(*args) def test(*args)
kubeclient = build_kube_client! kubeclient = build_kube_client!
......
...@@ -63,19 +63,11 @@ module Ci ...@@ -63,19 +63,11 @@ module Ci
end end
def link_to_merge_request_source_branch def link_to_merge_request_source_branch
return unless merge_request_presenter merge_request_presenter&.source_branch_link
link_to(merge_request_presenter.source_branch,
merge_request_presenter.source_branch_commits_path,
class: 'ref-name')
end end
def link_to_merge_request_target_branch def link_to_merge_request_target_branch
return unless merge_request_presenter merge_request_presenter&.target_branch_link
link_to(merge_request_presenter.target_branch,
merge_request_presenter.target_branch_commits_path,
class: 'ref-name')
end end
private private
......
...@@ -216,6 +216,22 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -216,6 +216,22 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
help_page_path('ci/merge_request_pipelines/index.md') help_page_path('ci/merge_request_pipelines/index.md')
end end
def source_branch_link
if source_branch_exists?
link_to(source_branch, source_branch_commits_path, class: 'ref-name')
else
content_tag(:span, source_branch, class: 'ref-name')
end
end
def target_branch_link
if target_branch_exists?
link_to(target_branch, target_branch_commits_path, class: 'ref-name')
else
content_tag(:span, target_branch, class: 'ref-name')
end
end
private private
def cached_can_be_reverted? def cached_can_be_reverted?
......
...@@ -10,4 +10,5 @@ class ClusterApplicationEntity < Grape::Entity ...@@ -10,4 +10,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) } expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) } expose :email, if: -> (e, _) { e.respond_to?(:email) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) } expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall
end end
...@@ -9,12 +9,11 @@ module Ci ...@@ -9,12 +9,11 @@ module Ci
return unless @ref.present? return unless @ref.present?
environments.each do |environment| environments.each { |environment| stop(environment) }
next unless environment.stop_action_available? end
next unless can?(current_user, :stop_environment, environment)
environment.stop_with_action!(current_user) def execute_for_merge_request(merge_request)
end merge_request.environments.each { |environment| stop(environment) }
end end
private private
...@@ -24,5 +23,12 @@ module Ci ...@@ -24,5 +23,12 @@ module Ci
.new(project, current_user, ref: @ref, recently_updated: true) .new(project, current_user, ref: @ref, recently_updated: true)
.execute .execute
end end
def stop(environment)
return unless environment.stop_action_available?
return unless can?(current_user, :stop_environment, environment)
environment.stop_with_action!(current_user)
end
end end
end end
...@@ -37,7 +37,7 @@ module Clusters ...@@ -37,7 +37,7 @@ module Clusters
end end
def check_timeout def check_timeout
if timeouted? if timed_out?
begin begin
app.make_errored!("Operation timed out. Check pod logs for #{pod_name} for more details.") app.make_errored!("Operation timed out. Check pod logs for #{pod_name} for more details.")
end end
...@@ -51,8 +51,8 @@ module Clusters ...@@ -51,8 +51,8 @@ module Clusters
install_command.pod_name install_command.pod_name
end end
def timeouted? def timed_out?
Time.now.utc - app.updated_at.to_time.utc > ClusterWaitForAppInstallationWorker::TIMEOUT Time.now.utc - app.updated_at.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
end end
def remove_installation_pod def remove_installation_pod
......
# frozen_string_literal: true
module Clusters
module Applications
class CheckUninstallProgressService < BaseHelmService
def execute
return unless app.uninstalling?
case installation_phase
when Gitlab::Kubernetes::Pod::SUCCEEDED
on_success
when Gitlab::Kubernetes::Pod::FAILED
on_failed
else
check_timeout
end
rescue Kubeclient::HttpError => e
log_error(e)
app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
end
private
def on_success
app.destroy!
rescue StandardError => e
app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message })
ensure
remove_installation_pod
end
def on_failed
app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
end
def check_timeout
if timed_out?
app.make_errored!(_('Operation timed out. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
else
WaitForUninstallAppWorker.perform_in(WaitForUninstallAppWorker::INTERVAL, app.name, app.id)
end
end
def pod_name
app.uninstall_command.pod_name
end
def timed_out?
Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT
end
def remove_installation_pod
helm_api.delete_pod!(pod_name)
end
def installation_phase
helm_api.status(pod_name)
end
end
end
end
...@@ -10,8 +10,8 @@ module Clusters ...@@ -10,8 +10,8 @@ module Clusters
end end
def builder def builder
cluster.method("application_#{application_name}").call || cluster.public_send(:"application_#{application_name}") || # rubocop:disable GitlabSecurity/PublicSend
cluster.method("build_application_#{application_name}").call cluster.public_send(:"build_application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend
end end
end end
end end
......
# frozen_string_literal: true
module Clusters
module Applications
class DestroyService < ::Clusters::Applications::BaseService
def execute(_request)
instantiate_application.tap do |application|
break unless application.can_uninstall?
application.make_scheduled!
Clusters::Applications::UninstallWorker.perform_async(application.name, application.id)
end
end
private
def builder
cluster.public_send(:"application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
# frozen_string_literal: true
module Clusters
module Applications
class UninstallService < BaseHelmService
def execute
return unless app.scheduled?
app.make_uninstalling!
uninstall
end
private
def uninstall
helm_api.uninstall(app.uninstall_command)
Clusters::Applications::WaitForUninstallAppWorker.perform_in(
Clusters::Applications::WaitForUninstallAppWorker::INTERVAL, app.name, app.id)
rescue Kubeclient::HttpError => e
log_error(e)
app.make_errored!("Kubernetes error: #{e.error_code}")
rescue StandardError => e
log_error(e)
app.make_errored!('Failed to uninstall.')
end
end
end
end
...@@ -10,7 +10,7 @@ module Clusters ...@@ -10,7 +10,7 @@ module Clusters
end end
def builder def builder
cluster.method("application_#{application_name}").call cluster.public_send(:"application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend
end end
end end
end end
......
...@@ -73,13 +73,13 @@ module Git ...@@ -73,13 +73,13 @@ module Git
def push_data def push_data
@push_data ||= Gitlab::DataBuilder::Push.build( @push_data ||= Gitlab::DataBuilder::Push.build(
project, project: project,
current_user, user: current_user,
params[:oldrev], oldrev: params[:oldrev],
params[:newrev], newrev: params[:newrev],
params[:ref], ref: params[:ref],
limited_commits, commits: limited_commits,
event_message, message: event_message,
commits_count: commits_count, commits_count: commits_count,
push_options: params[:push_options] || {} push_options: params[:push_options] || {}
) )
......
...@@ -24,6 +24,11 @@ module MergeRequests ...@@ -24,6 +24,11 @@ module MergeRequests
end end
end end
def cleanup_environments(merge_request)
Ci::StopEnvironmentsService.new(merge_request.source_project, current_user)
.execute_for_merge_request(merge_request)
end
private private
def handle_wip_event(merge_request) def handle_wip_event(merge_request)
......
...@@ -17,6 +17,7 @@ module MergeRequests ...@@ -17,6 +17,7 @@ module MergeRequests
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches merge_request.update_project_counter_caches
cleanup_environments(merge_request)
end end
merge_request merge_request
......
...@@ -18,6 +18,7 @@ module MergeRequests ...@@ -18,6 +18,7 @@ module MergeRequests
invalidate_cache_counts(merge_request, users: merge_request.assignees) invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches merge_request.update_project_counter_caches
delete_non_latest_diffs(merge_request) delete_non_latest_diffs(merge_request)
cleanup_environments(merge_request)
end end
private private
......
...@@ -94,16 +94,13 @@ module Projects ...@@ -94,16 +94,13 @@ module Projects
return unless project.lfs_enabled? return unless project.lfs_enabled?
lfs_objects_to_download = Projects::LfsPointers::LfsImportService.new(project).execute result = Projects::LfsPointers::LfsImportService.new(project).execute
lfs_objects_to_download.each do |lfs_download_object| if result[:status] == :error
Projects::LfsPointers::LfsDownloadService.new(project, lfs_download_object) # To avoid aborting the importing process, we silently fail
.execute # if any exception raises.
Gitlab::AppLogger.error("The Lfs import process failed. #{result[:message]}")
end end
rescue => e
# Right now, to avoid aborting the importing process, we silently fail
# if any exception raises.
Rails.logger.error("The Lfs import process failed. #{e.message}")
end end
def import_data def import_data
......
...@@ -21,9 +21,9 @@ module Projects ...@@ -21,9 +21,9 @@ module Projects
# This method accepts two parameters: # This method accepts two parameters:
# - oids: hash of oids to query. The structure is { lfs_file_oid => lfs_file_size } # - oids: hash of oids to query. The structure is { lfs_file_oid => lfs_file_size }
# #
# Returns a hash with the structure { lfs_file_oids => download_link } # Returns an array of LfsDownloadObject
def execute(oids) def execute(oids)
return {} unless project&.lfs_enabled? && remote_uri && oids.present? return [] unless project&.lfs_enabled? && remote_uri && oids.present?
get_download_links(oids) get_download_links(oids)
end end
......
# frozen_string_literal: true # frozen_string_literal: true
# This service manages the whole worflow of discovering the Lfs files in a # This service is responsible of managing the retrieval of the lfs objects,
# repository, linking them to the project and downloading (and linking) the non # and call the service LfsDownloadService, which performs the download
# existent ones. # for each of the retrieved lfs objects
module Projects module Projects
module LfsPointers module LfsPointers
class LfsImportService < BaseService class LfsImportService < BaseService
include Gitlab::Utils::StrongMemoize
HEAD_REV = 'HEAD'.freeze
LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze
LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze
LfsImportError = Class.new(StandardError)
def execute def execute
return {} unless project&.lfs_enabled? return success unless project&.lfs_enabled?
if external_lfs_endpoint? lfs_objects_to_download = LfsObjectDownloadListService.new(project).execute
# If the endpoint host is different from the import_url it means
# that the repo is using a third party service for storing the LFS files.
# In this case, we have to disable lfs in the project
disable_lfs!
return {} lfs_objects_to_download.each do |lfs_download_object|
LfsDownloadService.new(project, lfs_download_object).execute
end end
get_download_links success
rescue LfsDownloadLinkListService::DownloadLinksError => e rescue => e
raise LfsImportError, "The LFS objects download list couldn't be imported. Error: #{e.message}" error(e.message)
end
private
def external_lfs_endpoint?
lfsconfig_endpoint_uri && lfsconfig_endpoint_uri.host != import_uri.host
end
def disable_lfs!
project.update(lfs_enabled: false)
end
# rubocop: disable CodeReuse/ActiveRecord
def get_download_links
existent_lfs = LfsListService.new(project).execute
linked_oids = LfsLinkService.new(project).execute(existent_lfs.keys)
# Retrieving those oids not linked and which we need to download
not_linked_lfs = existent_lfs.except(*linked_oids)
LfsDownloadLinkListService.new(project, remote_uri: current_endpoint_uri).execute(not_linked_lfs)
end
# rubocop: enable CodeReuse/ActiveRecord
def lfsconfig_endpoint_uri
strong_memoize(:lfsconfig_endpoint_uri) do
# Retrieveing the blob data from the .lfsconfig file
data = project.repository.lfsconfig_for(HEAD_REV)
# Parsing the data to retrieve the url
parsed_data = data&.match(LFS_ENDPOINT_PATTERN)
if parsed_data
URI.parse(parsed_data[1]).tap do |endpoint|
endpoint.user ||= import_uri.user
endpoint.password ||= import_uri.password
end
end
end
rescue URI::InvalidURIError
raise LfsImportError, 'Invalid URL in .lfsconfig file'
end
def import_uri
@import_uri ||= URI.parse(project.import_url)
rescue URI::InvalidURIError
raise LfsImportError, 'Invalid project import URL'
end
def current_endpoint_uri
(lfsconfig_endpoint_uri || default_endpoint_uri)
end
# The import url must end with '.git' here we ensure it is
def default_endpoint_uri
@default_endpoint_uri ||= begin
import_uri.dup.tap do |uri|
path = uri.path.gsub(%r(/$), '')
path += '.git' unless path.ends_with?('.git')
uri.path = path + LFS_BATCH_API_ENDPOINT
end
end
end end
end end
end end
......
...@@ -6,9 +6,9 @@ module Projects ...@@ -6,9 +6,9 @@ module Projects
class LfsLinkService < BaseService class LfsLinkService < BaseService
# Accept an array of oids to link # Accept an array of oids to link
# #
# Returns a hash with the same structure with oids linked # Returns an array with the oid of the existent lfs objects
def execute(oids) def execute(oids)
return {} unless project&.lfs_enabled? return [] unless project&.lfs_enabled?
# Search and link existing LFS Object # Search and link existing LFS Object
link_existing_lfs_objects(oids) link_existing_lfs_objects(oids)
......
# frozen_string_literal: true
# This service manages the whole worflow of discovering the Lfs files in a
# repository, linking them to the project and downloading (and linking) the non
# existent ones.
module Projects
module LfsPointers
class LfsObjectDownloadListService < BaseService
include Gitlab::Utils::StrongMemoize
HEAD_REV = 'HEAD'.freeze
LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze
LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze
LfsObjectDownloadListError = Class.new(StandardError)
def execute
return [] unless project&.lfs_enabled?
if external_lfs_endpoint?
# If the endpoint host is different from the import_url it means
# that the repo is using a third party service for storing the LFS files.
# In this case, we have to disable lfs in the project
disable_lfs!
return []
end
# Getting all Lfs pointers already in the database and linking them to the project
linked_oids = LfsLinkService.new(project).execute(lfs_pointers_in_repository.keys)
# Retrieving those oids not present in the database which we need to download
missing_oids = lfs_pointers_in_repository.except(*linked_oids) # rubocop: disable CodeReuse/ActiveRecord
# Downloading the required information and gathering it inside a LfsDownloadObject for each oid
LfsDownloadLinkListService.new(project, remote_uri: current_endpoint_uri).execute(missing_oids)
rescue LfsDownloadLinkListService::DownloadLinksError => e
raise LfsObjectDownloadListError, "The LFS objects download list couldn't be imported. Error: #{e.message}"
end
private
def external_lfs_endpoint?
lfsconfig_endpoint_uri && lfsconfig_endpoint_uri.host != import_uri.host
end
def disable_lfs!
unless project.update(lfs_enabled: false)
raise LfsDownloadLinkListService::DownloadLinksError, "Invalid project state"
end
end
# Retrieves all lfs pointers in the repository
def lfs_pointers_in_repository
@lfs_pointers_in_repository ||= LfsListService.new(project).execute
end
def lfsconfig_endpoint_uri
strong_memoize(:lfsconfig_endpoint_uri) do
# Retrieveing the blob data from the .lfsconfig file
data = project.repository.lfsconfig_for(HEAD_REV)
# Parsing the data to retrieve the url
parsed_data = data&.match(LFS_ENDPOINT_PATTERN)
if parsed_data
URI.parse(parsed_data[1]).tap do |endpoint|
endpoint.user ||= import_uri.user
endpoint.password ||= import_uri.password
end
end
end
rescue URI::InvalidURIError
raise LfsObjectDownloadListError, 'Invalid URL in .lfsconfig file'
end
def import_uri
@import_uri ||= URI.parse(project.import_url)
rescue URI::InvalidURIError
raise LfsObjectDownloadListError, 'Invalid project import URL'
end
def current_endpoint_uri
(lfsconfig_endpoint_uri || default_endpoint_uri)
end
# The import url must end with '.git' here we ensure it is
def default_endpoint_uri
@default_endpoint_uri ||= begin
import_uri.dup.tap do |uri|
path = uri.path.gsub(%r(/$), '')
path += '.git' unless path.ends_with?('.git')
uri.path = path + LFS_BATCH_API_ENDPOINT
end
end
end
end
end
end
...@@ -41,12 +41,11 @@ module Tags ...@@ -41,12 +41,11 @@ module Tags
def build_push_data(tag) def build_push_data(tag)
Gitlab::DataBuilder::Push.build( Gitlab::DataBuilder::Push.build(
project, project: project,
current_user, user: current_user,
tag.dereferenced_target.sha, oldrev: tag.dereferenced_target.sha,
Gitlab::Git::BLANK_SHA, newrev: Gitlab::Git::BLANK_SHA,
"#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", ref: "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}")
[])
end end
end end
end end
...@@ -37,8 +37,8 @@ module Todos ...@@ -37,8 +37,8 @@ module Todos
private private
def enqueue_private_features_worker def enqueue_private_features_worker
project_ids.each do |project_id| projects.each do |project|
TodosDestroyer::PrivateFeaturesWorker.perform_async(project_id, user.id) TodosDestroyer::PrivateFeaturesWorker.perform_async(project.id, user.id)
end end
end end
...@@ -62,9 +62,8 @@ module Todos ...@@ -62,9 +62,8 @@ module Todos
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
override :project_ids
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def project_ids def projects
condition = case entity condition = case entity
when Project when Project
{ id: entity.id } { id: entity.id }
...@@ -72,13 +71,13 @@ module Todos ...@@ -72,13 +71,13 @@ module Todos
{ namespace_id: non_member_groups } { namespace_id: non_member_groups }
end end
Project.where(condition).select(:id) Project.where(condition)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def non_authorized_projects def non_authorized_projects
project_ids.where('id NOT IN (?)', user.authorized_projects.select(:id)) projects.where('id NOT IN (?)', user.authorized_projects.select(:id))
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -110,7 +109,7 @@ module Todos ...@@ -110,7 +109,7 @@ module Todos
authorized_reporter_projects = user authorized_reporter_projects = user
.authorized_projects(Gitlab::Access::REPORTER).select(:id) .authorized_projects(Gitlab::Access::REPORTER).select(:id)
Issue.where(project_id: project_ids, confidential: true) Issue.where(project_id: projects, confidential: true)
.where('project_id NOT IN(?)', authorized_reporter_projects) .where('project_id NOT IN(?)', authorized_reporter_projects)
.where('author_id != ?', user.id) .where('author_id != ?', user.id)
.where('id NOT IN (?)', assigned_ids) .where('id NOT IN (?)', assigned_ids)
......
= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-logging-settings'), html: { class: 'fieldset-form' } do |f| = form_for @application_setting, url: admin_application_settings_path(anchor: 'js-logging-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting) = form_errors(@application_setting)
%p
%strong
NOTE:
These settings will be removed from the UI in a GitLab 12.0 release and made available within gitlab.yml.
The specific client side DSN setting is already handled as a component from a Sentry perspective and will be removed.
In addition, you will be able to define a Sentry Environment to differentiate between multiple deployments. For example, development, staging, and production.
%fieldset %fieldset
.form-group .form-group
.form-check .form-check
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- page_title _('Kubernetes Cluster') - page_title _('Kubernetes Cluster')
- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project - manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
- expanded = Rails.env.test? - expanded = expanded_by_default?
- status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster) - status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path, .edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
......
- breadcrumb_title "General Settings" - breadcrumb_title "General Settings"
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded') } %section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded') }
......
- breadcrumb_title "CI / CD Settings" - breadcrumb_title "CI / CD Settings"
- page_title "CI / CD" - page_title "CI / CD"
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) } %section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
%div %div
- if Gitlab::CurrentSettings.help_page_text.present? - if Gitlab::CurrentSettings.help_page_text.present?
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text) .prepend-top-default.md
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr %hr
%h1 %h1
......
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.settings.no-animate#cleanup{ class: ('expanded' if expanded) } %section.settings.no-animate#cleanup{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.settings.no-animate#default-branch-settings{ class: ('expanded' if expanded) } %section.settings.no-animate#default-branch-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.qa-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded) } %section.qa-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
%h4 %h4
......
- breadcrumb_title _("General Settings") - breadcrumb_title _("General Settings")
- page_title _("General") - page_title _("General")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.settings.general-settings.no-animate.expanded#js-general-settings %section.settings.general-settings.no-animate.expanded#js-general-settings
.settings-header .settings-header
......
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
- presented_labels_sorted_by_title(issue.labels, issue.project).each do |label| - presented_labels_sorted_by_title(issue.labels, issue.project).each do |label|
= link_to_label(label, css_class: 'label-link') = link_to_label(label, css_class: 'label-link')
= render_if_exists "projects/issues/issue_weight", issue: issue
.issuable-meta .issuable-meta
%ul.controls %ul.controls
- if issue.closed? - if issue.closed?
......
- time_format = '%b %e, %Y %l:%M%P %Z%z'
- if merge_request.merged?
- mr_status_date = merge_request.merged_at
- mr_status_title = _('Merged')
- mr_status_icon = 'merge'
- mr_status_class = 'merged'
- elsif merge_request.closed?
- mr_status_date = merge_request.closed_event&.created_at
- mr_status_title = _('Closed')
- mr_status_icon = 'issue-close'
- mr_status_class = 'closed'
- else
- mr_status_date = merge_request.created_at
- mr_status_title = mr_status_date ? _('Opened') : _('Open')
- mr_status_icon = 'issue-open-m'
- mr_status_class = 'open'
- if mr_status_date
- mr_status_tooltip = "<div><span class=\"bold\">#{mr_status_title}</span> #{time_ago_in_words(mr_status_date)} ago</div><span class=\"text-tertiary\">#{l(mr_status_date.to_time, format: time_format)}</span>"
- else
- mr_status_tooltip = "<div><span class=\"bold\">#{mr_status_title}</span></div>"
%span.mr-status-wrapper.suggestion-help-hover{ class: css_class, data: { toggle: 'tooltip', placement: 'bottom', html: 'true', title: mr_status_tooltip } }
= sprite_icon(mr_status_icon, size: 16, css_class: "merge-request-status #{mr_status_class}")
- expanded = Rails.env.test? - expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) } %section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
= _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link } = _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link }
%p %p
= _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.') = _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.')
= render_if_exists 'projects/new_ci_cd_banner_external_repo'
%p %p
- pages_getting_started_guide = link_to _('Pages getting started guide'), help_page_path("user/project/pages/getting_started_part_two", anchor: "fork-a-project-to-get-started-from"), target: '_blank' - pages_getting_started_guide = link_to _('Pages getting started guide'), help_page_path("user/project/pages/getting_started_part_two", anchor: "fork-a-project-to-get-started-from"), target: '_blank'
= _('Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}.').html_safe % { pages_getting_started_guide: pages_getting_started_guide } = _('Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}.').html_safe % { pages_getting_started_guide: pages_getting_started_guide }
...@@ -42,6 +43,7 @@ ...@@ -42,6 +43,7 @@
%a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' } %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
%span.d-none.d-sm-block Import project %span.d-none.d-sm-block Import project
%span.d-block.d-sm-none Import %span.d-block.d-sm-none Import
= render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab
.tab-content.gitlab-tab-content .tab-content.gitlab-tab-content
.tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' } .tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' }
...@@ -68,6 +70,8 @@ ...@@ -68,6 +70,8 @@
%h4 No import options available %h4 No import options available
%p Contact an administrator to enable options for importing your project. %p Contact an administrator to enable options for importing your project.
= render_if_exists 'projects/new_ci_cd_only_project_pane', active_tab: active_tab
.save-project-loader.d-none .save-project-loader.d-none
.center .center
%h2 %h2
......
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.qa-protected-branches-settings.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded) } %section.qa-protected-branches-settings.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
- expanded = Rails.env.test? - expanded = expanded_by_default?
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) } %section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- page_title _("CI / CD Settings") - page_title _("CI / CD Settings")
- page_title _("CI / CD") - page_title _("CI / CD")
- expanded = Rails.env.test? - expanded = expanded_by_default?
- general_expanded = @project.errors.empty? ? expanded : true - general_expanded = @project.errors.empty? ? expanded : true
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) } %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
- cronjob:gitlab_usage_ping - cronjob:gitlab_usage_ping
- cronjob:import_export_project_cleanup - cronjob:import_export_project_cleanup
- cronjob:pages_domain_verification_cron - cronjob:pages_domain_verification_cron
- cronjob:pages_domain_removal_cron
- cronjob:pipeline_schedule - cronjob:pipeline_schedule
- cronjob:prune_old_events - cronjob:prune_old_events
- cronjob:remove_expired_group_links - cronjob:remove_expired_group_links
...@@ -32,6 +33,8 @@ ...@@ -32,6 +33,8 @@
- gcp_cluster:cluster_wait_for_ingress_ip_address - gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:cluster_configure - gcp_cluster:cluster_configure
- gcp_cluster:cluster_project_configure - gcp_cluster:cluster_project_configure
- gcp_cluster:clusters_applications_wait_for_uninstall_app
- gcp_cluster:clusters_applications_uninstall
- github_import_advance_stage - github_import_advance_stage
- github_importer:github_import_import_diff_note - github_importer:github_import_import_diff_note
......
# frozen_string_literal: true
module Clusters
module Applications
class UninstallWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::UninstallService.new(app).execute
end
end
end
end
end
# frozen_string_literal: true
module Clusters
module Applications
class WaitForUninstallAppWorker
include ApplicationWorker
include ClusterQueue
include ClusterApplications
INTERVAL = 10.seconds
TIMEOUT = 20.minutes
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::CheckUninstallProgressService.new(app).execute
end
end
end
end
end
# frozen_string_literal: true
class PagesDomainRemovalCronWorker
include ApplicationWorker
include CronjobQueue
def perform
return unless Feature.enabled?(:remove_disabled_domains)
PagesDomain.for_removal.find_each do |domain|
domain.destroy!
rescue => e
Raven.capture_exception(e)
end
end
end
...@@ -3,20 +3,26 @@ ...@@ -3,20 +3,26 @@
class PipelineScheduleWorker class PipelineScheduleWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
include ::Gitlab::ExclusiveLeaseHelpers
EXCLUSIVE_LOCK_KEY = 'pipeline_schedules:run:lock'
LOCK_TIMEOUT = 50.minutes
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform def perform
Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now) in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
.preload(:owner, :project).find_each do |schedule| Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
.preload(:owner, :project).find_each do |schedule|
Ci::CreatePipelineService.new(schedule.project,
schedule.owner, schedule.schedule_next_run!
ref: schedule.ref)
.execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule) Ci::CreatePipelineService.new(schedule.project,
rescue => e schedule.owner,
error(schedule, e) ref: schedule.ref)
ensure .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
schedule.schedule_next_run! rescue => e
error(schedule, e)
end
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
---
title: Format extra help page text like wiki
merge_request: 26782
author: Bastian Blank
type: fixed
---
title: Fix base domain help text update
merge_request: 27746
author:
type: fixed
---
title: Add initial GraphQL query for Groups
merge_request: 27492
author:
type: added
---
title: Allow Sentry configuration to be passed on gitlab.yml
merge_request: 27091
author: Roger Meier
type: added
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