Commit a66cbb6e authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge commit '2d980fad' into...

Merge commit '2d980fad' into backstage/gb/improve-jobs-controller-performance

* commit '2d980fad': (51 commits)
parents fe225fd9 2d980fad
...@@ -13,10 +13,8 @@ variables: ...@@ -13,10 +13,8 @@ variables:
BUILD_ASSETS_IMAGE: "false" BUILD_ASSETS_IMAGE: "false"
before_script: before_script:
- bundle --version
- date - date
- source scripts/utils.sh - source scripts/utils.sh
- date
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
- date - date
......
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
.use-pg-10: &use-pg-10 .use-pg-10: &use-pg-10
services: services:
- postgres:10.7 - name: postgres:10.7
- redis:alpine command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
.use-mysql: &use-mysql .use-mysql: &use-mysql
services: services:
...@@ -52,8 +53,10 @@ ...@@ -52,8 +53,10 @@
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
- TEST_TOOL=${JOB_NAME[0]} - TEST_TOOL=${JOB_NAME[0]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - TEST_LEVEL=${JOB_NAME[1]}
- export KNAPSACK_GENERATE_REPORT=true - DATABASE=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_${TEST_LEVEL}_${DATABASE}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true KNAPSACK_LOG_LEVEL=debug KNAPSACK_TEST_DIR=spec
- export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH} - export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH}
- export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
...@@ -63,7 +66,10 @@ ...@@ -63,7 +66,10 @@
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}' - '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}' - '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- scripts/gitaly-test-spawn - scripts/gitaly-test-spawn
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml" - date
- 'export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./lib/quality/test_level.rb -e "puts Quality::TestLevel.new.pattern(:${TEST_LEVEL})")'
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml --tag level:${TEST_LEVEL} --tag ~geo"
- date
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always when: always
...@@ -140,19 +146,68 @@ setup-test-env: ...@@ -140,19 +146,68 @@ setup-test-env:
except: except:
- /(^docs[\/-].*|.*-docs$)/ - /(^docs[\/-].*|.*-docs$)/
rspec-pg: rspec unit pg:
<<: *rspec-metadata-pg
parallel: 20
rspec integration pg:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg
parallel: 50 parallel: 6
rspec system pg:
<<: *rspec-metadata-pg
parallel: 24
rspec unit pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 20
rspec integration pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 6
rspec-pg-10: rspec system pg-10:
<<: *rspec-metadata-pg-10 <<: *rspec-metadata-pg-10
<<: *only-schedules-master <<: *only-schedules-master
parallel: 50 parallel: 24
rspec-mysql: rspec unit mysql:
<<: *rspec-metadata-mysql <<: *rspec-metadata-mysql
<<: *only-schedules-master <<: *only-schedules-master
parallel: 50 parallel: 20
rspec integration mysql:
<<: *rspec-metadata-mysql
<<: *only-schedules-master
parallel: 6
rspec system mysql:
<<: *rspec-metadata-mysql
<<: *only-schedules-master
parallel: 24
.rspec-mysql-on-demand: &rspec-mysql-on-demand
only:
variables:
- $CI_COMMIT_MESSAGE =~ /\[run mysql\]/i
- $CI_COMMIT_REF_NAME =~ /mysql/
rspec unit mysql on-demand:
<<: *rspec-metadata-mysql
<<: *rspec-mysql-on-demand
parallel: 20
rspec integration mysql on-demand:
<<: *rspec-metadata-mysql
<<: *rspec-mysql-on-demand
parallel: 6
rspec system mysql on-demand:
<<: *rspec-metadata-mysql
<<: *rspec-mysql-on-demand
parallel: 24
rspec-fast-spec-helper: rspec-fast-spec-helper:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg
...@@ -164,16 +219,17 @@ rspec-fast-spec-helper: ...@@ -164,16 +219,17 @@ rspec-fast-spec-helper:
script: script:
- export CACHE_CLASSES=true - export CACHE_CLASSES=true
- scripts/gitaly-test-spawn - scripts/gitaly-test-spawn
- bin/rspec --color --format documentation --tag quarantine spec/ - bin/rspec --color --format documentation --tag quarantine -- spec/
rspec-pg-quarantine: rspec quarantine pg:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg
<<: *rspec-quarantine <<: *rspec-quarantine
allow_failure: true allow_failure: true
rspec-mysql-quarantine: rspec quarantine mysql:
<<: *rspec-metadata-mysql <<: *rspec-metadata-mysql
<<: *rspec-quarantine <<: *rspec-quarantine
<<: *only-schedules-master
allow_failure: true allow_failure: true
static-analysis: static-analysis:
......
...@@ -40,12 +40,12 @@ update-tests-metadata: ...@@ -40,12 +40,12 @@ update-tests-metadata:
policy: push policy: push
script: script:
- retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document - retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_*_pg_node_*.json
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH} - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH' - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json - rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
- scripts/insert-rspec-profiling-data - scripts/insert-rspec-profiling-data
only: only:
......
...@@ -361,7 +361,7 @@ group :development, :test do ...@@ -361,7 +361,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.31.0', require: false gem 'haml_lint', '~> 0.31.0', require: false
gem 'simplecov', '~> 0.14.0', require: false gem 'simplecov', '~> 0.16.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
...@@ -372,6 +372,7 @@ group :development, :test do ...@@ -372,6 +372,7 @@ group :development, :test do
gem 'activerecord_sane_schema_dumper', '1.0' gem 'activerecord_sane_schema_dumper', '1.0'
gem 'stackprof', '~> 0.2.10', require: false gem 'stackprof', '~> 0.2.10', require: false
gem 'derailed_benchmarks', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false gem 'simple_po_parser', '~> 1.1.2', require: false
...@@ -399,6 +400,8 @@ gem 'html2text' ...@@ -399,6 +400,8 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.17.0' gem 'ruby-prof', '~> 0.17.0'
gem 'rbtrace', '~> 0.4', require: false gem 'rbtrace', '~> 0.4', require: false
gem 'memory_profiler', '~> 0.9', require: false
gem 'benchmark-memory', '~> 0.1', require: false
# OAuth # OAuth
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
......
...@@ -82,6 +82,8 @@ GEM ...@@ -82,6 +82,8 @@ GEM
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)
benchmark-memory (0.1.2)
memory_profiler (~> 0.9)
better_errors (2.5.0) better_errors (2.5.0)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
...@@ -155,6 +157,14 @@ GEM ...@@ -155,6 +157,14 @@ GEM
html-pipeline html-pipeline
declarative (0.0.10) declarative (0.0.10)
declarative-option (0.1.0) declarative-option (0.1.0)
derailed_benchmarks (1.3.5)
benchmark-ips (~> 2)
get_process_mem (~> 0)
heapy (~> 0)
memory_profiler (~> 0)
rack (>= 1)
rake (> 10, < 13)
thor (~> 0.19)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0) device_detector (1.0.0)
...@@ -174,7 +184,7 @@ GEM ...@@ -174,7 +184,7 @@ GEM
diffy (3.1.0) diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0) discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0) rest-client (~> 2.0)
docile (1.1.5) docile (1.3.1)
domain_name (0.5.20180417) domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2) doorkeeper (4.3.2)
...@@ -377,6 +387,7 @@ GEM ...@@ -377,6 +387,7 @@ GEM
hashie (>= 3.0) hashie (>= 3.0)
health_check (2.6.0) health_check (2.6.0)
rails (>= 4.0) rails (>= 4.0)
heapy (0.1.4)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
mimemagic mimemagic
...@@ -478,6 +489,7 @@ GEM ...@@ -478,6 +489,7 @@ GEM
memoist (0.16.0) memoist (0.16.0)
memoizable (0.4.2) memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
memory_profiler (0.9.13)
method_source (0.9.2) method_source (0.9.2)
mime-types (3.2.2) mime-types (3.2.2)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
...@@ -873,11 +885,11 @@ GEM ...@@ -873,11 +885,11 @@ GEM
jwt (>= 1.5, < 3.0) jwt (>= 1.5, < 3.0)
multi_json (~> 1.10) multi_json (~> 1.10)
simple_po_parser (1.1.2) simple_po_parser (1.1.2)
simplecov (0.14.1) simplecov (0.16.1)
docile (~> 1.1.0) docile (~> 1.1)
json (>= 1.8, < 3) json (>= 1.8, < 3)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.0) simplecov-html (0.10.2)
slack-notifier (1.5.1) slack-notifier (1.5.1)
spring (2.0.2) spring (2.0.2)
activesupport (>= 4.2) activesupport (>= 4.2)
...@@ -1014,6 +1026,7 @@ DEPENDENCIES ...@@ -1014,6 +1026,7 @@ DEPENDENCIES
batch-loader (~> 1.4.0) batch-loader (~> 1.4.0)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1)
better_errors (~> 2.5.0) better_errors (~> 2.5.0)
binding_of_caller (~> 0.8.0) binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4) bootsnap (~> 1.4)
...@@ -1034,6 +1047,7 @@ DEPENDENCIES ...@@ -1034,6 +1047,7 @@ DEPENDENCIES
creole (~> 0.5.0) creole (~> 0.5.0)
database_cleaner (~> 1.7.0) database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0) deckar01-task_list (= 2.2.0)
derailed_benchmarks
device_detector device_detector
devise (~> 4.6) devise (~> 4.6)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
...@@ -1111,6 +1125,7 @@ DEPENDENCIES ...@@ -1111,6 +1125,7 @@ DEPENDENCIES
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.2) loofah (~> 2.2)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
memory_profiler (~> 0.9)
method_source (~> 0.8) method_source (~> 0.8)
mimemagic (~> 0.3.2) mimemagic (~> 0.3.2)
mini_magick mini_magick
...@@ -1203,7 +1218,7 @@ DEPENDENCIES ...@@ -1203,7 +1218,7 @@ DEPENDENCIES
sidekiq (~> 5.2.7) sidekiq (~> 5.2.7)
sidekiq-cron (~> 1.0) sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2) simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0) simplecov (~> 0.16.1)
slack-notifier (~> 1.5.1) slack-notifier (~> 1.5.1)
spring (~> 2.0.0) spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
......
import { sprintf, __ } from '~/locale';
export default { export default {
computed: { computed: {
resolveButtonTitle() { resolveButtonTitle() {
let title = 'Mark comment as resolved'; let title = __('Mark comment as resolved');
if (this.resolvedBy) { if (this.resolvedBy) {
title = `Resolved by ${this.resolvedBy.name}`; title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
} }
return title; return title;
......
import { __ } from '~/locale';
const notImplemented = () => { const notImplemented = () => {
throw new Error('Not implemented!'); throw new Error(__('Not implemented!'));
}; };
export default { export default {
......
import * as mutationTypes from './mutation_types'; import * as mutationTypes from './mutation_types';
import { __ } from '~/locale';
const notImplemented = () => { const notImplemented = () => {
throw new Error('Not implemented!'); throw new Error(__('Not implemented!'));
}; };
export default { export default {
......
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Vue from 'vue'; import Vue from 'vue';
import AccessorUtilities from '~/lib/utils/accessor';
import { GlToast } from '@gitlab/ui'; import { GlToast } from '@gitlab/ui';
import PersistentUserCallout from '../persistent_user_callout'; import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale'; import { s__, sprintf } from '../locale';
...@@ -43,8 +44,10 @@ export default class Clusters { ...@@ -43,8 +44,10 @@ export default class Clusters {
helpPath, helpPath,
ingressHelpPath, ingressHelpPath,
ingressDnsHelpPath, ingressDnsHelpPath,
clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset; } = document.querySelector('.js-edit-cluster-form').dataset;
this.clusterId = clusterId;
this.store = new ClustersStore(); this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath); this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath); this.store.setManagePrometheusPath(managePrometheusPath);
...@@ -69,6 +72,10 @@ export default class Clusters { ...@@ -69,6 +72,10 @@ export default class Clusters {
this.errorContainer = document.querySelector('.js-cluster-error'); this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success'); this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating'); this.creatingContainer = document.querySelector('.js-cluster-creating');
this.unreachableContainer = document.querySelector('.js-cluster-api-unreachable');
this.authenticationFailureContainer = document.querySelector(
'.js-cluster-authentication-failure',
);
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason'); this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice'); this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token'); this.showTokenButton = document.querySelector('.js-show-cluster-token');
...@@ -125,6 +132,13 @@ export default class Clusters { ...@@ -125,6 +132,13 @@ export default class Clusters {
PersistentUserCallout.factory(callout); PersistentUserCallout.factory(callout);
} }
addBannerCloseHandler(el, status) {
el.querySelector('.js-close-banner').addEventListener('click', () => {
el.classList.add('hidden');
this.setBannerDismissedState(status, true);
});
}
addListeners() { addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication); eventHub.$on('installApplication', this.installApplication);
...@@ -133,6 +147,9 @@ export default class Clusters { ...@@ -133,6 +147,9 @@ export default class Clusters {
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data)); eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data)); eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
} }
removeListeners() { removeListeners() {
...@@ -205,6 +222,8 @@ export default class Clusters { ...@@ -205,6 +222,8 @@ export default class Clusters {
this.errorContainer.classList.add('hidden'); this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden'); this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden'); this.creatingContainer.classList.add('hidden');
this.unreachableContainer.classList.add('hidden');
this.authenticationFailureContainer.classList.add('hidden');
} }
checkForNewInstalls(prevApplicationMap, newApplicationMap) { checkForNewInstalls(prevApplicationMap, newApplicationMap) {
...@@ -228,9 +247,32 @@ export default class Clusters { ...@@ -228,9 +247,32 @@ export default class Clusters {
} }
} }
setBannerDismissedState(status, isDismissed) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
window.localStorage.setItem(
`cluster_${this.clusterId}_banner_dismissed`,
`${status}_${isDismissed}`,
);
}
}
isBannerDismissed(status) {
let bannerState;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`);
}
return bannerState === `${status}_true`;
}
updateContainer(prevStatus, status, error) { updateContainer(prevStatus, status, error) {
this.hideAll(); this.hideAll();
if (this.isBannerDismissed(status)) {
return;
}
this.setBannerDismissedState(status, false);
// We poll all the time but only want the `created` banner to show when newly created // We poll all the time but only want the `created` banner to show when newly created
if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) { if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
switch (status) { switch (status) {
...@@ -241,6 +283,12 @@ export default class Clusters { ...@@ -241,6 +283,12 @@ export default class Clusters {
this.errorContainer.classList.remove('hidden'); this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error; this.errorReasonContainer.textContent = error;
break; break;
case 'unreachable':
this.unreachableContainer.classList.remove('hidden');
break;
case 'authentication_failure':
this.authenticationFailureContainer.classList.remove('hidden');
break;
case 'scheduled': case 'scheduled':
case 'creating': case 'creating':
this.creatingContainer.classList.remove('hidden'); this.creatingContainer.classList.remove('hidden');
......
...@@ -6,6 +6,7 @@ import 'core-js/fn/array/from'; ...@@ -6,6 +6,7 @@ import 'core-js/fn/array/from';
import 'core-js/fn/array/includes'; import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign'; import 'core-js/fn/object/assign';
import 'core-js/fn/object/values'; import 'core-js/fn/object/values';
import 'core-js/fn/object/entries';
import 'core-js/fn/promise'; import 'core-js/fn/promise';
import 'core-js/fn/promise/finally'; import 'core-js/fn/promise/finally';
import 'core-js/fn/string/code-point-at'; import 'core-js/fn/string/code-point-at';
......
import Vue from 'vue'; import Vue from 'vue';
import ErrorTrackingSettings from './components/app.vue'; import ErrorTrackingSettings from './components/app.vue';
import createStore from './store'; import createStore from './store';
import initSettingsPanels from '~/settings_panels';
export default () => { export default () => {
initSettingsPanels();
const formContainerEl = document.querySelector('.js-error-tracking-form'); const formContainerEl = document.querySelector('.js-error-tracking-form');
const { const {
dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint }, dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint },
......
...@@ -3,12 +3,12 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; ...@@ -3,12 +3,12 @@ import { InMemoryCache } from 'apollo-cache-inmemory';
import { createUploadLink } from 'apollo-upload-client'; import { createUploadLink } from 'apollo-upload-client';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
export default (resolvers = {}, baseUrl = '') => { export default (resolvers = {}, config = {}) => {
let uri = `${gon.relative_url_root}/api/graphql`; let uri = `${gon.relative_url_root}/api/graphql`;
if (baseUrl) { if (config.baseUrl) {
// Prepend baseUrl and ensure that `///` are replaced with `/` // Prepend baseUrl and ensure that `///` are replaced with `/`
uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/'); uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/');
} }
return new ApolloClient({ return new ApolloClient({
...@@ -18,7 +18,7 @@ export default (resolvers = {}, baseUrl = '') => { ...@@ -18,7 +18,7 @@ export default (resolvers = {}, baseUrl = '') => {
[csrf.headerKey]: csrf.token, [csrf.headerKey]: csrf.token,
}, },
}), }),
cache: new InMemoryCache(), cache: new InMemoryCache(config.cacheConfig),
resolvers, resolvers,
}); });
}; };
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale'; import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref'; import getRefMixin from '../../mixins/get_ref';
import getFiles from '../../queries/getFiles.graphql'; import getFiles from '../../queries/getFiles.graphql';
import getProjectPath from '../../queries/getProjectPath.graphql';
import TableHeader from './header.vue'; import TableHeader from './header.vue';
import TableRow from './row.vue'; import TableRow from './row.vue';
import ParentRow from './parent_row.vue';
const PAGE_SIZE = 100;
export default { export default {
components: { components: {
GlLoadingIcon, GlLoadingIcon,
TableHeader, TableHeader,
TableRow, TableRow,
ParentRow,
}, },
mixins: [getRefMixin], mixins: [getRefMixin],
apollo: { apollo: {
files: { projectPath: {
query: getFiles, query: getProjectPath,
variables() {
return {
ref: this.ref,
path: this.path,
};
},
}, },
}, },
props: { props: {
...@@ -32,7 +32,14 @@ export default { ...@@ -32,7 +32,14 @@ export default {
}, },
data() { data() {
return { return {
files: [], projectPath: '',
nextPageCursor: '',
entries: {
trees: [],
submodules: [],
blobs: [],
},
isLoadingFiles: false,
}; };
}, },
computed: { computed: {
...@@ -42,8 +49,66 @@ export default { ...@@ -42,8 +49,66 @@ export default {
{ path: this.path, ref: this.ref }, { path: this.path, ref: this.ref },
); );
}, },
isLoadingFiles() { showParentRow() {
return this.$apollo.queries.files.loading; return !this.isLoadingFiles && this.path !== '';
},
},
watch: {
$route: function routeChange() {
this.entries.trees = [];
this.entries.submodules = [];
this.entries.blobs = [];
this.nextPageCursor = '';
this.fetchFiles();
},
},
mounted() {
// We need to wait for `ref` and `projectPath` to be set
this.$nextTick(() => this.fetchFiles());
},
methods: {
fetchFiles() {
this.isLoadingFiles = true;
return this.$apollo
.query({
query: getFiles,
variables: {
projectPath: this.projectPath,
ref: this.ref,
path: this.path,
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},
})
.then(({ data }) => {
if (!data) return;
const pageInfo = this.hasNextPage(data.project.repository.tree);
this.isLoadingFiles = false;
this.entries = Object.keys(this.entries).reduce(
(acc, key) => ({
...acc,
[key]: this.normalizeData(key, data.project.repository.tree[key].edges),
}),
{},
);
if (pageInfo && pageInfo.hasNextPage) {
this.nextPageCursor = pageInfo.endCursor;
this.fetchFiles();
}
})
.catch(() => createFlash(__('An error occurding while fetching folder content.')));
},
normalizeData(key, data) {
return this.entries[key].concat(data.map(({ node }) => node));
},
hasNextPage(data) {
return []
.concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
.find(({ hasNextPage }) => hasNextPage);
}, },
}, },
}; };
...@@ -58,18 +123,22 @@ export default { ...@@ -58,18 +123,22 @@ export default {
tableCaption tableCaption
}} }}
</caption> </caption>
<table-header /> <table-header v-once />
<tbody> <tbody>
<table-row <parent-row v-show="showParentRow" :commit-ref="ref" :path="path" />
v-for="entry in files" <template v-for="val in entries">
:id="entry.id" <table-row
:key="entry.id" v-for="entry in val"
:path="entry.flatPath" :id="entry.id"
:type="entry.type" :key="`${entry.flatPath}-${entry.id}`"
/> :current-path="path"
:path="entry.flatPath"
:type="entry.type"
/>
</template>
</tbody> </tbody>
</table> </table>
<gl-loading-icon v-if="isLoadingFiles" class="my-3" size="md" /> <gl-loading-icon v-show="isLoadingFiles" class="my-3" size="md" />
</div> </div>
</div> </div>
</template> </template>
<script>
export default {
props: {
commitRef: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
parentRoute() {
const splitArray = this.path.split('/');
splitArray.pop();
return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` };
},
},
methods: {
clickRow() {
this.$router.push(this.parentRoute);
},
},
};
</script>
<template>
<tr v-once @click="clickRow">
<td colspan="3" class="tree-item-file-name">
<router-link :to="parentRoute" :aria-label="__('Go to parent')">
..
</router-link>
</td>
</tr>
</template>
...@@ -6,7 +6,11 @@ export default { ...@@ -6,7 +6,11 @@ export default {
mixins: [getRefMixin], mixins: [getRefMixin],
props: { props: {
id: { id: {
type: Number, type: String,
required: true,
},
currentPath: {
type: String,
required: true, required: true,
}, },
path: { path: {
...@@ -26,7 +30,7 @@ export default { ...@@ -26,7 +30,7 @@ export default {
return `fa-${getIconName(this.type, this.path)}`; return `fa-${getIconName(this.type, this.path)}`;
}, },
isFolder() { isFolder() {
return this.type === 'folder'; return this.type === 'tree';
}, },
isSubmodule() { isSubmodule() {
return this.type === 'commit'; return this.type === 'commit';
...@@ -34,6 +38,12 @@ export default { ...@@ -34,6 +38,12 @@ export default {
linkComponent() { linkComponent() {
return this.isFolder ? 'router-link' : 'a'; return this.isFolder ? 'router-link' : 'a';
}, },
fullPath() {
return this.path.replace(new RegExp(`^${this.currentPath}/`), '');
},
shortSha() {
return this.id.slice(0, 8);
},
}, },
methods: { methods: {
openRow() { openRow() {
...@@ -49,9 +59,11 @@ export default { ...@@ -49,9 +59,11 @@ export default {
<tr v-once :class="`file_${id}`" class="tree-item" @click="openRow"> <tr v-once :class="`file_${id}`" class="tree-item" @click="openRow">
<td class="tree-item-file-name"> <td class="tree-item-file-name">
<i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i>
<component :is="linkComponent" :to="routerLinkTo" class="str-truncated">{{ path }}</component> <component :is="linkComponent" :to="routerLinkTo" class="str-truncated">
{{ fullPath }}
</component>
<template v-if="isSubmodule"> <template v-if="isSubmodule">
@ <a href="#" class="commit-sha">{{ id }}</a> @ <a href="#" class="commit-sha">{{ shortSha }}</a>
</template> </template>
</td> </td>
<td class="d-none d-sm-table-cell tree-commit"></td> <td class="d-none d-sm-table-cell tree-commit"></td>
......
{"__schema":{"types":[{"kind":"INTERFACE","name":"Entry","possibleTypes":[{"name":"Blob"},{"name":"Submodule"},{"name":"TreeEntry"}]}]}}
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
Vue.use(VueApollo); Vue.use(VueApollo);
const defaultClient = createDefaultClient({ // We create a fragment matcher so that we can create a fragment from an interface
Query: { // Without this, Apollo throws a heuristic fragment matcher warning
files() { const fragmentMatcher = new IntrospectionFragmentMatcher({
return [ introspectionQueryResultData,
{ });
__typename: 'file',
id: 1, const defaultClient = createDefaultClient(
name: 'app', {},
flatPath: 'app', {
type: 'folder', cacheConfig: {
}, fragmentMatcher,
{ dataIdFromObject: obj => {
__typename: 'file', // eslint-disable-next-line no-underscore-dangle
id: 2, switch (obj.__typename) {
name: 'gitlab-svg', // We need to create a dynamic ID for each entry
flatPath: 'gitlab-svg', // Each entry can have the same ID as the ID is a commit ID
type: 'commit', // So we create a unique cache ID with the path and the ID
}, case 'TreeEntry':
{ case 'Submodule':
__typename: 'file', case 'Blob':
id: 3, return `${obj.flatPath}-${obj.id}`;
name: 'index.js', default:
flatPath: 'index.js', // If the type doesn't match any of the above we fallback
type: 'blob', // to using the default Apollo ID
}, // eslint-disable-next-line no-underscore-dangle
{ return obj.id || obj._id;
__typename: 'file', }
id: 4, },
name: 'test.pdf',
flatPath: 'fixtures/test.pdf',
type: 'blob',
},
];
}, },
}, },
}); );
export default new VueApollo({ export default new VueApollo({
defaultClient, defaultClient,
......
query getFiles($path: String!, $ref: String!) { fragment TreeEntry on Entry {
files(path: $path, ref: $ref) @client { id
id flatPath
flatPath type
type }
fragment PageInfo on PageInfo {
hasNextPage
endCursor
}
query getFiles(
$projectPath: ID!
$path: String
$ref: String!
$pageSize: Int!
$nextPageCursor: String
) {
project(fullPath: $projectPath) {
repository {
tree(path: $path, ref: $ref) {
trees(first: $pageSize, after: $nextPageCursor) {
edges {
node {
...TreeEntry
}
}
pageInfo {
...PageInfo
}
}
submodules(first: $pageSize, after: $nextPageCursor) {
edges {
node {
...TreeEntry
}
}
pageInfo {
...PageInfo
}
}
blobs(first: $pageSize, after: $nextPageCursor) {
edges {
node {
...TreeEntry
}
}
pageInfo {
...PageInfo
}
}
}
}
} }
} }
...@@ -11,17 +11,12 @@ export default function createRouter(base, baseRef) { ...@@ -11,17 +11,12 @@ export default function createRouter(base, baseRef) {
mode: 'history', mode: 'history',
base: joinPaths(gon.relative_url_root || '', base), base: joinPaths(gon.relative_url_root || '', base),
routes: [ routes: [
{
path: '/',
name: 'projectRoot',
component: IndexPage,
},
{ {
path: `/tree/${baseRef}(/.*)?`, path: `/tree/${baseRef}(/.*)?`,
name: 'treePath', name: 'treePath',
component: TreePage, component: TreePage,
props: route => ({ props: route => ({
path: route.params.pathMatch, path: route.params.pathMatch.replace(/^\//, ''),
}), }),
beforeEnter(to, from, next) { beforeEnter(to, from, next) {
document document
...@@ -31,6 +26,11 @@ export default function createRouter(base, baseRef) { ...@@ -31,6 +26,11 @@ export default function createRouter(base, baseRef) {
next(); next();
}, },
}, },
{
path: '/',
name: 'projectRoot',
component: IndexPage,
},
], ],
}); });
} }
const entryTypeIcons = { const entryTypeIcons = {
folder: 'folder', tree: 'folder',
commit: 'archive', commit: 'archive',
}; };
......
...@@ -2,6 +2,7 @@ import $ from 'jquery'; ...@@ -2,6 +2,7 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import Flash, { hideFlash } from './flash'; import Flash, { hideFlash } from './flash';
import { parseBoolean } from './lib/utils/common_utils'; import { parseBoolean } from './lib/utils/common_utils';
import { __ } from './locale';
export default () => { export default () => {
$('body').on('click', '.js-usage-consent-action', e => { $('body').on('click', '.js-usage-consent-action', e => {
...@@ -25,7 +26,7 @@ export default () => { ...@@ -25,7 +26,7 @@ export default () => {
}) })
.catch(() => { .catch(() => {
hideConsentMessage(); hideConsentMessage();
Flash('Something went wrong. Try again later.'); Flash(__('Something went wrong. Try again later.'));
}); });
}); });
}; };
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { __ } from './locale'; import { s__, __, sprintf } from './locale';
import ModalStore from './boards/stores/modal_store'; import ModalStore from './boards/stores/modal_store';
// TODO: remove eventHub hack after code splitting refactor // TODO: remove eventHub hack after code splitting refactor
...@@ -157,14 +157,20 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -157,14 +157,20 @@ function UsersSelect(currentUser, els, options = {}) {
.get(0); .get(0);
if (selectedUsers.length === 0) { if (selectedUsers.length === 0) {
return 'Unassigned'; return s__('UsersSelect|Unassigned');
} else if (selectedUsers.length === 1) { } else if (selectedUsers.length === 1) {
return firstUser.name; return firstUser.name;
} else if (isSelected) { } else if (isSelected) {
const otherSelected = selectedUsers.filter(s => s !== selectedUser.id); const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
return `${selectedUser.name} + ${otherSelected.length} more`; return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
name: selectedUser.name,
length: otherSelected.length,
});
} else { } else {
return `${firstUser.name} + ${selectedUsers.length - 1} more`; return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
name: firstUser.name,
length: selectedUsers.length - 1,
});
} }
}; };
...@@ -218,11 +224,11 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -218,11 +224,11 @@ function UsersSelect(currentUser, els, options = {}) {
tooltipTitle = _.escape(user.name); tooltipTitle = _.escape(user.name);
} else { } else {
user = { user = {
name: 'Unassigned', name: s__('UsersSelect|Unassigned'),
username: '', username: '',
avatar: '', avatar: '',
}; };
tooltipTitle = __('Assignee'); tooltipTitle = s__('UsersSelect|Assignee');
} }
$value.html(assigneeTemplate(user)); $value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle'); $collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle');
...@@ -233,7 +239,11 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -233,7 +239,11 @@ function UsersSelect(currentUser, els, options = {}) {
'<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>', '<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>',
); );
assigneeTemplate = _.template( assigneeTemplate = _.template(
'<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>', `<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
openingTag: '<a href="#" class="js-assign-yourself">',
closingTag: '</a>',
})}</span> <% } %>`,
); );
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
...@@ -302,7 +312,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -302,7 +312,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1; showDivider += 1;
users.unshift({ users.unshift({
beforeDivider: true, beforeDivider: true,
name: 'Unassigned', name: s__('UsersSelect|Unassigned'),
id: 0, id: 0,
}); });
} }
...@@ -310,7 +320,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -310,7 +320,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1; showDivider += 1;
name = showAnyUser; name = showAnyUser;
if (name === true) { if (name === true) {
name = 'Any User'; name = s__('UsersSelect|Any User');
} }
anyUser = { anyUser = {
beforeDivider: true, beforeDivider: true,
...@@ -596,7 +606,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -596,7 +606,7 @@ function UsersSelect(currentUser, els, options = {}) {
showEmailUser = $(select).data('emailUser'); showEmailUser = $(select).data('emailUser');
firstUser = $(select).data('firstUser'); firstUser = $(select).data('firstUser');
return $(select).select2({ return $(select).select2({
placeholder: 'Search for a user', placeholder: __('Search for a user'),
multiple: $(select).hasClass('multiselect'), multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0, minimumInputLength: 0,
query: function(query) { query: function(query) {
...@@ -621,7 +631,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -621,7 +631,7 @@ function UsersSelect(currentUser, els, options = {}) {
} }
if (showNullUser) { if (showNullUser) {
nullUser = { nullUser = {
name: 'Unassigned', name: s__('UsersSelect|Unassigned'),
id: 0, id: 0,
}; };
data.results.unshift(nullUser); data.results.unshift(nullUser);
...@@ -629,7 +639,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -629,7 +639,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showAnyUser) { if (showAnyUser) {
name = showAnyUser; name = showAnyUser;
if (name === true) { if (name === true) {
name = 'Any User'; name = s__('UsersSelect|Any User');
} }
anyUser = { anyUser = {
name: name, name: name,
...@@ -645,7 +655,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -645,7 +655,7 @@ function UsersSelect(currentUser, els, options = {}) {
) { ) {
var trimmed = query.term.trim(); var trimmed = query.term.trim();
emailUser = { emailUser = {
name: 'Invite "' + trimmed + '" by email', name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
username: trimmed, username: trimmed,
id: trimmed, id: trimmed,
invite: true, invite: true,
...@@ -688,7 +698,7 @@ UsersSelect.prototype.initSelection = function(element, callback) { ...@@ -688,7 +698,7 @@ UsersSelect.prototype.initSelection = function(element, callback) {
id = $(element).val(); id = $(element).val();
if (id === '0') { if (id === '0') {
nullUser = { nullUser = {
name: 'Unassigned', name: s__('UsersSelect|Unassigned'),
}; };
return callback(nullUser); return callback(nullUser);
} else if (id !== '') { } else if (id !== '') {
......
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
required: false, required: false,
default: () => ({ default: () => ({
sourceProjectId: '', sourceProjectId: '',
issueId: '', mergeRequestId: '',
appUrl: '', appUrl: '',
}), }),
}, },
......
...@@ -48,7 +48,7 @@ export default { ...@@ -48,7 +48,7 @@ export default {
visualReviewAppMeta() { visualReviewAppMeta() {
return { return {
appUrl: this.mr.appUrl, appUrl: this.mr.appUrl,
issueId: this.mr.iid, mergeRequestId: this.mr.iid,
sourceProjectId: this.mr.sourceProjectId, sourceProjectId: this.mr.sourceProjectId,
}; };
}, },
......
...@@ -14,7 +14,7 @@ export default { ...@@ -14,7 +14,7 @@ export default {
</script> </script>
<template> <template>
<p v-once class="mr-info-list mr-links source-branch-removal-status append-bottom-0"> <p v-once class="mr-info-list mr-links append-bottom-0">
<span class="status-text" v-html="removesBranchText"> </span> <span class="status-text" v-html="removesBranchText"> </span>
<i v-tooltip :title="tooltipTitle" :aria-label="tooltipTitle" class="fa fa-question-circle"> <i v-tooltip :title="tooltipTitle" :aria-label="tooltipTitle" class="fa fa-question-circle">
</i> </i>
......
...@@ -333,41 +333,45 @@ export default { ...@@ -333,41 +333,45 @@ export default {
<div class="mr-widget-section"> <div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" /> <component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links"> <div class="mr-widget-info">
{{ s__('mrWidget|Allows commits from members who can merge to the target branch') }} <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
</section> <p>
{{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
</p>
</section>
<mr-widget-related-links <mr-widget-related-links
v-if="shouldRenderRelatedLinks" v-if="shouldRenderRelatedLinks"
:state="mr.state" :state="mr.state"
:related-links="mr.relatedLinks" :related-links="mr.relatedLinks"
/> />
<mr-widget-alert-message <mr-widget-alert-message
v-if="showMergePipelineForkWarning" v-if="showMergePipelineForkWarning"
type="warning" type="warning"
:help-path="mr.mergeRequestPipelinesHelpPath" :help-path="mr.mergeRequestPipelinesHelpPath"
> >
{{ {{
s__( s__(
'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result', 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result',
) )
}} }}
</mr-widget-alert-message> </mr-widget-alert-message>
<mr-widget-alert-message <mr-widget-alert-message
v-if="showTargetBranchAdvancedError" v-if="showTargetBranchAdvancedError"
type="danger" type="danger"
:help-path="mr.mergeRequestPipelinesHelpPath" :help-path="mr.mergeRequestPipelinesHelpPath"
> >
{{ {{
s__( s__(
'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging', 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging',
) )
}} }}
</mr-widget-alert-message> </mr-widget-alert-message>
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
</div>
</div> </div>
<div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div> <div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div>
</div> </div>
......
...@@ -71,11 +71,15 @@ export default { ...@@ -71,11 +71,15 @@ export default {
</div> </div>
<div class="text-secondary"> <div class="text-secondary">
<div v-if="user.bio" class="js-bio d-flex mb-1"> <div v-if="user.bio" class="js-bio d-flex mb-1">
<icon name="profile" css-classes="category-icon" /> <icon name="profile" css-classes="category-icon flex-shrink-0" />
<span class="ml-1">{{ user.bio }}</span> <span class="ml-1">{{ user.bio }}</span>
</div> </div>
<div v-if="user.organization" class="js-organization d-flex mb-1"> <div v-if="user.organization" class="js-organization d-flex mb-1">
<icon v-show="!jobInfoIsLoading" name="work" css-classes="category-icon" /> <icon
v-show="!jobInfoIsLoading"
name="work"
css-classes="category-icon flex-shrink-0"
/>
<span class="ml-1">{{ user.organization }}</span> <span class="ml-1">{{ user.organization }}</span>
</div> </div>
<gl-skeleton-loading <gl-skeleton-loading
...@@ -88,7 +92,7 @@ export default { ...@@ -88,7 +92,7 @@ export default {
<icon <icon
v-show="!locationIsLoading && user.location" v-show="!locationIsLoading && user.location"
name="location" name="location"
css-classes="category-icon" css-classes="category-icon flex-shrink-0"
/> />
<span class="ml-1">{{ user.location }}</span> <span class="ml-1">{{ user.location }}</span>
<gl-skeleton-loading <gl-skeleton-loading
......
@mixin avatar-size($size, $margin-right) {
width: $size;
height: $size;
margin-right: $margin-right;
}
.avatar-circle { .avatar-circle {
float: left; float: left;
margin-right: 15px; margin-right: 15px;
border-radius: $avatar-radius; border-radius: $avatar-radius;
border: 1px solid $gray-normal; border: 1px solid $gray-normal;
&.s16 { @include avatar-size(16px, 6px); } &.s16 { @include avatar-size(16px, 8px); }
&.s18 { @include avatar-size(18px, 6px); } &.s18 { @include avatar-size(18px, 8px); }
&.s19 { @include avatar-size(19px, 6px); } &.s19 { @include avatar-size(19px, 8px); }
&.s20 { @include avatar-size(20px, 7px); } &.s20 { @include avatar-size(20px, 8px); }
&.s24 { @include avatar-size(24px, 8px); } &.s24 { @include avatar-size(24px, 8px); }
&.s26 { @include avatar-size(26px, 8px); } &.s26 { @include avatar-size(26px, 8px); }
&.s32 { @include avatar-size(32px, 10px); } &.s32 { @include avatar-size(32px, 8px); }
&.s36 { @include avatar-size(36px, 10px); } &.s36 { @include avatar-size(36px, 16px); }
&.s40 { @include avatar-size(40px, 10px); } &.s40 { @include avatar-size(40px, 16px); }
&.s46 { @include avatar-size(46px, 15px); } &.s46 { @include avatar-size(46px, 16px); }
&.s48 { @include avatar-size(48px, 10px); } &.s48 { @include avatar-size(48px, 16px); }
&.s60 { @include avatar-size(60px, 12px); } &.s60 { @include avatar-size(60px, 16px); }
&.s64 { @include avatar-size(64px, 14px); } &.s64 { @include avatar-size(64px, 16px); }
&.s70 { @include avatar-size(70px, 14px); } &.s70 { @include avatar-size(70px, 16px); }
&.s90 { @include avatar-size(90px, 15px); } &.s90 { @include avatar-size(90px, 16px); }
&.s100 { @include avatar-size(100px, 15px); } &.s96 { @include avatar-size(96px, 16px); }
&.s110 { @include avatar-size(110px, 15px); } &.s100 { @include avatar-size(100px, 16px); }
&.s140 { @include avatar-size(140px, 15px); } &.s110 { @include avatar-size(110px, 16px); }
&.s160 { @include avatar-size(160px, 20px); } &.s140 { @include avatar-size(140px, 16px); }
&.s160 { @include avatar-size(160px, 16px); }
} }
.avatar { .avatar {
...@@ -39,6 +34,7 @@ ...@@ -39,6 +34,7 @@
padding: 0; padding: 0;
background: $gray-lightest; background: $gray-lightest;
overflow: hidden; overflow: hidden;
border-color: rgba($black, $gl-avatar-border-opacity);
&.avatar-inline { &.avatar-inline {
float: none; float: none;
...@@ -64,41 +60,37 @@ ...@@ -64,41 +60,37 @@
&.avatar-placeholder { &.avatar-placeholder {
border: 0; border: 0;
} }
&:not([href]):hover {
border-color: darken($gray-normal, 10%);
}
} }
.identicon { .identicon {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
color: $gl-gray-700; color: $gray-800;
background-color: $gray-darker; background-color: $gray-darker;
// Sizes // Sizes
&.s16 { font-size: 12px; &.s16 { font-size: 10px;
line-height: 1.33; } line-height: 16px; }
&.s24 { font-size: 13px; &.s24 { font-size: 12px;
line-height: 1.8; } line-height: 24px; }
&.s26 { font-size: 20px; &.s26 { font-size: 20px;
line-height: 1.33; } line-height: 1.33; }
&.s32 { font-size: 20px; &.s32 { font-size: 14px;
line-height: 30px; } line-height: 32px; }
&.s40 { font-size: 16px; &.s40 { font-size: 16px;
line-height: 38px; } line-height: 38px; }
&.s48 { font-size: 20px; &.s48 { font-size: 20px;
line-height: 46px; } line-height: 48px; }
&.s60 { font-size: 32px; &.s60 { font-size: 32px;
line-height: 58px; } line-height: 58px; }
&.s64 { font-size: 32px; &.s64 { font-size: 28px;
line-height: 64px; } line-height: 64px; }
&.s70 { font-size: 34px; &.s70 { font-size: 34px;
...@@ -107,6 +99,9 @@ ...@@ -107,6 +99,9 @@
&.s90 { font-size: 36px; &.s90 { font-size: 36px;
line-height: 88px; } line-height: 88px; }
&.s96 { font-size: 48px;
line-height: 96px; }
&.s100 { font-size: 36px; &.s100 { font-size: 36px;
line-height: 98px; } line-height: 98px; }
...@@ -144,7 +139,6 @@ ...@@ -144,7 +139,6 @@
.avatar { .avatar {
border-radius: 0; border-radius: 0;
border: 0;
height: auto; height: auto;
width: 100%; width: 100%;
margin: 0; margin: 0;
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
@import 'framework/animations'; @import 'framework/animations';
@import 'framework/vue_transitions'; @import 'framework/vue_transitions';
@import 'framework/avatar';
@import 'framework/asciidoctor'; @import 'framework/asciidoctor';
@import 'framework/banner'; @import 'framework/banner';
@import 'framework/blocks'; @import 'framework/blocks';
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
} }
@mixin btn-default { @mixin btn-default {
border-radius: 3px; border-radius: $border-radius-default;
font-size: $gl-font-size; font-size: $gl-font-size;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
padding: $gl-vert-padding $gl-btn-padding; padding: $gl-vert-padding $gl-btn-padding;
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
@include btn-default; @include btn-default;
} }
@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) { @mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border, $active-text) {
background-color: $background; background-color: $background;
color: $text; color: $text;
border-color: $border; border-color: $border;
...@@ -61,13 +61,22 @@ ...@@ -61,13 +61,22 @@
} }
} }
&:focus {
box-shadow: 0 0 4px 1px $blue-300;
}
&:active { &:active {
background-color: $active-background; background-color: $active-background;
border-color: $active-border; border-color: $active-border;
color: $hover-text; box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
color: $active-text;
> .icon { > .icon {
color: $hover-text; color: $active-text;
}
&:focus {
box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
} }
} }
} }
...@@ -164,21 +173,21 @@ ...@@ -164,21 +173,21 @@
&.btn-inverted { &.btn-inverted {
&.btn-success { &.btn-success {
@include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700); @include btn-outline($white-light, $green-600, $green-500, $green-100, $green-700, $green-500, $green-200, $green-600, $green-800);
} }
&.btn-remove, &.btn-remove,
&.btn-danger { &.btn-danger {
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); @include btn-outline($white-light, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800);
} }
&.btn-warning { &.btn-warning {
@include btn-outline($white-light, $orange-500, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700); @include btn-outline($white-light, $orange-500, $orange-500, $orange-100, $orange-700, $orange-500, $orange-200, $orange-600, $orange-800);
} }
&.btn-primary, &.btn-primary,
&.btn-info { &.btn-info {
@include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700); @include btn-outline($white-light, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800);
} }
} }
...@@ -193,11 +202,11 @@ ...@@ -193,11 +202,11 @@
&.btn-close, &.btn-close,
&.btn-close-color { &.btn-close-color {
@include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700); @include btn-outline($white-light, $orange-600, $orange-500, $orange-100, $orange-700, $orange-500, $orange-200, $orange-600, $orange-800);
} }
&.btn-spam { &.btn-spam {
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); @include btn-outline($white-light, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800);
} }
&.btn-danger, &.btn-danger,
...@@ -402,7 +411,7 @@ ...@@ -402,7 +411,7 @@
.btn-inverted { .btn-inverted {
&-secondary { &-secondary {
@include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700); @include btn-outline($white-light, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800);
} }
} }
......
...@@ -241,6 +241,7 @@ ...@@ -241,6 +241,7 @@
*/ */
&.code { &.code {
padding: 0; padding: 0;
border-radius: 0 0 $border-radius-default $border-radius-default;
} }
.list-inline.previews { .list-inline.previews {
......
...@@ -280,3 +280,7 @@ label { ...@@ -280,3 +280,7 @@ label {
max-width: $input-lg-width; max-width: $input-lg-width;
width: 100%; width: 100%;
} }
.input-group-text {
max-height: $input-height;
}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
pre { pre {
padding: 10px 0; padding: 10px 0;
border: 0; border: 0;
border-radius: 0; border-radius: 0 0 $border-radius-default $border-radius-default;
font-family: $monospace-font; font-family: $monospace-font;
font-size: $code-font-size; font-size: $code-font-size;
line-height: 19px; line-height: 19px;
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
padding: 10px; padding: 10px;
text-align: right; text-align: right;
float: left; float: left;
border-bottom-left-radius: $border-radius-default;
a { a {
font-family: $monospace-font; font-family: $monospace-font;
......
...@@ -376,3 +376,12 @@ ...@@ -376,3 +376,12 @@
} }
} }
} }
/*
* Mixin that handles the size and right margin of avatars.
*/
@mixin avatar-size($size, $margin-right) {
width: $size;
height: $size;
margin-right: $margin-right;
}
...@@ -589,6 +589,7 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards ...@@ -589,6 +589,7 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards
*/ */
$avatar-radius: 50%; $avatar-radius: 50%;
$gl-avatar-size: 40px; $gl-avatar-size: 40px;
$gl-avatar-border-opacity: 0.1;
/* /*
* Blame * Blame
......
...@@ -7,6 +7,7 @@ $secondary: $gray-light; ...@@ -7,6 +7,7 @@ $secondary: $gray-light;
$input-disabled-bg: $gray-light; $input-disabled-bg: $gray-light;
$input-border-color: $gray-200; $input-border-color: $gray-200;
$input-color: $gl-text-color; $input-color: $gl-text-color;
$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font; $font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font; $font-family-monospace: $monospace-font;
$btn-line-height: 20px; $btn-line-height: 20px;
......
...@@ -69,6 +69,8 @@ ...@@ -69,6 +69,8 @@
align-self: flex-start; align-self: flex-start;
font-weight: 500; font-weight: 500;
font-size: 20px; font-size: 20px;
color: $orange-900;
opacity: 1;
margin: $gl-padding-8 14px 0 0; margin: $gl-padding-8 14px 0 0;
} }
......
...@@ -494,6 +494,12 @@ table.code { ...@@ -494,6 +494,12 @@ table.code {
} }
} }
.line_holder:last-of-type {
td:first-child {
border-bottom-left-radius: $border-radius-default;
}
}
&.left-side-selected { &.left-side-selected {
td.line_content.parallel.right-side { td.line_content.parallel.right-side {
user-select: none; user-select: none;
......
...@@ -21,13 +21,6 @@ ...@@ -21,13 +21,6 @@
color: $login-brand-holder-color; color: $login-brand-holder-color;
} }
h1:first-child {
font-weight: $gl-font-weight-normal;
margin-bottom: 0.68em;
margin-top: 0;
font-size: 34px;
}
h3 { h3 {
font-size: 22px; font-size: 22px;
} }
......
...@@ -87,6 +87,11 @@ ...@@ -87,6 +87,11 @@
padding: $gl-padding; padding: $gl-padding;
} }
.mr-widget-info {
padding-left: $gl-padding-50 - $gl-padding-32;
padding-right: $gl-padding;
}
.mr-state-widget { .mr-state-widget {
color: $gl-text-color; color: $gl-text-color;
...@@ -560,6 +565,10 @@ ...@@ -560,6 +565,10 @@
.mr-links { .mr-links {
padding-left: $status-icon-size + $gl-btn-padding; padding-left: $status-icon-size + $gl-btn-padding;
&:last-child {
padding-bottom: $gl-padding;
}
} }
.mr-info-list { .mr-info-list {
...@@ -1030,11 +1039,6 @@ ...@@ -1030,11 +1039,6 @@
background: $black-transparent; background: $black-transparent;
} }
.source-branch-removal-status {
padding-left: 50px;
padding-bottom: $gl-padding;
}
.mr-compare { .mr-compare {
.diff-file .file-title-flex-parent { .diff-file .file-title-flex-parent {
top: $header-height + 51px; top: $header-height + 51px;
......
...@@ -5,8 +5,10 @@ module Clusters ...@@ -5,8 +5,10 @@ module Clusters
include Presentable include Presentable
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include FromUnion include FromUnion
include ReactiveCaching
self.table_name = 'clusters' self.table_name = 'clusters'
self.reactive_cache_key = -> (cluster) { [cluster.class.model_name.singular, cluster.id] }
PROJECT_ONLY_APPLICATIONS = { PROJECT_ONLY_APPLICATIONS = {
Applications::Jupyter.application_name => Applications::Jupyter, Applications::Jupyter.application_name => Applications::Jupyter,
...@@ -57,6 +59,8 @@ module Clusters ...@@ -57,6 +59,8 @@ module Clusters
validate :no_groups, unless: :group_type? validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type? validate :no_projects, unless: :project_type?
after_save :clear_reactive_cache!
delegate :status, to: :provider, allow_nil: true delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true delegate :on_creation?, to: :provider, allow_nil: true
...@@ -123,15 +127,19 @@ module Clusters ...@@ -123,15 +127,19 @@ module Clusters
end end
def status_name def status_name
if provider provider&.status_name || connection_status.presence || :created
provider.status_name end
else
:created def connection_status
with_reactive_cache do |data|
data[:connection_status]
end end
end end
def created? def calculate_reactive_cache
status_name == :created return unless enabled?
{ connection_status: retrieve_connection_status }
end end
def applications def applications
...@@ -221,6 +229,51 @@ module Clusters ...@@ -221,6 +229,51 @@ module Clusters
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end end
def retrieve_connection_status
kubeclient.core_client.discover
rescue *Gitlab::Kubernetes::Errors::CONNECTION
:unreachable
rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
:authentication_failure
rescue Kubeclient::HttpError => e
kubeclient_error_status(e.message)
rescue => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
:unknown_failure
else
:connected
end
# KubeClient uses the same error class
# For connection errors (eg. timeout) and
# for Kubernetes errors.
def kubeclient_error_status(message)
if message&.match?(/timed out|timeout/i)
:unreachable
else
:authentication_failure
end
end
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
# ProjectAutoDevops#Domain, project variables or group variables,
# as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL
#
# This method should is scheduled to be removed on
# https://gitlab.com/gitlab-org/gitlab-ce/issues/56959
def legacy_auto_devops_domain
if project_type?
project&.auto_devops&.domain.presence ||
project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence ||
project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
elsif group_type?
group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
end
end
def restrict_modification def restrict_modification
if provider&.on_creation? if provider&.on_creation?
errors.add(:base, "cannot modify during creation") errors.add(:base, "cannot modify during creation")
......
...@@ -13,6 +13,7 @@ class Identity < ApplicationRecord ...@@ -13,6 +13,7 @@ class Identity < ApplicationRecord
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed? before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider? after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
scope :for_user, ->(user) { where(user: user) }
scope :with_provider, ->(provider) { where(provider: provider) } scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider) iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
......
...@@ -22,10 +22,6 @@ module Clusters ...@@ -22,10 +22,6 @@ module Clusters
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp? "https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end end
def can_toggle_cluster?
can?(current_user, :update_cluster, cluster) && created?
end
def can_read_cluster? def can_read_cluster?
can?(current_user, :read_cluster, cluster) can?(current_user, :read_cluster, cluster)
end end
......
...@@ -11,12 +11,14 @@ ...@@ -11,12 +11,14 @@
= f.hidden_field :user_id = f.hidden_field :user_id
.form-group.row .form-group.row
= f.label :user_id, class: 'col-sm-2 col-form-label' .col-sm-2.col-form-label
= f.label :user_id
.col-sm-10 .col-sm-10
- name = "#{@abuse_report.user.name} (@#{@abuse_report.user.username})" - name = "#{@abuse_report.user.name} (@#{@abuse_report.user.username})"
= text_field_tag :user_name, name, class: "form-control", readonly: true = text_field_tag :user_name, name, class: "form-control", readonly: true
.form-group.row .form-group.row
= f.label :message, class: 'col-sm-2 col-form-label' .col-sm-2.col-form-label
= f.label :message
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url) = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.form-text.text-muted .form-text.text-muted
......
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
= form_errors(application) = form_errors(application)
= content_tag :div, class: 'form-group row' do = content_tag :div, class: 'form-group row' do
= f.label :name, class: 'col-sm-2 col-form-label' .col-sm-2.col-form-label
= f.label :name
.col-sm-10 .col-sm-10
= f.text_field :name, class: 'form-control' = f.text_field :name, class: 'form-control'
= doorkeeper_errors_for application, :name = doorkeeper_errors_for application, :name
= content_tag :div, class: 'form-group row' do = content_tag :div, class: 'form-group row' do
= f.label :redirect_uri, class: 'col-sm-2 col-form-label' .col-sm-2.col-form-label
= f.label :redirect_uri
.col-sm-10 .col-sm-10
= f.text_area :redirect_uri, class: 'form-control' = f.text_area :redirect_uri, class: 'form-control'
= doorkeeper_errors_for application, :redirect_uri = doorkeeper_errors_for application, :redirect_uri
...@@ -21,14 +23,16 @@ ...@@ -21,14 +23,16 @@
for local tests for local tests
= content_tag :div, class: 'form-group row' do = content_tag :div, class: 'form-group row' do
= f.label :trusted, class: 'col-sm-2 col-form-label pt-0' .col-sm-2.col-form-label.pt-0
= f.label :trusted
.col-sm-10 .col-sm-10
= f.check_box :trusted = f.check_box :trusted
%span.form-text.text-muted %span.form-text.text-muted
Trusted applications are automatically authorized on GitLab OAuth flow. Trusted applications are automatically authorized on GitLab OAuth flow.
.form-group.row .form-group.row
= f.label :scopes, class: 'col-sm-2 col-form-label pt-0' .col-sm-2.col-form-label.pt-0
= f.label :scopes
.col-sm-10 .col-sm-10
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
......
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
= form_errors(@broadcast_message) = form_errors(@broadcast_message)
.form-group.row .form-group.row
= f.label :message, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :message
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control js-autosize", = f.text_area :message, class: "form-control js-autosize",
required: true, required: true,
...@@ -20,19 +21,23 @@ ...@@ -20,19 +21,23 @@
.col-sm-10.offset-sm-2 .col-sm-10.offset-sm-2
= link_to 'Customize colors', '#', class: 'js-toggle-colors-link' = link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
.form-group.row.js-toggle-colors-container.toggle-colors.hide .form-group.row.js-toggle-colors-container.toggle-colors.hide
= f.label :color, "Background Color", class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :color, "Background Color"
.col-sm-10 .col-sm-10
= f.color_field :color, class: "form-control" = f.color_field :color, class: "form-control"
.form-group.row.js-toggle-colors-container.toggle-colors.hide .form-group.row.js-toggle-colors-container.toggle-colors.hide
= f.label :font, "Font Color", class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :font, "Font Color"
.col-sm-10 .col-sm-10
= f.color_field :font, class: "form-control" = f.color_field :font, class: "form-control"
.form-group.row .form-group.row
= f.label :starts_at, _("Starts at (UTC)"), class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :starts_at, _("Starts at (UTC)")
.col-sm-10.datetime-controls .col-sm-10.datetime-controls
= f.datetime_select :starts_at, {}, class: 'form-control form-control-inline' = f.datetime_select :starts_at, {}, class: 'form-control form-control-inline'
.form-group.row .form-group.row
= f.label :ends_at, _("Ends at (UTC)"), class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :ends_at, _("Ends at (UTC)")
.col-sm-10.datetime-controls .col-sm-10.datetime-controls
= f.datetime_select :ends_at, {}, class: 'form-control form-control-inline' = f.datetime_select :ends_at, {}, class: 'form-control form-control-inline'
.form-actions .form-actions
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
= render_if_exists 'admin/namespace_plan', f: f = render_if_exists 'admin/namespace_plan', f: f
.form-group.row.group-description-holder .form-group.row.group-description-holder
= f.label :avatar, _("Group avatar"), class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :avatar, _("Group avatar")
.col-sm-10 .col-sm-10
= render 'shared/choose_avatar_button', f: f = render 'shared/choose_avatar_button', f: f
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
= form_errors(@identity) = form_errors(@identity)
.form-group.row .form-group.row
= f.label :provider, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :provider
.col-sm-10 .col-sm-10
- values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] } - values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control' = f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group.row .form-group.row
= f.label :extern_uid, _("Identifier"), class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :extern_uid, _("Identifier")
.col-sm-10 .col-sm-10
= f.text_field :extern_uid, class: 'form-control', required: true = f.text_field :extern_uid, class: 'form-control', required: true
......
...@@ -2,15 +2,18 @@ ...@@ -2,15 +2,18 @@
= form_errors(@label) = form_errors(@label)
.form-group.row .form-group.row
= f.label :title, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :title
.col-sm-10 .col-sm-10
= f.text_field :title, class: "form-control", required: true = f.text_field :title, class: "form-control", required: true
.form-group.row .form-group.row
= f.label :description, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :description
.col-sm-10 .col-sm-10
= f.text_field :description, class: "form-control js-quick-submit" = f.text_field :description, class: "form-control js-quick-submit"
.form-group.row .form-group.row
= f.label :color, _("Background color"), class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :color, _("Background color")
.col-sm-10 .col-sm-10
.input-group .input-group
.input-group-prepend .input-group-prepend
......
...@@ -117,7 +117,8 @@ ...@@ -117,7 +117,8 @@
.card-body .card-body
= form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row .form-group.row
= f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-3' .col-sm-3.col-form-label
= f.label :new_namespace_id, "Namespace"
.col-sm-9 .col-sm-9
.dropdown .dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' }) = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
......
%fieldset %fieldset
%legend Access %legend Access
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :projects_limit, class: 'col-form-label' = f.label :projects_limit
.col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control' .col-sm-10
= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :can_create_group, class: 'col-form-label' = f.label :can_create_group
.col-sm-10= f.check_box :can_create_group .col-sm-10
= f.check_box :can_create_group
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :access_level, class: 'col-form-label' = f.label :access_level
.col-sm-10 .col-sm-10
- editing_current_user = (current_user == @user) - editing_current_user = (current_user == @user)
...@@ -34,8 +36,8 @@ ...@@ -34,8 +36,8 @@
You cannot remove your own admin rights. You cannot remove your own admin rights.
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :external, class: 'col-form-label' = f.label :external
.hidden{ data: user_internal_regex_data } .hidden{ data: user_internal_regex_data }
.col-sm-10 .col-sm-10
= f.check_box :external do = f.check_box :external do
......
...@@ -5,20 +5,20 @@ ...@@ -5,20 +5,20 @@
%fieldset %fieldset
%legend Account %legend Account
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :name, class: 'col-form-label' = f.label :name
.col-sm-10 .col-sm-10
= f.text_field :name, required: true, autocomplete: 'off', class: 'form-control' = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required %span.help-inline * required
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :username, class: 'col-form-label' = f.label :username
.col-sm-10 .col-sm-10
= f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control' = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required %span.help-inline * required
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :email, class: 'col-form-label' = f.label :email
.col-sm-10 .col-sm-10
= f.text_field :email, required: true, autocomplete: 'off', class: 'form-control' = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required %span.help-inline * required
...@@ -27,8 +27,8 @@ ...@@ -27,8 +27,8 @@
%fieldset %fieldset
%legend Password %legend Password
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :password, class: 'col-form-label' = f.label :password
.col-sm-10 .col-sm-10
%strong %strong
Reset link will be generated and sent to the user. Reset link will be generated and sent to the user.
...@@ -38,13 +38,15 @@ ...@@ -38,13 +38,15 @@
%fieldset %fieldset
%legend Password %legend Password
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :password, class: 'col-form-label' = f.label :password
.col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control' .col-sm-10
= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :password_confirmation, class: 'col-form-label' = f.label :password_confirmation
.col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control' .col-sm-10
= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
= render partial: 'access_levels', locals: { f: f } = render partial: 'access_levels', locals: { f: f }
...@@ -55,27 +57,31 @@ ...@@ -55,27 +57,31 @@
%fieldset %fieldset
%legend Profile %legend Profile
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :avatar, class: 'col-form-label' = f.label :avatar
.col-sm-10 .col-sm-10
= f.file_field :avatar = f.file_field :avatar
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :skype, class: 'col-form-label' = f.label :skype
.col-sm-10= f.text_field :skype, class: 'form-control' .col-sm-10
= f.text_field :skype, class: 'form-control'
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :linkedin, class: 'col-form-label' = f.label :linkedin
.col-sm-10= f.text_field :linkedin, class: 'form-control' .col-sm-10
= f.text_field :linkedin, class: 'form-control'
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :twitter, class: 'col-form-label' = f.label :twitter
.col-sm-10= f.text_field :twitter, class: 'form-control' .col-sm-10
= f.text_field :twitter, class: 'form-control'
.form-group.row .form-group.row
.col-sm-2.text-right .col-sm-2.col-form-label
= f.label :website_url, 'Website', class: 'col-form-label' = f.label :website_url
.col-sm-10= f.text_field :website_url, class: 'form-control' .col-sm-10
= f.text_field :website_url, class: 'form-control'
= render_if_exists 'admin/users/admin_notes', f: f = render_if_exists 'admin/users/admin_notes', f: f
......
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
.append-right-default .append-right-default
= s_("CiVariable|Masked") = s_("CiVariable|Masked")
%button{ type: 'button', %button{ type: 'button',
class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_masked}", class: "js-project-feature-toggle project-feature-toggle qa-variable-masked #{'is-checked' if is_masked}",
"aria-label": s_("CiVariable|Toggle masked") } "aria-label": s_("CiVariable|Toggle masked") }
%input{ type: "hidden", %input{ type: "hidden",
class: 'js-ci-variable-input-masked js-project-feature-toggle-input', class: 'js-ci-variable-input-masked js-project-feature-toggle-input',
......
...@@ -5,5 +5,17 @@ ...@@ -5,5 +5,17 @@
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' } .hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...') = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
.col-11
= s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
.col-1.p-0
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
.hidden.js-cluster-authentication-failure.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
.col-11
= s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
.col-1.p-0
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' } .hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
...@@ -24,7 +24,8 @@ ...@@ -24,7 +24,8 @@
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'), ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'), ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
manage_prometheus_path: manage_prometheus_path } } manage_prometheus_path: manage_prometheus_path,
cluster_id: @cluster.id } }
.js-cluster-application-notice .js-cluster-application-notice
.flash-container .flash-container
......
.form-group .form-group
= f.label :create_chat_team, class: 'col-form-label' do .col-sm-2.col-form-label
%span.mattermost-icon = f.label :create_chat_team do
= custom_icon('icon_mattermost') %span.mattermost-icon
Mattermost = custom_icon('icon_mattermost')
Mattermost
.col-sm-10 .col-sm-10
.form-check.js-toggle-container .form-check.js-toggle-container
.js-toggle-button.form-check-input= f.check_box(:create_chat_team, { checked: true }, true, false) .js-toggle-button.form-check-input= f.check_box(:create_chat_team, { checked: true }, true, false)
......
.form-group.row .form-group.row
= f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2 pt-0' .col-sm-2.col-form-label.pt-0
= f.label :lfs_enabled, 'Large File Storage'
.col-sm-10 .col-sm-10
.form-check .form-check
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input' = f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
...@@ -10,12 +11,14 @@ ...@@ -10,12 +11,14 @@
%br/ %br/
%span.descr This setting can be overridden in each project. %span.descr This setting can be overridden in each project.
.form-group.row .form-group.row
= f.label s_('ProjectCreationLevel|Allowed to create projects'), class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label s_('ProjectCreationLevel|Allowed to create projects')
.col-sm-10 .col-sm-10
= f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, @group.project_creation_level), {}, class: 'form-control' = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, @group.project_creation_level), {}, class: 'form-control'
.form-group.row .form-group.row
= f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0' .col-sm-2.col-form-label.pt-0
= f.label :require_two_factor_authentication, 'Two-factor authentication'
.col-sm-10 .col-sm-10
.form-check .form-check
= f.check_box :require_two_factor_authentication, class: 'form-check-input' = f.check_box :require_two_factor_authentication, class: 'form-check-input'
......
...@@ -10,15 +10,17 @@ ...@@ -10,15 +10,17 @@
.container.navless-container .container.navless-container
.content .content
= render "layouts/flash" = render "layouts/flash"
.row.append-bottom-15 .row.mt-3
.col-sm-7.brand-holder .col-sm-12
%h1 %h1.mb-3.font-weight-normal
= brand_title = brand_title
.row.mb-3
.col-sm-7.order-12.order-sm-1.brand-holder
= brand_image = brand_image
- if current_appearance&.description? - if current_appearance&.description?
= brand_text = brand_text
- else - else
%h3 %h3.mt-sm-0
= _('Open source software to collaborate on code') = _('Open source software to collaborate on code')
%p %p
...@@ -29,7 +31,7 @@ ...@@ -29,7 +31,7 @@
= render_if_exists 'layouts/devise_help_text' = render_if_exists 'layouts/devise_help_text'
.col-sm-5.new-session-forms-container .col-sm-5.order-1.order-sm-12.new-session-forms-container
= yield = yield
%hr.footer-fixed %hr.footer-fixed
......
...@@ -13,13 +13,18 @@ ...@@ -13,13 +13,18 @@
- unless @user.password_automatically_set? - unless @user.password_automatically_set?
.form-group.row .form-group.row
= f.label :current_password, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
.col-sm-10= f.password_field :current_password, required: true, class: 'form-control' = f.label :current_password
.col-sm-10
= f.password_field :current_password, required: true, class: 'form-control'
.form-group.row .form-group.row
= f.label :password, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
.col-sm-10= f.password_field :password, required: true, class: 'form-control' = f.label :password
.col-sm-10
= f.password_field :password, required: true, class: 'form-control'
.form-group.row .form-group.row
= f.label :password_confirmation, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :password_confirmation
.col-sm-10 .col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control' = f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions .form-actions
......
...@@ -5,22 +5,22 @@ ...@@ -5,22 +5,22 @@
%p= msg %p= msg
.form-group.row .form-group.row
= f.label :domain, class: 'col-form-label col-sm-2' do .col-sm-2.col-form-label
= _("Domain") = f.label :domain, _("Domain")
.col-sm-10 .col-sm-10
= f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control', disabled: @domain.persisted? = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control', disabled: @domain.persisted?
- if Gitlab.config.pages.external_https - if Gitlab.config.pages.external_https
.form-group.row .form-group.row
= f.label :certificate, class: 'col-form-label col-sm-2' do .col-sm-2.col-form-label
= _("Certificate (PEM)") = f.label :certificate, _("Certificate (PEM)")
.col-sm-10 .col-sm-10
= f.text_area :certificate, rows: 5, class: 'form-control' = f.text_area :certificate, rows: 5, class: 'form-control'
%span.help-inline= _("Upload a certificate for your domain with all intermediates") %span.help-inline= _("Upload a certificate for your domain with all intermediates")
.form-group.row .form-group.row
= f.label :key, class: 'col-form-label col-sm-2' do .col-sm-2.col-form-label
= _("Key (PEM)") = f.label :key, _("Key (PEM)")
.col-sm-10 .col-sm-10
= f.text_area :key, rows: 5, class: 'form-control' = f.text_area :key, rows: 5, class: 'form-control'
%span.help-inline= _("Upload a private key for your certificate") %span.help-inline= _("Upload a private key for your certificate")
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
.card-body .card-body
= form_errors(@protected_branch) = form_errors(@protected_branch)
.form-group.row .form-group.row
= f.label :name, class: 'col-md-2 text-right' do .col-md-2.text-right
Branch: = f.label :name, 'Branch:'
.col-md-10 .col-md-10
= render partial: "projects/protected_branches/shared/dropdown", locals: { f: f } = render partial: "projects/protected_branches/shared/dropdown", locals: { f: f }
.form-text.text-muted .form-text.text-muted
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
.card-body .card-body
= form_errors(@protected_tag) = form_errors(@protected_tag)
.form-group.row .form-group.row
= f.label :name, class: 'col-md-2 text-right' do .col-md-2.text-right
Tag: = f.label :name, 'Tag:'
.col-md-10.protected-tags-dropdown .col-md-10.protected-tags-dropdown
= render partial: "projects/protected_tags/shared/dropdown", locals: { f: f } = render partial: "projects/protected_tags/shared/dropdown", locals: { f: f }
.form-text.text-muted .form-text.text-muted
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
- setting = error_tracking_setting - setting = error_tracking_setting
%section.settings.expanded.no-animate %section.settings.no-animate.js-error-tracking-settings
.settings-header .settings-header
%h4 %h4
= _('Error Tracking') = _('Error Tracking')
%button.btn.js-settings-toggle{ type: 'button' }
= _('Expand')
%p %p
= _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.') = _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.')
= link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer' = link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer'
......
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
- breadcrumb_title _('Operations Settings') - breadcrumb_title _('Operations Settings')
= render_if_exists 'projects/settings/operations/incidents' = render_if_exists 'projects/settings/operations/incidents'
= render 'projects/settings/operations/error_tracking', expanded: true = render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/external_dashboard' = render 'projects/settings/operations/external_dashboard'
= render_if_exists 'projects/settings/operations/tracing' = render_if_exists 'projects/settings/operations/tracing'
...@@ -2,17 +2,20 @@ ...@@ -2,17 +2,20 @@
= form_errors(@label) = form_errors(@label)
.form-group.row .form-group.row
= f.label :title, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :title
.col-sm-10 .col-sm-10
= f.text_field :title, class: "form-control js-label-title qa-label-title", required: true, autofocus: true = f.text_field :title, class: "form-control js-label-title qa-label-title", required: true, autofocus: true
= render_if_exists 'shared/labels/create_label_help_text' = render_if_exists 'shared/labels/create_label_help_text'
.form-group.row .form-group.row
= f.label :description, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :description
.col-sm-10 .col-sm-10
= f.text_field :description, class: "form-control js-quick-submit qa-label-description" = f.text_field :description, class: "form-control js-quick-submit qa-label-description"
.form-group.row .form-group.row
= f.label :color, "Background color", class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :color, "Background color"
.col-sm-10 .col-sm-10
.input-group .input-group
.input-group-prepend .input-group-prepend
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
= form_errors(@snippet) = form_errors(@snippet)
.form-group.row .form-group.row
= f.label :title, class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :title
.col-sm-10 .col-sm-10
= f.text_field :title, class: 'form-control qa-snippet-title', required: true, autofocus: true = f.text_field :title, class: 'form-control qa-snippet-title', required: true, autofocus: true
...@@ -17,7 +18,8 @@ ...@@ -17,7 +18,8 @@
.file-editor .file-editor
.form-group.row .form-group.row
= f.label :file_name, "File", class: 'col-form-label col-sm-2' .col-sm-2.col-form-label
= f.label :file_name, "File"
.col-sm-10 .col-sm-10
.file-holder.snippet .file-holder.snippet
.js-file-title.file-title .js-file-title.file-title
......
---
title: Fix border radii on diff files and repo files
merge_request:
author:
type: fixed
---
title: Validate Kubernetes credentials at cluster creation
merge_request: 27403
author:
type: added
---
title: Fix col-sm-* in forms to keep layout
merge_request: 24885
author: Takuya Noguchi
type: fixed
---
title: Fix padding in MR widget
merge_request: 28472
author:
type: fixed
---
title: Prioritize login form on mobile breakpoint
merge_request: 28360
author:
type: changed
---
title: Fix input group height
merge_request:
author:
type: other
---
title: Bring secondary button styles up to design standard
merge_request: 27920
author:
type: fixed
---
title: Add expand/collapse to error tracking settings
merge_request: 28619
author:
type: added
---
title: Prevent icons from shrinking in User popover when contents exceed container
merge_request: 28696
author:
type: fixed
---
title: Remove the circuit breaker API
merge_request: 28669
author:
type: removed
---
title: Fix milestone references containing &, <, or >
merge_request: 28667
author:
type: fixed
---
title: Update SAST.gitlab-ci.yml - Add SAST_GITLEAKS_ENTROPY_LEVEL
merge_request: 28607
author:
type: fixed
---
title: 'API: change masked attribute type to Boolean'
merge_request: 28758
author:
type: other
...@@ -23,6 +23,8 @@ to help identify if something is wrong: ...@@ -23,6 +23,8 @@ to help identify if something is wrong:
![Geo health check](img/geo_node_healthcheck.png) ![Geo health check](img/geo_node_healthcheck.png)
For information on how to resolve common errors reported from the UI, see [common errors](#common-errors).
If the UI is not working, or you are unable to log in, you can run the Geo If the UI is not working, or you are unable to log in, you can run the Geo
health check manually to get this information as well as a few more details. health check manually to get this information as well as a few more details.
This rake task can be run on an app node in the **primary** or **secondary** This rake task can be run on an app node in the **primary** or **secondary**
...@@ -40,7 +42,8 @@ Checking Geo ... ...@@ -40,7 +42,8 @@ Checking Geo ...
GitLab Geo is available ... yes GitLab Geo is available ... yes
GitLab Geo is enabled ... yes GitLab Geo is enabled ... yes
GitLab Geo secondary database is correctly configured ... yes GitLab Geo secondary database is correctly configured ... yes
Using database streaming replication? ... yes Database replication enabled? ... yes
Database replication working? ... yes
GitLab Geo tracking database is configured to use Foreign Data Wrapper? ... yes GitLab Geo tracking database is configured to use Foreign Data Wrapper? ... yes
GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... yes GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... yes
GitLab Geo HTTP(S) connectivity ... GitLab Geo HTTP(S) connectivity ...
...@@ -68,22 +71,22 @@ Example output: ...@@ -68,22 +71,22 @@ Example output:
``` ```
http://secondary.example.com/ http://secondary.example.com/
----------------------------------------------------- -----------------------------------------------------
GitLab Version: 11.8.1-ee GitLab Version: 11.10.4-ee
Geo Role: Secondary Geo Role: Secondary
Health Status: Healthy Health Status: Healthy
Repositories: 190/190 (100%) Repositories: 289/289 (100%)
Verified Repositories: 190/190 (100%) Verified Repositories: 289/289 (100%)
Wikis: 190/190 (100%) Wikis: 289/289 (100%)
Verified Wikis: 190/190 (100%) Verified Wikis: 289/289 (100%)
LFS Objects: 35/35 (100%) LFS Objects: 8/8 (100%)
Attachments: 528/528 (100%) Attachments: 5/5 (100%)
CI job artifacts: 477/477 (100%) CI job artifacts: 0/0 (0%)
Repositories Checked: 0/190 (0%) Repositories Checked: 0/289 (0%)
Sync Settings: Full Sync Settings: Full
Database replication lag: 0 seconds Database replication lag: 0 seconds
Last event ID seen from primary: 2158 (about 2 minute ago) Last event ID seen from primary: 10215 (about 2 minutes ago)
Last event ID processed by cursor: 2158 (about 2 minute ago) Last event ID processed by cursor: 10215 (about 2 minutes ago)
Last status report was: 4 minutes ago Last status report was: 2 minutes ago
``` ```
## Is Postgres replication working? ## Is Postgres replication working?
...@@ -455,3 +458,57 @@ reload of the FDW schema. To manually reload the FDW schema: ...@@ -455,3 +458,57 @@ reload of the FDW schema. To manually reload the FDW schema:
[database-start-replication]: database.md#step-3-initiate-the-replication-process [database-start-replication]: database.md#step-3-initiate-the-replication-process
[database-pg-replication]: database.md#postgresql-replication [database-pg-replication]: database.md#postgresql-replication
## Common errors
This section documents common errors reported in the admin UI and how to fix them.
### Geo database configuration file is missing
GitLab cannot find or doesn't have permission to access the `database_geo.yml` configuration file.
In an Omnibus GitLab installation, the file should be in `/var/opt/gitlab/gitlab-rails/etc`.
If it doesn't exist or inadvertent changes have been made to it, run `sudo gitlab-ctl reconfigure` to restore it to its correct state.
If this path is mounted on a remote volume, please check your volume configuration and that it has correct permissions.
### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.
This error refers to a problem with the database replica on a **secondary** node,
which Geo expects to have access to. It usually means, either:
- An unsupported replication method was used (for example, logical replication).
- The instructions to setup a [Geo database replication](database.md) were not followed correctly.
A common source of confusion with **secondary** nodes is that it requires two separate
PostgreSQL instances:
- A read-only replica of the **primary** node.
- A regular, writable instance that holds replication metadata. That is, the Geo tracking database.
### Geo node does not appear to be replicating the database from the primary node.
The most common problems that prevent the database from replicating correctly are:
- **Secondary** nodes cannot reach the **primary** node. Check credentials, firewall rules, etc.
- SSL certificate problems. Make sure you copied `/etc/gitlab/gitlab-secrets.json` from the **primary** node.
- Database storage disk is full.
- Database replication slot is misconfigured.
- Database is not using a replication slot or another alternative and cannot catch-up because WAL files were purged.
Make sure you follow the [Geo database replication](database.md) instructions for supported configuration.
### Geo database version (...) does not match latest migration (...)
If you are using GitLab Omnibus installation, something might have failed during upgrade. You can:
- Run `sudo gitlab-ctl reconfigure`.
- Manually trigger the database migration by running: `sudo gitlab-rake geo:db:migrate` as root on the **secondary** node.
### Geo database is not configured to use Foreign Data Wrapper
This error means the Geo Tracking Database doesn't have the FDW server and credentials
configured.
See [How do I fix a "Foreign Data Wrapper (FDW) is not configured" error?](#how-do-i-fix-a-foreign-data-wrapper-fdw-is-not-configured-error).
...@@ -135,7 +135,7 @@ the contention. ...@@ -135,7 +135,7 @@ the contention.
- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq, PGBouncer) - 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq, PGBouncer)
- 1 NFS/Gitaly server - 1 NFS/Gitaly server
![Horizontal architecture diagram](https://docs.gitlab.com/ee/administration/img/high_availability/horizontal.png) ![Horizontal architecture diagram](img/horizontal.png)
### Hybrid ### Hybrid
...@@ -153,7 +153,7 @@ contention due to certain workloads. ...@@ -153,7 +153,7 @@ contention due to certain workloads.
- 1 or more NFS/Gitaly servers - 1 or more NFS/Gitaly servers
- 1 Monitoring node (Prometheus, Grafana) - 1 Monitoring node (Prometheus, Grafana)
![Hybrid architecture diagram](https://docs.gitlab.com/ee/administration/img/high_availability/hybrid.png) ![Hybrid architecture diagram](img/hybrid.png)
#### Reference Architecture #### Reference Architecture
...@@ -194,7 +194,7 @@ with the added complexity of many more nodes to configure, manage and monitor. ...@@ -194,7 +194,7 @@ with the added complexity of many more nodes to configure, manage and monitor.
- 2 or more Web nodes (All other web requests) - 2 or more Web nodes (All other web requests)
- 2 or more NFS/Gitaly servers - 2 or more NFS/Gitaly servers
![Fully Distributed architecture diagram](https://docs.gitlab.com/ee/administration/img/high_availability/fully-distributed.png) ![Fully Distributed architecture diagram](img/fully-distributed.png)
The following pages outline the steps necessary to configure each component The following pages outline the steps necessary to configure each component
separately: separately:
......
...@@ -134,7 +134,7 @@ otherwise the networks will become a single point of failure. ...@@ -134,7 +134,7 @@ otherwise the networks will become a single point of failure.
#### Architecture #### Architecture
![PG HA Architecture](pg_ha_architecture.png) ![PG HA Architecture](img/pg_ha_architecture.png)
Database nodes run two services with PostgreSQL: Database nodes run two services with PostgreSQL:
......
# Circuitbreaker API
NOTE: **Deprecated:**
Support of the circuit breaker is removed, as Gitaly can be configured to
to work without NFS and [communicate solely over HTTP](../administration/gitaly/index.md).
...@@ -59,6 +59,7 @@ description: 'Learn how to contribute to GitLab.' ...@@ -59,6 +59,7 @@ description: 'Learn how to contribute to GitLab.'
- [DeclarativePolicy framework](policies.md) - [DeclarativePolicy framework](policies.md)
- [How Git object deduplication works in GitLab](git_object_deduplication.md) - [How Git object deduplication works in GitLab](git_object_deduplication.md)
- [Geo development](geo.md) - [Geo development](geo.md)
- [Routing](routing.md)
## Performance guides ## Performance guides
......
This diff is collapsed.
...@@ -27,7 +27,7 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -27,7 +27,7 @@ are very appreciative of the work done by translators and proofreaders!
- Czech - Czech
- Proofreaders needed. - Proofreaders needed.
- Danish - Danish
- Proofreaders needed. - Saederup92 - [GitLab](https://gitlab.com/Saederup92), [Crowdin](https://crowdin.com/profile/Saederup92)
- Dutch - Dutch
- Emily Hendle - [GitLab](https://gitlab.com/pundachan), [Crowdin](https://crowdin.com/profile/pandachan) - Emily Hendle - [GitLab](https://gitlab.com/pundachan), [Crowdin](https://crowdin.com/profile/pandachan)
- Esperanto - Esperanto
......
...@@ -108,11 +108,13 @@ To make sure that indices still fit. You could find great details in: ...@@ -108,11 +108,13 @@ To make sure that indices still fit. You could find great details in:
In order to run the test you can use the following commands: In order to run the test you can use the following commands:
- `rake spec` to run the rspec suite - `bin/rake spec` to run the rspec suite
- `rake karma` to run the karma test suite - `bin/rake spec:unit` to run the only the unit tests
- `rake gitlab:test` to run all the tests - `bin/rake spec:integration` to run the only the integration tests
- `bin/rake spec:system` to run the only the system tests
- `bin/rake karma` to run the karma test suite
Note: `rake spec` takes significant time to pass. Note: `bin/rake spec` takes significant time to pass.
Instead of running full test suite locally you can save a lot of time by running Instead of running full test suite locally you can save a lot of time by running
a single test or directory related to your changes. After you submit merge request a single test or directory related to your changes. After you submit merge request
CI will run full test suite for you. Green CI status in the merge request means CI will run full test suite for you. Green CI status in the merge request means
...@@ -121,6 +123,9 @@ full test suite is passed. ...@@ -121,6 +123,9 @@ full test suite is passed.
Note: You can't run `rspec .` since this will try to run all the `_spec.rb` Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
files it can find, also the ones in `/tmp` files it can find, also the ones in `/tmp`
Note: You can pass RSpec command line options to the `spec:unit`,
`spec:integration`, and `spec:system` tasks, e.g. `bin/rake "spec:unit[--tag ~geo --dry-run]"`.
To run a single test file you can use: To run a single test file you can use:
- `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test - `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test
......
# Routing
The GitLab backend is written primarily with Rails so it uses [Rails
routing](https://guides.rubyonrails.org/routing.html). Beside Rails best
practices, there are few rules unique to the GitLab application. To
support subgroups, GitLab project and group routes use the wildcard
character to match project and group routes. For example, we might have
a path such as:
/gitlab-com/customer-success/north-america/west/customerA
However, paths can be ambiguous. Consider the following example:
/gitlab-com/edit
It's ambiguous whether there is a subgroup named `edit` or whether
this is a special endpoint to edit the `gitlab-com` group.
To eliminate the ambiguity and to make the backend easier to maintain,
we introduced the `/-/` scope. The purpose of it is to separate group or
project paths from the rest of the routes. Also it helps to reduce the
number of [reserved names](../user/reserved_names.md).
## Global routes
We have a number of global routes. For example:
/-/health
/-/metrics
## Group routes
Every group route must be under the `/-/` scope.
Examples:
gitlab-org/-/edit
gitlab-org/-/activity
gitlab-org/-/security/dashboard
gitlab-org/serverless/-/activity
To achieve that, use the `scope '-'` method.
## Project routes
Every project route must be under the `/-/` scope, except cases where a Git
client or other software requires something different.
Examples:
gitlab-org/gitlab-ce/-/activity
gitlab-org/gitlab-ce/-/jobs/123
gitlab-org/gitlab-ce/-/settings/repository
gitlab-org/serverless/runtimes/-/settings/repository
Currently, only some project routes are placed under the `/-/` scope. However,
you can help us migrate more of them! To migrate project routes:
1. Modify existing routes by adding `-` scope.
1. Add redirects for legacy routes by using `Gitlab::Routing.redirect_legacy_paths`.
1. Create a technical debt issue to remove deprecated routes in later releases.
To get started, see an [example merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28435).
...@@ -2,19 +2,29 @@ ...@@ -2,19 +2,29 @@
## Test Design ## Test Design
Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
as we do the design of our features. as we do the design of our features.
When implementing a feature, we think about developing the right capabilities the right way, which helps us When implementing a feature, we think about developing the right capabilities the right way, which helps us
narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to
a level that is difficult to manage. a level that is difficult to manage.
Test heuristics can help solve this problem. They concisely address many of the common ways bugs Test heuristics can help solve this problem. They concisely address many of the common ways bugs
manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
our test design. We can find some helpful heuristics documented in the Handbook in the our test design. We can find some helpful heuristics documented in the Handbook in the
[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section. [Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
## Run tests against MySQL
By default, tests are only run againts PostgreSQL, but you can run them on
demand against MySQL by following one of the following conventions:
| Convention | Valid example |
|:----------------------|:-----------------------------|
| Include `mysql` in your branch name | `enhance-mysql-support` |
| Include `[run mysql]` in your commit message | `Fix MySQL support<br><br>[run mysql]` |
## Test speed ## Test speed
GitLab has a massive test suite that, without [parallelization], can take hours GitLab has a massive test suite that, without [parallelization], can take hours
...@@ -184,11 +194,11 @@ instead of 30+ seconds in case of a regular `spec_helper`. ...@@ -184,11 +194,11 @@ instead of 30+ seconds in case of a regular `spec_helper`.
### `let` variables ### `let` variables
GitLab's RSpec suite has made extensive use of `let`(along with it strict, non-lazy GitLab's RSpec suite has made extensive use of `let`(along with it strict, non-lazy
version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity][lets-not], version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity][lets-not],
so we need to set some guidelines for their use going forward: so we need to set some guidelines for their use going forward:
- `let!` variables are preferable to instance variables. `let` variables - `let!` variables are preferable to instance variables. `let` variables
are preferable to `let!` variables. Local variables are preferable to are preferable to `let!` variables. Local variables are preferable to
`let` variables. `let` variables.
- Use `let` to reduce duplication throughout an entire spec file. - Use `let` to reduce duplication throughout an entire spec file.
- Don't use `let` to define variables used by a single test; define them as - Don't use `let` to define variables used by a single test; define them as
...@@ -199,8 +209,8 @@ so we need to set some guidelines for their use going forward: ...@@ -199,8 +209,8 @@ so we need to set some guidelines for their use going forward:
- Try to avoid overriding the definition of one `let` variable with another. - Try to avoid overriding the definition of one `let` variable with another.
- Don't define a `let` variable that's only used by the definition of another. - Don't define a `let` variable that's only used by the definition of another.
Use a helper method instead. Use a helper method instead.
- `let!` variables should be used only in case if strict evaluation with defined - `let!` variables should be used only in case if strict evaluation with defined
order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
be evaluated until it is referenced. be evaluated until it is referenced.
[lets-not]: https://robots.thoughtbot.com/lets-not [lets-not]: https://robots.thoughtbot.com/lets-not
......
...@@ -4,12 +4,14 @@ ...@@ -4,12 +4,14 @@
_This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._ _This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._
As of 2019-04-16, we have the following distribution of tests per level: As of 2019-05-01, we have the following distribution of tests per level:
- 67 black-box tests at the system level (aka end-to-end or QA tests) in CE, 98 in EE. This represents 0.3% of all the CE tests (0.3% in EE). | Test level | Community Edition | Enterprise Edition | Community + Enterprise Edition |
- 5,457 white-box tests at the system level (aka system or feature tests) in CE, 6,585 in EE. This represents 24.6% of all the CE tests (20.3% in EE). | --------- | ---------- | -------------- | ----- |
- 8,298 integration tests in CE, 10,633 in EE: 0.3% of all the CE tests (0.3% in EE). This represents 37.2% of all the CE tests (32.8% in EE). | Black-box tests at the system level (aka end-to-end or QA tests) | 68 (0.14%) | 31 (0.2%) | 99 (0.17%) |
- 8,403 unit tests in CE, 15,090 in EE: 0.3% of all the CE tests (0.3% in EE). This represents 37.8% of all the CE tests (46.6% in EE). | White-box tests at the system level (aka system or feature tests) | 5,471 (11.9%) | 969 (7.4%) | 6440 (10.9%) |
| Integration tests | 8,333 (18.2%) | 2,244 (17.2%) | 10,577 (17.9%) |
| Unit tests | 32,031 (69.7%) | 9,778 (75.1%) | 41,809 (71%) |
## Unit tests ## Unit tests
......
# SalesForce OmniAuth Provider # Salesforce OmniAuth Provider
You can integrate your GitLab instance with [SalesForce](https://www.salesforce.com/) to enable users to login to your GitLab instance with their SalesForce account. You can integrate your GitLab instance with [Salesforce](https://www.salesforce.com/) to enable users to log in to your GitLab instance with their Salesforce account.
## Create SalesForce Application ## Create a Salesforce Connected App
To enable SalesForce OmniAuth provider, you must use SalesForce's credentials for your GitLab instance. To enable Salesforce OmniAuth provider, you must use Salesforce's credentials for your GitLab instance.
To get the credentials (a pair of Client ID and Client Secret), you must register an application on SalesForces. To get the credentials (a pair of Client ID and Client Secret), you must [create a Connected App](https://help.salesforce.com/articleView?id=connected_app_create.htm&type=5) on Salesforce.
1. Sign in to [SalesForce](https://www.salesforce.com/). 1. Sign in to [Salesforce](https://login.salesforce.com/).
1. Navigate to **Platform Tools/Apps/App Manager** and click on **New Connected App**. 1. In Setup, enter `App Manager` in the Quick Find box, click **App Manager**, then click **New Connected App**.
1. Fill in the application details into the following fields: 1. Fill in the application details into the following fields:
- **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive. - **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive.
- **Contact Email**: Enter the contact email for Salesforce to use when contacting you or your support team.
- **Description**: Description for the application. - **Description**: Description for the application.
![SalesForce App Details](img/salesforce_app_details.png) ![Salesforce App Details](img/salesforce_app_details.png)
1. Select **API (Enable OAuth Settings)** and click on **Enable OAuth Settings**. 1. Select **API (Enable OAuth Settings)** and click on **Enable OAuth Settings**.
1. Fill in the application details into the following fields: 1. Fill in the application details into the following fields:
- **Callback URL**: The call callback URL. For example, `https://gitlab.example.com/users/auth/salesforce/callback`. - **Callback URL**: The callback URL of your GitLab installation. For example, `https://gitlab.example.com/users/auth/salesforce/callback`.
- **Selected OAuth Scopes**: Move **Access your basic information (id, profile, email, address, phone)** and **Allow access to your unique identifier (openid)** to the right column. - **Selected OAuth Scopes**: Move **Access your basic information (id, profile, email, address, phone)** and **Allow access to your unique identifier (openid)** to the right column.
![SalesForce Oauth App Details](img/salesforce_oauth_app_details.png) ![Salesforce Oauth App Details](img/salesforce_oauth_app_details.png)
1. Click **Save**. 1. Click **Save**.
1. On your GitLab server, open the configuration file. 1. On your GitLab server, open the configuration file.
...@@ -63,17 +64,16 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe ...@@ -63,17 +64,16 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
app_secret: 'SALESFORCE_CLIENT_SECRET' app_secret: 'SALESFORCE_CLIENT_SECRET'
} }
``` ```
1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the SalesForce connected application page. 1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the Salesforce connected application page.
1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the SalesForce connected application page. 1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the Salesforce connected application page.
![SalesForce App Secret Details](img/salesforce_app_secret_details.png) ![Salesforce App Secret Details](img/salesforce_app_secret_details.png)
1. Save the configuration file. 1. Save the configuration file.
1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you 1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
installed GitLab via Omnibus or from source respectively.
On the sign in page, there should now be a SalesForce icon below the regular sign in form. On the sign in page, there should now be a Salesforce icon below the regular sign in form.
Click the icon to begin the authentication process. SalesForce will ask the user to sign in and authorize the GitLab application. Click the icon to begin the authentication process. Salesforce will ask the user to sign in and authorize the GitLab application.
If everything goes well, the user will be returned to GitLab and will be signed in. If everything goes well, the user will be returned to GitLab and will be signed in.
NOTE: **Note:** NOTE: **Note:**
GitLab requires the email address of each new user. Once the user is logged in using SalesForce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email. GitLab requires the email address of each new user. Once the user is logged in using Salesforce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email.
\ No newline at end of file
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
The new folder needs to have git user ownership and read/write/execute access for git user and its group: The new folder needs to have git user ownership and read/write/execute access for git user and its group:
``` ```
sudo -u git mkdir /var/opt/gitlab/git-data/repository-import-<date>/new_group sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group
``` ```
### Copy your bare repositories inside this newly created folder: ### Copy your bare repositories inside this newly created folder:
......
---
type: reference
---
# GitLab Admin Area **[CORE ONLY]** # GitLab Admin Area **[CORE ONLY]**
The Admin Area provides a web UI for administering some features of GitLab self-managed instances. The Admin Area provides a web UI for administering some features of GitLab self-managed instances.
...@@ -16,7 +20,7 @@ The Admin Area is made up of the following sections: ...@@ -16,7 +20,7 @@ The Admin Area is made up of the following sections:
| Section | Description | | Section | Description |
|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------| |:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
| Overview | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administer-projects), users, groups, jobs, runners, and Gitaly servers. | | Overview | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administer-projects), [users](#administer-users), groups, jobs, runners, and Gitaly servers. |
| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. | | Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. | | Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. | | System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
...@@ -90,3 +94,23 @@ You can combine the filter options. For example, to list only public projects wi ...@@ -90,3 +94,23 @@ You can combine the filter options. For example, to list only public projects wi
1. Click the **Public** tab. 1. Click the **Public** tab.
1. Enter `score` in the **Filter by name...** input box. 1. Enter `score` in the **Filter by name...** input box.
## Administer Users
You can administer all users in the GitLab instance from the Admin Area's Users page.
To access the Users page, go to **Admin Area > Overview > Users**.
Click the **Active**, **Admins**, **2FA Enabled**, or **2FA Disabled**, **External**, or
**Without projects** tab to list only users of that criteria.
For each user, their username, email address, are listed, also the date their account was
created and the date of last activity. To edit a user, click the **Edit** button in that user's
row. To delete the user, or delete the user and their contributions, click the cog dropdown in
that user's row, and select the desired option.
To change the sort order, click the sort dropdown and select the desired order. By default the sort dropdown shows **Name**.
To search for users, enter your criteria in the search field. The user search is case
insensitive, and applies partial matching to name and username. To search for an email address,
you must provide the complete email address.
---
type: reference
---
# Labels administration **[CORE ONLY]** # Labels administration **[CORE ONLY]**
## Default Labels In the Admin Area, you can manage labels for the GitLab instance. For more details, see [Labels](../project/labels.md).
### Define your own default Label Set ## Default Labels
Labels that are created within the Labels view on the Admin Dashboard will be automatically added to each new project. Labels created in the Admin Area become available to each _new_ project.
![Default label set](img/admin_labels.png) ![Default label set](img/admin_labels.png)
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's
important to describe those, too. Think of things that may go wrong and include them here.
This is important to minimize requests for support, and to avoid doc comments with
questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
---
type: howto
---
# Activate all GitLab Enterprise Edition functionality with a license **[STARTER ONLY]** # Activate all GitLab Enterprise Edition functionality with a license **[STARTER ONLY]**
To activate all GitLab Enterprise Edition (EE) functionality, you need to upload To activate all GitLab Enterprise Edition (EE) functionality, you need to upload
...@@ -108,3 +112,15 @@ but only the latest license will be used as the active license. ...@@ -108,3 +112,15 @@ but only the latest license will be used as the active license.
[free trial]: https://about.gitlab.com/free-trial/ [free trial]: https://about.gitlab.com/free-trial/
[pricing]: https://about.gitlab.com/pricing/ [pricing]: https://about.gitlab.com/pricing/
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's
important to describe those, too. Think of things that may go wrong and include them here.
This is important to minimize requests for support, and to avoid doc comments with
questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
\ No newline at end of file
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