Commit 17e8e8d3 authored by John T Skarbek's avatar John T Skarbek

Merge remote-tracking branch 'origin/master'

parents 076d199d 41fed29a
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
......@@ -28,6 +28,8 @@ stages:
- prepare
- merge
- test
- review
- qa
- post-test
- pages
- post-cleanup
......
......@@ -9,7 +9,7 @@ cloud-native-image:
cache: {}
when: manual
script:
- gem install gitlab --no-document
- install_gitlab_gem
- CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
only:
- tags@gitlab-org/gitlab-ce
......
......@@ -16,7 +16,7 @@
gitlab:assets:compile:
<<: *assets-compile-cache
extends: .dedicated-no-docs-pull-cache-job
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.21-chrome-71.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.21-chrome-73.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
dependencies:
- setup-test-env
services:
......@@ -38,6 +38,10 @@ gitlab:assets:compile:
- bundle exec rake gitlab:assets:compile
- time scripts/build_assets_image
- scripts/clean-old-cached-assets
# Play dependent manual jobs
- install_api_client_dependencies_with_apt
- play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
- play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
artifacts:
name: webpack-report
expire_in: 31d
......@@ -103,7 +107,7 @@ gitlab:ui:visual:
- $CI_COMMIT_MESSAGE =~ /\[skip visual\]/i
artifacts:
paths:
- tests/__image_snapshots__/
- gitlab-ui/tests/__image_snapshots__/
when: always
karma:
......
package-and-qa:
image: ruby:2.5-alpine
stage: test
stage: qa
when: manual
before_script: []
dependencies: []
cache: {}
variables:
GIT_DEPTH: "1"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
retry: 0
script:
- apk add --update openssl curl jq
- gem install gitlab --no-document
- source ./scripts/review_apps/review-apps.sh
- wait_for_job_to_be_done "gitlab:assets:compile"
- source scripts/utils.sh
- install_gitlab_gem
- ./scripts/trigger-build omnibus
when: manual
only:
- /.+/@gitlab-org/gitlab-ce
- /.+/@gitlab-org/gitlab-ee
......@@ -86,7 +86,7 @@
.rspec-metadata-pg-10: &rspec-metadata-pg-10
<<: *rspec-metadata
<<: *use-pg-10
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-71.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29"
.rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata
......
......@@ -26,12 +26,10 @@
extends: .dedicated-runner
<<: *review-only
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: test
cache: {}
dependencies: []
environment: &review-environment
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
before_script:
- source scripts/utils.sh
.review-docker: &review-docker
<<: *review-base
......@@ -42,18 +40,13 @@
- gitlab-org
- docker
variables: &review-docker-variables
GIT_DEPTH: "1"
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly"
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
before_script: []
build-qa-image:
<<: *review-docker
variables:
<<: *review-docker-variables
GIT_DEPTH: "20"
stage: prepare
script:
- time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
......@@ -63,16 +56,14 @@ build-qa-image:
.review-build-cng-base: &review-build-cng-base
image: ruby:2.5-alpine
stage: test
before_script: []
when: manual
before_script:
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- install_gitlab_gem
dependencies: []
cache: {}
variables:
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
script:
- apk add --update openssl curl jq
- gem install gitlab --no-document
- source ./scripts/review_apps/review-apps.sh
- wait_for_job_to_be_done "gitlab:assets:compile"
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
review-build-cng:
......@@ -85,26 +76,32 @@ schedule:review-build-cng:
.review-deploy-base: &review-deploy-base
<<: *review-base
stage: review
retry: 2
allow_failure: true
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
environment:
<<: *review-environment
environment: &review-environment
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
before_script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- apk update && apk add jq
- gem install gitlab --no-document
- source ./scripts/review_apps/review-apps.sh
- echo "${CI_ENVIRONMENT_URL}" > review_app_url.txt
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh
script:
- wait_for_job_to_be_done "review-build-cng"
- perform_review_app_deployment
artifacts:
paths:
- review_app_url.txt
expire_in: 2 days
when: always
review-deploy:
<<: *review-deploy-base
......@@ -113,15 +110,29 @@ schedule:review-deploy:
<<: *review-deploy-base
<<: *review-schedules-only
script:
- wait_for_job_to_be_done "schedule:review-build-cng"
- perform_review_app_deployment
review-stop:
<<: *review-base
stage: review
when: manual
allow_failure: true
variables:
GIT_DEPTH: "1"
environment:
<<: *review-environment
action: stop
script:
- source scripts/review_apps/review-apps.sh
- delete
- cleanup
.review-qa-base: &review-qa-base
<<: *review-docker
stage: qa
allow_failure: true
variables:
<<: *review-docker-variables
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
GITLAB_USERNAME: "root"
......@@ -131,40 +142,45 @@ schedule:review-deploy:
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
QA_DEBUG: "true"
dependencies:
- review-deploy
artifacts:
paths:
- ./qa/gitlab-qa-run-*
expire_in: 7 days
when: always
before_script:
- echo "${QA_IMAGE}"
- export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- apk update && apk add curl jq
- source ./scripts/review_apps/review-apps.sh
- echo "${QA_IMAGE}"
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
review-qa-smoke:
<<: *review-qa-base
retry: 2
script:
- wait_for_job_to_be_done "review-deploy"
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
review-qa-all:
<<: *review-qa-base
when: manual
script:
- wait_for_job_to_be_done "review-deploy"
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
when: manual
.review-performance-base: &review-performance-base
<<: *review-qa-base
script:
- wait_for_job_to_be_done "review-deploy"
stage: qa
before_script:
- export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- mkdir -p gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
- mkdir -p sitespeed-results
script:
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
after_script:
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
......@@ -175,42 +191,26 @@ review-qa-all:
review-performance:
<<: *review-performance-base
review-stop:
<<: *review-base
extends: .single-script-job-dedicated-runner
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
allow_failure: true
variables:
SCRIPT_NAME: "review_apps/review-apps.sh"
when: manual
environment:
<<: *review-environment
action: stop
script:
- source $(basename "${SCRIPT_NAME}")
- delete
- cleanup
schedule:review-performance:
<<: *review-performance-base
<<: *review-schedules-only
dependencies:
- schedule:review-deploy
schedule:review-cleanup:
<<: *review-base
<<: *review-schedules-only
stage: build
allow_failure: true
variables:
GIT_DEPTH: "1"
environment:
name: review/auto-cleanup
action: stop
before_script:
- gem install gitlab --no-document
- source scripts/utils.sh
- install_gitlab_gem
script:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
schedule:review-performance:
<<: *review-performance-base
<<: *review-schedules-only
script:
- wait_for_job_to_be_done "schedule:review-deploy"
danger-review:
extends: .dedicated-pull-cache-job
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
......
......@@ -2,7 +2,7 @@
"plugins":[
"./scripts/frontend/stylelint/stylelint-duplicate-selectors.js",
"./scripts/frontend/stylelint/stylelint-utility-classes.js",
"stylelint-scss",
"stylelint-scss"
],
"rules":{
"at-rule-blacklist":[
......@@ -64,7 +64,7 @@
"number-leading-zero":"always",
"number-no-trailing-zeros":true,
"property-no-unknown":true,
"property-no-vendor-prefix":true,
"property-no-vendor-prefix": [true, { "ignoreProperties": ["user-select"] }],
"rule-empty-line-before":[
"always-multi-line",
{
......@@ -94,7 +94,7 @@
{
"message":"Selector should be written in lowercase with hyphens (selector-class-pattern)",
"severity": "warning"
},
}
],
"selector-list-comma-newline-after":"always",
"selector-max-compound-selectors":[3, { "severity": "warning" }],
......@@ -104,8 +104,8 @@
"selector-pseudo-element-no-unknown":true,
"shorthand-property-no-redundant-values":true,
"string-quotes":"single",
"value-no-vendor-prefix":[true, { ignoreValues: ["sticky"] }],
"value-no-vendor-prefix":[true, { "ignoreValues": ["sticky"] }],
"stylelint-gitlab/duplicate-selectors":[true,{ "severity": "warning" }],
"stylelint-gitlab/utility-classes":[true,{ "severity": "warning" }],
"stylelint-gitlab/utility-classes":[true,{ "severity": "warning" }]
}
}
......@@ -79,6 +79,7 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
# GraphQL API
gem 'graphql', '~> 1.8.0'
gem 'graphiql-rails', '~> 1.4.10'
gem 'apollo_upload_server', '~> 2.0.0.beta3'
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'
......
......@@ -52,6 +52,9 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
aes_key_wrap (1.0.1)
akismet (2.0.0)
apollo_upload_server (2.0.0.beta.3)
graphql (>= 1.8)
rails (>= 4.2)
arel (8.0.0)
asana (0.8.1)
faraday (~> 0.9)
......@@ -988,6 +991,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
apollo_upload_server (~> 2.0.0.beta3)
asana (~> 0.8.1)
asciidoctor (~> 1.5.8)
asciidoctor-plantuml (= 0.0.8)
......
......@@ -168,7 +168,7 @@ export default {
</script>
<template>
<div>
<div class="d-flex board-card-header">
<div class="d-flex board-card-header" dir="auto">
<h4 class="board-card-title append-bottom-0 prepend-top-0">
<icon
v-if="issue.confidential"
......
import Visibility from 'visibilityjs';
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
import {
APPLICATION_STATUS,
REQUEST_SUBMITTED,
REQUEST_FAILURE,
UPGRADE_REQUESTED,
UPGRADE_REQUEST_FAILURE,
INGRESS,
INGRESS_DOMAIN_SUFFIX,
} from './constants';
import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX } from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons';
Vue.use(GlToast);
/**
* Cluster page has 2 separate parts:
* Toggle button and applications section
......@@ -134,7 +129,6 @@ export default class Clusters {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
eventHub.$on('upgradeFailed', appId => this.upgradeFailed(appId));
eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
......@@ -144,7 +138,6 @@ export default class Clusters {
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
eventHub.$off('upgradeApplication', this.upgradeApplication);
eventHub.$off('upgradeFailed', this.upgradeFailed);
eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname');
......@@ -258,12 +251,13 @@ export default class Clusters {
installApplication(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUBMITTED);
this.store.updateAppProperty(appId, 'requestReason', null);
this.store.updateAppProperty(appId, 'statusReason', null);
this.store.installApplication(appId);
return this.service.installApplication(appId, data.params).catch(() => {
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
this.store.notifyInstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
......@@ -274,17 +268,15 @@ export default class Clusters {
upgradeApplication(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUESTED);
this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
this.service.installApplication(appId, data.params).catch(() => this.upgradeFailed(appId));
}
upgradeFailed(appId) {
this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUEST_FAILURE);
this.store.updateApplication(appId);
this.service.installApplication(appId, data.params).catch(() => {
this.store.notifyUpdateFailure(appId);
});
}
dismissUpgradeSuccess(appId) {
this.store.updateAppProperty(appId, 'requestStatus', null);
this.store.acknowledgeSuccessfulUpdate(appId);
}
toggleIngressDomainHelpText(ingressPreviousState, ingressNewState) {
......
......@@ -8,12 +8,7 @@ import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import UninstallApplicationButton from './uninstall_application_button.vue';
import {
APPLICATION_STATUS,
REQUEST_SUBMITTED,
REQUEST_FAILURE,
UPGRADE_REQUESTED,
} from '../constants';
import { APPLICATION_STATUS } from '../constants';
export default {
components: {
......@@ -63,10 +58,6 @@ export default {
type: String,
required: false,
},
requestStatus: {
type: String,
required: false,
},
requestReason: {
type: String,
required: false,
......@@ -76,6 +67,11 @@ export default {
required: false,
default: false,
},
installFailed: {
type: Boolean,
required: false,
default: false,
},
version: {
type: String,
required: false,
......@@ -88,6 +84,21 @@ export default {
type: Boolean,
required: false,
},
updateSuccessful: {
type: Boolean,
required: false,
default: false,
},
updateFailed: {
type: Boolean,
required: false,
default: false,
},
updateAcknowledged: {
type: Boolean,
required: false,
default: true,
},
installApplicationRequestParams: {
type: Object,
required: false,
......@@ -102,21 +113,12 @@ export default {
return Object.values(APPLICATION_STATUS).includes(this.status);
},
isInstalling() {
return (
this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING ||
(this.requestStatus === REQUEST_SUBMITTED && !this.statusReason && !this.installed)
);
return this.status === APPLICATION_STATUS.INSTALLING;
},
canInstall() {
if (this.isInstalling) {
return false;
}
return (
this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
this.status === APPLICATION_STATUS.INSTALLABLE ||
this.status === APPLICATION_STATUS.ERROR ||
this.isUnknownStatus
);
},
......@@ -137,7 +139,7 @@ export default {
return !this.installed || !this.uninstallable;
},
installButtonLoading() {
return !this.status || this.status === APPLICATION_STATUS.SCHEDULED || this.isInstalling;
return !this.status || this.isInstalling;
},
installButtonDisabled() {
// Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
......@@ -168,19 +170,13 @@ export default {
manageButtonLabel() {
return s__('ClusterIntegration|Manage');
},
hasError() {
return (
!this.isInstalling &&
(this.status === APPLICATION_STATUS.ERROR || this.requestStatus === REQUEST_FAILURE)
);
},
generalErrorDescription() {
return sprintf(s__('ClusterIntegration|Something went wrong while installing %{title}'), {
title: this.title,
});
},
versionLabel() {
if (this.upgradeFailed) {
if (this.updateFailed) {
return s__('ClusterIntegration|Upgrade failed');
} else if (this.isUpgrading) {
return s__('ClusterIntegration|Upgrading');
......@@ -188,19 +184,6 @@ export default {
return s__('ClusterIntegration|Upgraded');
},
upgradeRequested() {
return this.requestStatus === UPGRADE_REQUESTED;
},
upgradeSuccessful() {
return this.status === APPLICATION_STATUS.UPDATED;
},
upgradeFailed() {
if (this.isUpgrading) {
return false;
}
return this.status === APPLICATION_STATUS.UPDATE_ERRORED;
},
upgradeFailureDescription() {
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
},
......@@ -211,11 +194,11 @@ export default {
},
upgradeButtonLabel() {
let label;
if (this.upgradeAvailable && !this.upgradeFailed && !this.isUpgrading) {
if (this.upgradeAvailable && !this.updateFailed && !this.isUpgrading) {
label = s__('ClusterIntegration|Upgrade');
} else if (this.isUpgrading) {
label = s__('ClusterIntegration|Updating');
} else if (this.upgradeFailed) {
} else if (this.updateFailed) {
label = s__('ClusterIntegration|Retry update');
}
......@@ -223,24 +206,19 @@ export default {
},
isUpgrading() {
// Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
return (
this.status === APPLICATION_STATUS.UPDATING ||
(this.upgradeRequested && !this.upgradeSuccessful)
);
return this.status === APPLICATION_STATUS.UPDATING;
},
shouldShowUpgradeDetails() {
// This method only returns true when;
// Upgrade was successful OR Upgrade failed
// AND new upgrade is unavailable AND version information is present.
return (
(this.upgradeSuccessful || this.upgradeFailed) && !this.upgradeAvailable && this.version
);
return (this.updateSuccessful || this.updateFailed) && !this.upgradeAvailable && this.version;
},
},
watch: {
status() {
if (this.status === APPLICATION_STATUS.UPDATE_ERRORED) {
eventHub.$emit('upgradeFailed', this.id);
updateSuccessful() {
if (this.updateSuccessful) {
this.$toast.show(this.upgradeSuccessDescription);
}
},
},
......@@ -257,9 +235,6 @@ export default {
params: this.installApplicationRequestParams,
});
},
dismissUpgradeSuccess() {
eventHub.$emit('dismissUpgradeSuccess', this.id);
},
},
};
</script>
......@@ -297,7 +272,7 @@ export default {
</strong>
<slot name="description"></slot>
<div
v-if="hasError || isUnknownStatus"
v-if="installFailed || isUnknownStatus"
class="cluster-application-error text-danger prepend-top-10"
>
<p class="js-cluster-application-general-error-message append-bottom-0">
......@@ -318,10 +293,10 @@ export default {
class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
>
{{ versionLabel }}
<span v-if="upgradeSuccessful">to</span>
<span v-if="updateSuccessful">to</span>
<gl-link
v-if="upgradeSuccessful"
v-if="updateSuccessful"
:href="chartRepo"
target="_blank"
class="js-cluster-application-upgrade-version"
......@@ -330,24 +305,13 @@ export default {
</div>
<div
v-if="upgradeFailed && !isUpgrading"
v-if="updateFailed && !isUpgrading"
class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-upgrade-failure-message"
>
{{ upgradeFailureDescription }}
</div>
<div
v-if="upgradeRequested && upgradeSuccessful"
class="bs-callout bs-callout-success cluster-application-banner mt-2 mb-0 p-0 pl-3"
>
{{ upgradeSuccessDescription }}
<button class="close cluster-application-banner-close" @click="dismissUpgradeSuccess">
&times;
</button>
</div>
<loading-button
v-if="upgradeAvailable || upgradeFailed || isUpgrading"
v-if="upgradeAvailable || updateFailed || isUpgrading"
class="btn btn-primary js-cluster-application-upgrade-button mt-2"
:loading="isUpgrading"
:disabled="isUpgrading"
......@@ -361,9 +325,9 @@ export default {
role="gridcell"
>
<div v-if="showManageButton" class="btn-group table-action-buttons">
<a :href="manageLink" :class="{ disabled: disabled }" class="btn">{{
manageButtonLabel
}}</a>
<a :href="manageLink" :class="{ disabled: disabled }" class="btn">
{{ manageButtonLabel }}
</a>
</div>
<div class="btn-group table-action-buttons">
<loading-button
......
......@@ -226,7 +226,7 @@ export default {
s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster.
Helm Tiller is required to install any of the following applications.`)
}}
<a :href="helpPath"> {{ __('More information') }} </a>
<a :href="helpPath">{{ __('More information') }}</a>
</p>
<div class="cluster-application-list prepend-top-10">
......@@ -239,6 +239,7 @@ export default {
:request-status="applications.helm.requestStatus"
:request-reason="applications.helm.requestReason"
:installed="applications.helm.installed"
:install-failed="applications.helm.installFailed"
class="rounded-top"
title-link="https://docs.helm.sh/"
>
......@@ -267,6 +268,7 @@ export default {
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
:installed="applications.ingress.installed"
:install-failed="applications.ingress.installFailed"
:disabled="!helmInstalled"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
>
......@@ -281,9 +283,7 @@ export default {
<template v-if="ingressInstalled">
<div class="form-group">
<label for="ingress-endpoint">
{{ s__('ClusterIntegration|Ingress Endpoint') }}
</label>
<label for="ingress-endpoint">{{ s__('ClusterIntegration|Ingress Endpoint') }}</label>
<div v-if="ingressExternalEndpoint" class="input-group">
<input
id="ingress-endpoint"
......@@ -324,7 +324,6 @@ export default {
the process of being assigned. Please check your Kubernetes
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}}
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
{{ __('More information') }}
</a>
......@@ -344,6 +343,7 @@ export default {
:request-status="applications.cert_manager.requestStatus"
:request-reason="applications.cert_manager.requestReason"
:installed="applications.cert_manager.installed"
:install-failed="applications.cert_manager.installFailed"
:install-application-request-params="{ email: applications.cert_manager.email }"
:disabled="!helmInstalled"
title-link="https://cert-manager.readthedocs.io/en/latest/#"
......@@ -372,9 +372,8 @@ export default {
href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email"
target="_blank"
rel="noopener noreferrer"
>{{ __('More information') }}</a
>
{{ __('More information') }}
</a>
</p>
</div>
</div>
......@@ -391,6 +390,7 @@ export default {
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
:installed="applications.prometheus.installed"
:install-failed="applications.prometheus.installFailed"
:disabled="!helmInstalled"
title-link="https://prometheus.io/docs/introduction/overview/"
>
......@@ -408,6 +408,9 @@ export default {
:chart-repo="applications.runner.chartRepo"
:upgrade-available="applications.runner.upgradeAvailable"
:installed="applications.runner.installed"
:install-failed="applications.runner.installFailed"
:update-successful="applications.runner.updateSuccessful"
:update-failed="applications.runner.updateFailed"
:disabled="!helmInstalled"
title-link="https://docs.gitlab.com/runner/"
>
......@@ -430,6 +433,7 @@ export default {
:request-status="applications.jupyter.requestStatus"
:request-reason="applications.jupyter.requestReason"
:installed="applications.jupyter.installed"
:install-failed="applications.jupyter.installFailed"
:install-application-request-params="{ hostname: applications.jupyter.hostname }"
:disabled="!helmInstalled"
title-link="https://jupyterhub.readthedocs.io/en/stable/"
......@@ -447,9 +451,7 @@ export default {
<template v-if="ingressExternalEndpoint">
<div class="form-group">
<label for="jupyter-hostname">
{{ s__('ClusterIntegration|Jupyter Hostname') }}
</label>
<label for="jupyter-hostname">{{ s__('ClusterIntegration|Jupyter Hostname') }}</label>
<div class="input-group">
<input
......@@ -490,8 +492,10 @@ export default {
:request-status="applications.knative.requestStatus"
:request-reason="applications.knative.requestReason"
:installed="applications.knative.installed"
:install-failed="applications.knative.installFailed"
:install-application-request-params="{ hostname: applications.knative.hostname }"
:disabled="!helmInstalled"
v-bind="applications.knative"
title-link="https://github.com/knative/docs"
>
<div slot="description">
......@@ -523,9 +527,7 @@ export default {
class="form-group col-sm-12 mb-0"
>
<label for="knative-domainname">
<strong>
{{ s__('ClusterIntegration|Knative Domain Name:') }}
</strong>
<strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
</label>
<input
id="knative-domainname"
......@@ -538,9 +540,7 @@ export default {
<template v-if="knativeInstalled">
<div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
<label for="knative-endpoint">
<strong>
{{ s__('ClusterIntegration|Knative Endpoint:') }}
</strong>
<strong>{{ s__('ClusterIntegration|Knative Endpoint:') }}</strong>
</label>
<div v-if="knativeExternalEndpoint" class="input-group">
<input
......
......@@ -7,6 +7,7 @@ export const CLUSTER_TYPE = {
// These need to match what is returned from the server
export const APPLICATION_STATUS = {
NO_STATUS: null,
NOT_INSTALLABLE: 'not_installable',
INSTALLABLE: 'installable',
SCHEDULED: 'scheduled',
......@@ -27,17 +28,13 @@ export const APPLICATION_STATUS = {
export const APPLICATION_INSTALLED_STATUSES = [
APPLICATION_STATUS.INSTALLED,
APPLICATION_STATUS.UPDATING,
APPLICATION_STATUS.UPDATED,
APPLICATION_STATUS.UPDATE_ERRORED,
APPLICATION_STATUS.UNINSTALLING,
APPLICATION_STATUS.UNINSTALL_ERRORED,
];
// These are only used client-side
export const REQUEST_SUBMITTED = 'request-submitted';
export const REQUEST_FAILURE = 'request-failure';
export const UPGRADE_REQUESTED = 'upgrade-requested';
export const UPGRADE_REQUEST_FAILURE = 'upgrade-request-failure';
export const UPDATE_EVENT = 'update';
export const INSTALL_EVENT = 'install';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
......
import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT } from '../constants';
const {
NO_STATUS,
SCHEDULED,
NOT_INSTALLABLE,
INSTALLABLE,
INSTALLING,
INSTALLED,
ERROR,
UPDATING,
UPDATED,
UPDATE_ERRORED,
} = APPLICATION_STATUS;
const applicationStateMachine = {
/* When the application initially loads, it will have `NO_STATUS`
* It will transition from `NO_STATUS` once the async backend call is completed
*/
[NO_STATUS]: {
on: {
[SCHEDULED]: {
target: INSTALLING,
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
},
[INSTALLABLE]: {
target: INSTALLABLE,
},
[INSTALLING]: {
target: INSTALLING,
},
[INSTALLED]: {
target: INSTALLED,
},
[ERROR]: {
target: INSTALLABLE,
effects: {
installFailed: true,
},
},
[UPDATING]: {
target: UPDATING,
},
[UPDATED]: {
target: INSTALLED,
},
[UPDATE_ERRORED]: {
target: INSTALLED,
effects: {
updateFailed: true,
},
},
},
},
[NOT_INSTALLABLE]: {
on: {
[INSTALLABLE]: {
target: INSTALLABLE,
},
},
},
[INSTALLABLE]: {
on: {
[INSTALL_EVENT]: {
target: INSTALLING,
effects: {
installFailed: false,
},
},
// This is possible in artificial environments for E2E testing
[INSTALLED]: {
target: INSTALLED,
},
},
},
[INSTALLING]: {
on: {
[INSTALLED]: {
target: INSTALLED,
},
[ERROR]: {
target: INSTALLABLE,
effects: {
installFailed: true,
},
},
},
},
[INSTALLED]: {
on: {
[UPDATE_EVENT]: {
target: UPDATING,
effects: {
updateFailed: false,
updateSuccessful: false,
},
},
},
},
[UPDATING]: {
on: {
[UPDATED]: {
target: INSTALLED,
effects: {
updateSuccessful: true,
updateAcknowledged: false,
},
},
[UPDATE_ERRORED]: {
target: INSTALLED,
effects: {
updateFailed: true,
},
},
},
},
};
/**
* Determines an application new state based on the application current state
* and an event. If the application current state cannot handle a given event,
* the current state is returned.
*
* @param {*} application
* @param {*} event
*/
const transitionApplicationState = (application, event) => {
const newState = applicationStateMachine[application.status].on[event];
return newState
? {
...application,
status: newState.target,
...newState.effects,
}
: application;
};
export default transitionApplicationState;
......@@ -7,7 +7,11 @@ import {
CERT_MANAGER,
RUNNER,
APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS,
INSTALL_EVENT,
UPDATE_EVENT,
} from '../constants';
import transitionApplicationState from '../services/application_state_machine';
const isApplicationInstalled = appStatus => APPLICATION_INSTALLED_STATUSES.includes(appStatus);
......@@ -15,8 +19,8 @@ const applicationInitialState = {
status: null,
statusReason: null,
requestReason: null,
requestStatus: null,
installed: false,
installFailed: false,
};
export default class ClusterStore {
......@@ -49,6 +53,9 @@ export default class ClusterStore {
version: null,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
upgradeAvailable: null,
updateAcknowledged: true,
updateSuccessful: false,
updateFailed: false,
},
prometheus: {
...applicationInitialState,
......@@ -93,6 +100,32 @@ export default class ClusterStore {
this.state.statusReason = reason;
}
installApplication(appId) {
this.handleApplicationEvent(appId, INSTALL_EVENT);
}
notifyInstallFailure(appId) {
this.handleApplicationEvent(appId, APPLICATION_STATUS.ERROR);
}
updateApplication(appId) {
this.handleApplicationEvent(appId, UPDATE_EVENT);
}
notifyUpdateFailure(appId) {
this.handleApplicationEvent(appId, APPLICATION_STATUS.UPDATE_ERRORED);
}
handleApplicationEvent(appId, event) {
const currentAppState = this.state.applications[appId];
this.state.applications[appId] = transitionApplicationState(currentAppState, event);
}
acknowledgeSuccessfulUpdate(appId) {
this.state.applications[appId].updateAcknowledged = true;
}
updateAppProperty(appId, prop, value) {
this.state.applications[appId][prop] = value;
}
......@@ -109,12 +142,16 @@ export default class ClusterStore {
version,
update_available: upgradeAvailable,
} = serverAppEntry;
const currentApplicationState = this.state.applications[appId] || {};
const nextApplicationState = transitionApplicationState(currentApplicationState, status);
this.state.applications[appId] = {
...(this.state.applications[appId] || {}),
status,
...currentApplicationState,
...nextApplicationState,
statusReason,
installed: isApplicationInstalled(status),
installed: isApplicationInstalled(nextApplicationState.status),
// Make sure uninstallable is always false until this feature is unflagged
uninstallable: false,
};
if (appId === INGRESS) {
......
......@@ -4,6 +4,11 @@ import { generateTreeList } from '../store/utils';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => {
const { data } = e;
if (data === undefined) {
return;
}
const { treeEntries, tree } = generateTreeList(data);
// eslint-disable-next-line no-restricted-globals
......
import $ from 'jquery';
import { __ } from '~/locale';
export default class TransferDropdown {
constructor() {
......@@ -13,7 +14,7 @@ export default class TransferDropdown {
}
buildDropdown() {
const extraOptions = [{ id: '', text: 'No parent group' }, 'divider'];
const extraOptions = [{ id: '', text: __('No parent group') }, 'divider'];
this.groupDropdown.glDropdown({
selectable: true,
......
......@@ -108,6 +108,7 @@ export default {
:placeholder="placeholder"
:value="text"
class="note-textarea ide-commit-message-textarea"
dir="auto"
name="commit-message"
@scroll="handleScroll"
@input="onInput"
......
......@@ -74,7 +74,7 @@ export default {
<gl-loading-icon
v-if="isLoadingRepos"
class="js-loading-button-icon import-projects-loading-icon"
:size="4"
size="md"
/>
<div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive">
<table class="table import-table">
......
......@@ -7,6 +7,8 @@ import mutations from './mutations';
Vue.use(Vuex);
export { state, actions, getters, mutations };
export default () =>
new Vuex.Store({
state: state(),
......
......@@ -149,6 +149,7 @@ export default {
v-model="descriptionText"
:data-update-url="updateUrl"
class="hidden js-task-list-field"
dir="auto"
>
</textarea>
......
......@@ -53,6 +53,7 @@ export default {
v-model="formState.description"
class="note-textarea js-gfm-input js-autosize markdown-area
qa-description-textarea"
dir="auto"
data-supports-quick-actions="false"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
......
......@@ -20,6 +20,7 @@ export default {
ref="input"
v-model="formState.title"
class="form-control qa-title-input"
dir="auto"
type="text"
placeholder="Title"
aria-label="Title"
......
......@@ -72,6 +72,7 @@ export default {
'issue-realtime-trigger-pulse': pulseAnimation,
}"
class="title"
dir="auto"
v-html="titleHtml"
></h2>
<button
......
......@@ -135,6 +135,12 @@ function deferredInitialisation() {
});
loadAwardsHandler();
// Toggle Canary Badge
if (Cookies.get('gitlab_canary') && Cookies.get('gitlab_canary') === 'true') {
document.querySelector('.js-canary-badge').classList.remove('hidden');
document.querySelector('.js-canary-link').classList.add('hidden');
}
}
document.addEventListener('DOMContentLoaded', () => {
......
......@@ -351,6 +351,7 @@ Please check your network connection and try again.`;
ref="textarea"
slot="textarea"
v-model="note"
dir="auto"
:disabled="isSubmitting"
name="note[note]"
class="note-textarea js-vue-comment-form js-note-text
......
......@@ -122,6 +122,7 @@ export default {
v-model="note.note"
:data-update-url="note.path"
class="hidden js-task-list-field"
dir="auto"
></textarea>
<note-edited-text
v-if="note.last_edited_at"
......
......@@ -268,6 +268,7 @@ export default {
:data-supports-quick-actions="!isEditing"
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
dir="auto"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
@keydown.meta.enter="handleKeySubmit()"
......
......@@ -65,18 +65,18 @@ export default class ActivityCalendar {
this.daySize = 15;
this.daySizeWithSpace = this.daySize + this.daySpace * 2;
this.monthNames = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
__('Jan'),
__('Feb'),
__('Mar'),
__('Apr'),
__('May'),
__('Jun'),
__('Jul'),
__('Aug'),
__('Sep'),
__('Oct'),
__('Nov'),
__('Dec'),
];
this.months = [];
this.firstDayOfWeek = firstDayOfWeek;
......
......@@ -34,6 +34,7 @@ export default () => {
props: {
isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline,
mediator: this.mediator,
},
on: {
refreshPipelineGraph: this.requestRefreshPipelineGraph,
......
......@@ -30,6 +30,7 @@ export default {
:id="inputId"
:value="value"
class="form-control js-gfm-input append-bottom-default commit-message-edit"
dir="auto"
required="required"
rows="7"
@input="$emit('input', $event.target.value)"
......
......@@ -119,6 +119,7 @@ export default {
},
showTargetBranchAdvancedError() {
return Boolean(
this.mr.isOpen &&
this.mr.pipeline &&
this.mr.pipeline.target_sha &&
this.mr.pipeline.target_sha !== this.mr.targetBranchSha,
......
......@@ -17,15 +17,13 @@ export default {
required: true,
},
},
data() {
return {
milestoneDue: this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null,
milestoneStart: this.milestone.start_date
? parsePikadayDate(this.milestone.start_date)
: null,
};
},
computed: {
milestoneDue() {
return this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null;
},
milestoneStart() {
return this.milestone.start_date ? parsePikadayDate(this.milestone.start_date) : null;
},
isMilestoneStarted() {
if (!this.milestoneStart) {
return false;
......
<script>
import '~/commons/bootstrap';
import { GlTooltipDirective } from '@gitlab/ui';
import { sprintf } from '~/locale';
import IssueMilestone from '../../components/issue/issue_milestone.vue';
......
......@@ -17,10 +17,10 @@ export default {
<template>
<tr class="line_holder" :class="lineType">
<td class="diff-line-num old_line" :class="lineType">
<td class="diff-line-num old_line border-top-0 border-bottom-0" :class="lineType">
{{ line.old_line }}
</td>
<td class="diff-line-num new_line" :class="lineType">
<td class="diff-line-num new_line border-top-0 border-bottom-0" :class="lineType">
{{ line.new_line }}
</td>
<td class="line_content" :class="lineType">
......
......@@ -22,6 +22,7 @@ import noteHeader from '~/notes/components/note_header.vue';
import Icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from './timeline_entry_item.vue';
import { spriteIcon } from '../../../lib/utils/common_utils';
import initMRPopovers from '~/mr_popover/';
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
......@@ -71,6 +72,9 @@ export default {
);
},
},
mounted() {
initMRPopovers(this.$el.querySelectorAll('.gfm-merge_request'));
},
};
</script>
......
<script>
import $ from 'jquery';
import 'select2/select2';
import 'select2';
export default {
name: 'Select2Select',
......
......@@ -11,7 +11,7 @@
}
&-body {
height: 120px;
min-height: 120px;
&-warning {
background-color: $orange-50;
......@@ -22,11 +22,9 @@
}
}
&-time-ago {
&-icon {
color: $gray-500;
}
}
&-footer {
border-radius: $gl-padding;
......
.toast-close {
font-size: $default-icon-size !important;
}
......@@ -447,8 +447,8 @@
}
}
.title-container,
.navbar-nav {
li {
.badge.badge-pill {
position: inherit;
font-weight: $gl-font-weight-normal;
......@@ -460,7 +460,7 @@
border-radius: 7px;
box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
&.issues-count {
&.green-badge {
background-color: $green-500;
}
......@@ -472,7 +472,6 @@
background-color: $blue-500;
}
}
}
}
@include media-breakpoint-down(xs) {
......
......@@ -7,6 +7,9 @@
opacity: 1 !important;
* {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
// !important to make sure no style can override this when dragging
cursor: grabbing !important;
......@@ -256,6 +259,10 @@
}
}
.board-card-header {
text-align: initial;
}
.board-card-assignee {
margin-top: -$gl-padding-4;
margin-bottom: -$gl-padding-4;
......
......@@ -60,6 +60,7 @@
overflow-wrap: break-word;
min-width: 0;
width: 100%;
text-align: initial;
}
.btn-edit {
......
......@@ -456,7 +456,9 @@
// Don't hide the overflow in system messages
.system-note-message,
.issuable-detail {
.issuable-detail,
.md-preview-holder,
.note-body {
.scoped-label-wrapper {
.badge {
overflow: initial;
......
......@@ -75,6 +75,8 @@ input[type='checkbox']:hover {
}
.search-input-wrap {
width: 100%;
.search-icon,
.clear-icon {
position: absolute;
......@@ -84,6 +86,9 @@ input[type='checkbox']:hover {
.search-icon {
transition: color $default-transition-duration;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
......
......@@ -127,6 +127,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
[
*::ApplicationSettingsHelper.visible_attributes,
*::ApplicationSettingsHelper.external_authorization_service_attributes,
*lets_encrypt_visible_attributes,
:domain_blacklist_file,
disabled_oauth_sign_in_sources: [],
import_sources: [],
......@@ -134,4 +135,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
restricted_visibility_levels: []
]
end
def lets_encrypt_visible_attributes
return [] unless Feature.enabled?(:pages_auto_ssl)
[
:lets_encrypt_notification_email,
:lets_encrypt_terms_of_service_accepted
]
end
end
......@@ -10,8 +10,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
before_action only: [:metrics, :additional_metrics] do
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
push_frontend_feature_flag(:metrics_time_window)
push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
end
def index
......@@ -134,13 +135,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics
respond_to do |format|
format.html
format.json do
# Currently, this acts as a hint to load the metrics details into the cache
# if they aren't there already
@metrics = environment.metrics || {}
respond_to do |format|
format.html
format.json do
render json: @metrics, status: @metrics.any? ? :ok : :no_content
end
end
......@@ -156,6 +157,20 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
def metrics_dashboard
return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, @project)
result = Gitlab::Metrics::Dashboard::Service.new(@project, @current_user, environment: environment).get_dashboard
respond_to do |format|
if result[:status] == :success
format.json { render status: :ok, json: result }
else
format.json { render status: result[:http_status], json: result }
end
end
end
def search
respond_to do |format|
format.json do
......
......@@ -56,6 +56,8 @@ module Projects
# overridden in EE
def permitted_project_params
{
metrics_setting_attributes: [:external_dashboard_url],
error_tracking_setting_attributes: [
:enabled,
:api_host,
......
......@@ -44,6 +44,12 @@ module Resolvers
alias_method :project, :object
def resolve(**args)
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so
# make sure it's loaded and not `nil` before continueing.
project.sync if project.respond_to?(:sync)
return Issue.none if project.nil?
# Will need to be be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54520
args[:project_id] = project.id
......
......@@ -4,7 +4,7 @@ module BroadcastMessagesHelper
def broadcast_message(message)
return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
content_tag :div, dir: 'auto', class: 'broadcast-message', style: broadcast_message_style(message) do
icon('bullhorn') << ' ' << render_broadcast_message(message)
end
end
......
......@@ -4,7 +4,7 @@ require 'nokogiri'
module MarkupHelper
include ActionView::Helpers::TagHelper
include ActionView::Context
include ::Gitlab::ActionViewOutput::Context
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
......
......@@ -229,6 +229,16 @@ class ApplicationSetting < ApplicationRecord
presence: true,
if: -> (setting) { setting.external_auth_client_cert.present? }
validates :lets_encrypt_notification_email,
devise_email: true,
format: { without: /@example\.(com|org|net)\z/,
message: N_("Let's Encrypt does not accept emails on example.com") },
allow_blank: true
validates :lets_encrypt_notification_email,
presence: true,
if: :lets_encrypt_terms_of_service_accepted?
validates_with X509CertificateCredentialsValidator,
certificate: :external_auth_client_cert,
pkey: :external_auth_client_key,
......
......@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
VERSION = '0.4.0'.freeze
VERSION = '0.4.1'.freeze
self.table_name = 'clusters_applications_runners'
......
......@@ -47,6 +47,12 @@ class Deployment < ApplicationRecord
Deployments::SuccessWorker.perform_async(id)
end
end
after_transition any => [:success, :failed, :canceled] do |deployment|
deployment.run_after_commit do
Deployments::FinishedWorker.perform_async(id)
end
end
end
enum status: {
......@@ -79,7 +85,16 @@ class Deployment < ApplicationRecord
end
def cluster
project.deployment_platform(environment: environment.name)&.cluster
platform = project.deployment_platform(environment: environment.name)
if platform.present? && platform.respond_to?(:cluster)
platform.cluster
end
end
def execute_hooks
deployment_data = Gitlab::DataBuilder::Deployment.build(self)
project.execute_services(deployment_data, :deployment_hooks)
end
def last?
......
......@@ -188,6 +188,7 @@ class Project < ApplicationRecord
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :project_repository, inverse_of: :project
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
......@@ -297,6 +298,7 @@ class Project < ApplicationRecord
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
accepts_nested_attributes_for :error_tracking_setting, update_only: true
accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
......
# frozen_string_literal: true
class ProjectMetricsSetting < ApplicationRecord
belongs_to :project
validates :external_dashboard_url,
length: { maximum: 255 },
addressable_url: { enforce_sanitization: true, ascii_only: true }
end
# frozen_string_literal: true
module ChatMessage
class DeploymentMessage < BaseMessage
attr_reader :commit_url
attr_reader :deployable_id
attr_reader :deployable_url
attr_reader :environment
attr_reader :short_sha
attr_reader :status
def initialize(data)
super
@commit_url = data[:commit_url]
@deployable_id = data[:deployable_id]
@deployable_url = data[:deployable_url]
@environment = data[:environment]
@short_sha = data[:short_sha]
@status = data[:status]
end
def attachments
[{
text: "#{project_link}\n#{deployment_link}, SHA #{commit_link}, by #{user_combined_name}",
color: color
}]
end
def activity
{}
end
private
def message
"Deploy to #{environment} #{humanized_status}"
end
def color
case status
when 'success'
'good'
when 'canceled'
'warning'
when 'failed'
'danger'
else
'#334455'
end
end
def project_link
link(project_name, project_url)
end
def deployment_link
link("Job ##{deployable_id}", deployable_url)
end
def commit_link
link(short_sha, commit_url)
end
def humanized_status
status == 'success' ? 'succeeded' : status
end
end
end
......@@ -33,7 +33,7 @@ class ChatNotificationService < Service
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
pipeline wiki_page deployment]
end
def fields
......@@ -122,6 +122,8 @@ class ChatNotificationService < Service
ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
ChatMessage::WikiPageMessage.new(data)
when "deployment"
ChatMessage::DeploymentMessage.new(data)
end
end
......
......@@ -33,6 +33,11 @@ class DiscordService < ChatNotificationService
# No-op.
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: "text", name: "webhook", placeholder: "e.g. https://discordapp.com/api/webhooks/…" },
......
......@@ -35,6 +35,11 @@ class HangoutsChatService < ChatNotificationService
'https://chat.googleapis.com/v1/spaces…'
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
......
......@@ -33,6 +33,11 @@ class MicrosoftTeamsService < ChatNotificationService
def default_channel_placeholder
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
......
......@@ -1065,6 +1065,19 @@ class Repository
blob.data
end
def create_if_not_exists
return if exists?
raw.create_repository
after_create
end
def blobs_metadata(paths, ref = 'HEAD')
references = Array.wrap(paths).map { |path| [ref, path] }
Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) }
end
private
# TODO Generice finder, later split this on finders by Ref or Oid
......
......@@ -50,6 +50,7 @@ class Service < ApplicationRecord
scope :job_hooks, -> { where(job_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :deployment_hooks, -> { where(deployment_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
scope :deployment, -> { where(category: 'deployment') }
......@@ -335,6 +336,8 @@ class Service < ApplicationRecord
"Event will be triggered when a wiki page is created/updated"
when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
when "deployment"
"Event will be triggered when a deployment finishes"
end
end
......
......@@ -3,7 +3,7 @@
module UserStatusTooltip
extend ActiveSupport::Concern
include ActionView::Helpers::TagHelper
include ActionView::Context
include ::Gitlab::ActionViewOutput::Context
include EmojiHelper
include UsersHelper
......
......@@ -12,7 +12,16 @@ module Projects
private
def project_update_params
error_tracking_params
error_tracking_params.merge(metrics_setting_params)
end
def metrics_setting_params
attribs = params[:metrics_setting_attributes]
return {} unless attribs
destroy = attribs[:external_dashboard_url].blank?
{ metrics_setting_attributes: attribs.merge(_destroy: destroy) }
end
def error_tracking_params
......
......@@ -5,16 +5,33 @@
.form-group
= f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'label-bold'
= f.number_field :max_pages_size, class: 'form-control'
.form-text.text-muted 0 for unlimited
.form-text.text-muted
= _("0 for unlimited")
.form-group
.form-check
= f.check_box :pages_domain_verification_enabled, class: 'form-check-input'
= f.label :pages_domain_verification_enabled, class: 'form-check-label' do
Require users to prove ownership of custom domains
= _("Require users to prove ownership of custom domains")
.form-text.text-muted
Domain verification is an essential security measure for public GitLab
sites. Users are required to demonstrate they control a domain before
it is enabled
= _("Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled")
= link_to icon('question-circle'), help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
- if Feature.enabled?(:pages_auto_ssl)
%h5
= _("Configure Let's Encrypt")
%p
- lets_encrypt_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: "https://letsencrypt.org/" }
= _("%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites.").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: '</a>'.html_safe }
.form-group
= f.label :lets_encrypt_notification_email, _("Email"), class: 'label-bold'
= f.text_field :lets_encrypt_notification_email, class: 'form-control'
.form-text.text-muted
= _("A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates.")
.form-group
.form-check
= f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input'
= f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do
// Terms of Service should actually be a link, but the best way to get the url is using API
// So it will be done in later MR
= _("I have read and agree to the Let's Encrypt Terms of Service")
= f.submit 'Save changes', class: "btn btn-success"
= f.submit _('Save changes'), class: "btn btn-success"
......@@ -14,6 +14,7 @@
.col-sm-10
= f.text_area :message, class: "form-control js-autosize",
required: true,
dir: 'auto',
data: { preview_path: preview_admin_broadcast_messages_path }
.form-group.row.js-toggle-colors-container
.col-sm-10.offset-sm-2
......
......@@ -11,7 +11,8 @@
= link_to [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip event-target-link append-right-4', title: event.target_title do
= event.target.reference_link_text
- unless event.milestone?
%span.event-target-title.append-right-4= "&quot;".html_safe + event.target.title + "&quot".html_safe
%span.event-target-title.append-right-4{ dir: "auto" }
= "&quot;".html_safe + event.target.title + "&quot".html_safe
- else
%span.event-type.d-inline-block.append-right-4{ class: event.action_name }
= event_action_name(event)
......
......@@ -7,7 +7,8 @@
%span.event-type.d-inline-block.append-right-4{ class: event.action_name }
= event.action_name
= event_note_title_html(event)
%span.event-target-title.append-right-4= "&quot;".html_safe + event.target.title + "&quot".html_safe
%span.event-target-title.append-right-4{ dir: "auto" }
= "&quot;".html_safe + event.target.title + "&quot".html_safe
= render "events/event_scope", event: event
......
......@@ -17,6 +17,8 @@
- if logo_text.present?
%span.logo-text.d-none.d-lg-block.prepend-left-8
= logo_text
%span.js-canary-badge.badge.badge-pill.green-badge.align-self-center
= _('Next')
- if current_user
= render "layouts/nav/dashboard"
......@@ -38,7 +40,7 @@
= link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues', aria: { label: _('Issues') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('issues', size: 16)
- issues_count = assigned_issuables_count(:issues)
%span.badge.badge-pill.issues-count{ class: ('hidden' if issues_count.zero?) }
%span.badge.badge-pill.issues-count.green-badge{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
- if header_link?(:merge_requests)
= nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do
......
......@@ -7,3 +7,6 @@
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
= render 'shared/user_dropdown_contributing_link'
- if Gitlab.com?
%li.js-canary-link
= link_to _("Switch to GitLab Next"), "https://next.gitlab.com/"
......@@ -80,6 +80,8 @@
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_source_name(@project) }
= render_if_exists "projects/home_mirror"
- if @project.badges.present?
.project-badges.mb-2
- @project.badges.each do |badge|
......
......@@ -8,6 +8,7 @@
= f.text_area attr,
class: classes,
placeholder: placeholder,
dir: 'auto',
data: { supports_quick_actions: supports_quick_actions,
supports_autocomplete: supports_autocomplete }
- else
......
......@@ -6,7 +6,7 @@
.issuable-info-container
.issuable-main-info
.issue-title.title
%span.issue-title-text
%span.issue-title-text{ dir: "auto" }
- if issue.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue)
......
......@@ -37,21 +37,21 @@
%ul.merge-request-tabs.nav-tabs.nav.nav-links.scrolling-tabs
%li.notes-tab.qa-notes-tab
= tab_link_for @merge_request, :show, force_link: @commit.present? do
Discussion
= _("Discussion")
%span.badge.badge-pill= @merge_request.related_notes.user.count
- if @merge_request.source_project
%li.commits-tab
= tab_link_for @merge_request, :commits do
Commits
= _("Commits")
%span.badge.badge-pill= @commits_count
- if @pipelines.any?
%li.pipelines-tab
= tab_link_for @merge_request, :pipelines do
Pipelines
= _("Pipelines")
%span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
%li.diffs-tab.qa-diffs-tab
= tab_link_for @merge_request, :diffs do
Changes
= _("Changes")
%span.badge.badge-pill= @merge_request.diff_size
.d-inline-flex.flex-wrap
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request),
......
......@@ -11,7 +11,7 @@
= link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
.settings-content
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
.panel.panel-default
.panel-heading
%h3.panel-title= _('Mirror a repository')
......@@ -20,7 +20,7 @@
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
= text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url qa-mirror-repository-url-input', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
= text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url qa-mirror-repository-url-input', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", autocomplete: 'new-password'
= render 'projects/mirrors/instructions'
......
......@@ -21,10 +21,9 @@
- if @scope == 'projects'
.term
= render 'shared/projects/list', projects: @search_objects, pipeline_status: false
- elsif %w[blobs wiki_blobs].include?(@scope)
= render partial: 'search/results/blob', collection: @search_objects, locals: { projects: blob_projects(@search_objects) }
- else
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects
- locals = { projects: blob_projects(@search_objects) } if %w[blobs wiki_blobs].include?(@scope)
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects, locals: locals
- if @scope != 'projects'
= paginate_collection(@search_objects)
- project = find_project_for_result_blob(projects, wiki_blob)
- wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
- wiki_blob_link = project_wiki_path(project, Pathname.new(wiki_blob.filename).sub_ext(''))
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link }
%a.toggle-sidebar-button.js-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
= sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
= sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
%span.collapse-text _("Collapse sidebar")
%span.collapse-text= _("Collapse sidebar")
= button_tag class: 'close-nav-button', type: 'button' do
= sprite_icon('close', size: 16)
%span.collapse-text _("Close sidebar")
%span.collapse-text= _("Close sidebar")
......@@ -6,7 +6,7 @@
%div{ class: div_class }
= form.text_field :title, required: true, maxlength: 255, autofocus: true,
autocomplete: 'off', class: 'form-control pad qa-issuable-form-title', placeholder: _('Title')
autocomplete: 'off', class: 'form-control pad qa-issuable-form-title', placeholder: _('Title'), dir: 'auto'
- if issuable.respond_to?(:work_in_progress?)
.form-text.text-muted
......
......@@ -83,6 +83,7 @@
- pipeline_processing:ci_build_schedule
- deployment:deployments_success
- deployment:deployments_finished
- repository_check:repository_check_clear
- repository_check:repository_check_batch
......
# frozen_string_literal: true
module Deployments
class FinishedWorker
include ApplicationWorker
queue_namespace :deployment
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try(:execute_hooks)
end
end
end
......@@ -21,8 +21,10 @@ class PostReceive
if repo_type.wiki?
process_wiki_changes(post_received)
else
elsif repo_type.project?
process_project_changes(post_received)
else
# Other repos don't have hooks for now
end
end
......
---
title: Enable Sidekiq Reliable Fetcher for background jobs by default
merge_request: 27530
author:
type: added
---
title: Hide ScopedBadge overflow notes
merge_request: 27651
author:
type: fixed
---
title: Add .NET Core YAML template
merge_request: 25604
author: Piotr Wosiek
type: added
---
title: Adds badge for Canary environment and help link
merge_request:
author:
type: added
---
title: Add auto direction for issue title
merge_request: 27378
author: Ahmad Haghighi
type: fixed
---
title: Display a toast message when the Kubernetes runner has successfully upgraded.
merge_request: 27206
author:
type: changed
---
title: Add backend support for a External Dashboard URL setting
merge_request: 27550
author:
type: added
---
title: Only show the "target branch has advanced" message when the merge request is
open
merge_request: 27588
author:
type: fixed
---
title: Fix Kubernetes service template deployment jobs broken as of 11.10.0
merge_request: 27687
author:
type: fixed
---
title: Fix bug where system note MR has no popover
merge_request: 27747
author:
type: fixed
---
title: Resolve Misalignment on suggested changes diff table
merge_request: 27612
author:
type: fixed
---
title: Show proper wiki links in search results
merge_request: 27634
author:
type: fixed
---
title: Add deployment events to chat notification services
merge_request: 27338
author:
type: added
---
title: Add gitaly session id & catfile-cache feature flag
merge_request: 27472
author:
type: performance
---
title: 'refactor(issue): Refactored issue tests from Karma to Jest'
merge_request: 27673
author: Martin Hobert
type: other
---
title: 'Refactored notes tests from Karma to Jest'
merge_request: 27648
author: Martin Hobert
type: other
---
title: Disable password autocomplete in mirror repository form
merge_request: 27542
author:
type: fixed
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment