Commit 96d8f009 authored by egrieff's avatar egrieff

Merge branch 'master' into 57825-moving-an-issue-results-in-broken-image-links-in-comments

parents fd4a8688 348d779b
...@@ -79,3 +79,4 @@ package-lock.json ...@@ -79,3 +79,4 @@ package-lock.json
/junit_*.xml /junit_*.xml
/coverage-frontend/ /coverage-frontend/
jsdoc/ jsdoc/
**/tmp/rubocop_cache/**
\ No newline at end of file
...@@ -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
parallel: 6
rspec system pg:
<<: *rspec-metadata-pg <<: *rspec-metadata-pg
parallel: 50 parallel: 24
rspec unit pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 20
rspec-pg-10: rspec integration pg-10:
<<: *rspec-metadata-pg-10 <<: *rspec-metadata-pg-10
<<: *only-schedules-master <<: *only-schedules-master
parallel: 50 parallel: 6
rspec-mysql: rspec system pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 24
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)
......
app/assets/images/favicon-yellow.png

1.63 KB | W: | H:

app/assets/images/favicon-yellow.png

1.45 KB | W: | H:

app/assets/images/favicon-yellow.png
app/assets/images/favicon-yellow.png
app/assets/images/favicon-yellow.png
app/assets/images/favicon-yellow.png
  • 2-up
  • Swipe
  • Onion skin
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 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,
}); });
}; };
...@@ -3,7 +3,7 @@ import _ from 'underscore'; ...@@ -3,7 +3,7 @@ import _ from 'underscore';
import timeago from 'timeago.js'; import timeago from 'timeago.js';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { pluralize } from './text_utility'; import { pluralize } from './text_utility';
import { languageCode, s__ } from '../../locale'; import { languageCode, s__, __ } from '../../locale';
window.timeago = timeago; window.timeago = timeago;
...@@ -63,7 +63,15 @@ export const pad = (val, len = 2) => `0${val}`.slice(-len); ...@@ -63,7 +63,15 @@ export const pad = (val, len = 2) => `0${val}`.slice(-len);
* @returns {String} * @returns {String}
*/ */
export const getDayName = date => export const getDayName = date =>
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()]; [
__('Sunday'),
__('Monday'),
__('Tuesday'),
__('Wednesday'),
__('Thursday'),
__('Friday'),
__('Saturday'),
][date.getDay()];
/** /**
* @example * @example
...@@ -320,13 +328,13 @@ export const getSundays = date => { ...@@ -320,13 +328,13 @@ export const getSundays = date => {
} }
const daysToSunday = [ const daysToSunday = [
'Saturday', __('Saturday'),
'Friday', __('Friday'),
'Thursday', __('Thursday'),
'Wednesday', __('Wednesday'),
'Tuesday', __('Tuesday'),
'Monday', __('Monday'),
'Sunday', __('Sunday'),
]; ];
const month = date.getMonth(); const month = date.getMonth();
...@@ -336,7 +344,7 @@ export const getSundays = date => { ...@@ -336,7 +344,7 @@ export const getSundays = date => {
while (dateOfMonth.getMonth() === month) { while (dateOfMonth.getMonth() === month) {
const dayName = getDayName(dateOfMonth); const dayName = getDayName(dateOfMonth);
if (dayName === 'Sunday') { if (dayName === __('Sunday')) {
sundays.push(new Date(dateOfMonth.getTime())); sundays.push(new Date(dateOfMonth.getTime()));
} }
......
import { BYTES_IN_KIB } from './constants'; import { BYTES_IN_KIB } from './constants';
import { sprintf, __ } from '~/locale';
/** /**
* Function that allows a number with an X amount of decimals * Function that allows a number with an X amount of decimals
...@@ -72,13 +73,13 @@ export function bytesToGiB(number) { ...@@ -72,13 +73,13 @@ export function bytesToGiB(number) {
*/ */
export function numberToHumanSize(size) { export function numberToHumanSize(size) {
if (size < BYTES_IN_KIB) { if (size < BYTES_IN_KIB) {
return `${size} bytes`; return sprintf(__('%{size} bytes'), { size });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB) { } else if (size < BYTES_IN_KIB * BYTES_IN_KIB) {
return `${bytesToKiB(size).toFixed(2)} KiB`; return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) { } else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) {
return `${bytesToMiB(size).toFixed(2)} MiB`; return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) });
} }
return `${bytesToGiB(size).toFixed(2)} GiB`; return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) });
} }
/** /**
......
...@@ -148,12 +148,9 @@ export default { ...@@ -148,12 +148,9 @@ export default {
href="#" href="#"
title="Add reaction" title="Add reaction"
> >
<icon <icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" />
css-classes="link-highlight award-control-icon-neutral" <icon css-classes="link-highlight award-control-icon-positive" name="smiley" />
name="emoji_slightly_smiling_face" <icon css-classes="link-highlight award-control-icon-super-positive" name="smiley" />
/>
<icon css-classes="link-highlight award-control-icon-positive" name="emoji_smiley" />
<icon css-classes="link-highlight award-control-icon-super-positive" name="emoji_smiley" />
</a> </a>
</div> </div>
<reply-button <reply-button
......
...@@ -189,13 +189,13 @@ export default { ...@@ -189,13 +189,13 @@ export default {
type="button" type="button"
> >
<span class="award-control-icon award-control-icon-neutral"> <span class="award-control-icon award-control-icon-neutral">
<icon name="emoji_slightly_smiling_face" /> <icon name="slight-smile" />
</span> </span>
<span class="award-control-icon award-control-icon-positive"> <span class="award-control-icon award-control-icon-positive">
<icon name="emoji_smiley" /> <icon name="smiley" />
</span> </span>
<span class="award-control-icon award-control-icon-super-positive"> <span class="award-control-icon award-control-icon-super-positive">
<icon name="emoji_smiley" /> <icon name="smiley" />
</span> </span>
<i <i
aria-hidden="true" aria-hidden="true"
......
<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';
const PAGE_SIZE = 100;
export default { export default {
components: { components: {
GlLoadingIcon, GlLoadingIcon,
...@@ -14,14 +18,8 @@ export default { ...@@ -14,14 +18,8 @@ export default {
}, },
mixins: [getRefMixin], mixins: [getRefMixin],
apollo: { apollo: {
files: { projectPath: {
query: getFiles, query: getProjectPath,
variables() {
return {
ref: this.ref,
path: this.path,
};
},
}, },
}, },
props: { props: {
...@@ -32,7 +30,14 @@ export default { ...@@ -32,7 +30,14 @@ export default {
}, },
data() { data() {
return { return {
files: [], projectPath: '',
nextPageCursor: '',
entries: {
trees: [],
submodules: [],
blobs: [],
},
isLoadingFiles: false,
}; };
}, },
computed: { computed: {
...@@ -42,8 +47,63 @@ export default { ...@@ -42,8 +47,63 @@ export default {
{ path: this.path, ref: this.ref }, { path: this.path, ref: this.ref },
); );
}, },
isLoadingFiles() { },
return this.$apollo.queries.files.loading; 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 +118,21 @@ export default { ...@@ -58,18 +118,21 @@ export default {
tableCaption tableCaption
}} }}
</caption> </caption>
<table-header /> <table-header v-once />
<tbody> <tbody>
<template v-for="val in entries">
<table-row <table-row
v-for="entry in files" v-for="entry in val"
:id="entry.id" :id="entry.id"
:key="entry.id" :key="`${entry.flatPath}-${entry.id}`"
:current-path="path"
:path="entry.flatPath" :path="entry.flatPath"
:type="entry.type" :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>
...@@ -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',
},
{
__typename: 'file',
id: 2,
name: 'gitlab-svg',
flatPath: 'gitlab-svg',
type: 'commit',
},
{
__typename: 'file',
id: 3,
name: 'index.js',
flatPath: 'index.js',
type: 'blob',
},
{ {
__typename: 'file', cacheConfig: {
id: 4, fragmentMatcher,
name: 'test.pdf', dataIdFromObject: obj => {
flatPath: 'fixtures/test.pdf', // eslint-disable-next-line no-underscore-dangle
type: 'blob', switch (obj.__typename) {
// We need to create a dynamic ID for each entry
// Each entry can have the same ID as the ID is a commit ID
// So we create a unique cache ID with the path and the ID
case 'TreeEntry':
case 'Submodule':
case 'Blob':
return `${obj.flatPath}-${obj.id}`;
default:
// If the type doesn't match any of the above we fallback
// to using the default Apollo ID
// eslint-disable-next-line no-underscore-dangle
return obj.id || obj._id;
}
}, },
];
}, },
}, },
}); );
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',
}; };
......
...@@ -194,9 +194,9 @@ export default { ...@@ -194,9 +194,9 @@ export default {
v-show="noEmoji" v-show="noEmoji"
class="js-no-emoji-placeholder no-emoji-placeholder position-relative" class="js-no-emoji-placeholder no-emoji-placeholder position-relative"
> >
<icon name="emoji_slightly_smiling_face" css-classes="award-control-icon-neutral" /> <icon name="slight-smile" css-classes="award-control-icon-neutral" />
<icon name="emoji_smiley" css-classes="award-control-icon-positive" /> <icon name="smiley" css-classes="award-control-icon-positive" />
<icon name="emoji_smile" css-classes="award-control-icon-super-positive" /> <icon name="smile" css-classes="award-control-icon-super-positive" />
</span> </span>
</button> </button>
</span> </span>
......
...@@ -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,8 +333,11 @@ export default { ...@@ -333,8 +333,11 @@ 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" />
<div class="mr-widget-info">
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links"> <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
<p>
{{ s__('mrWidget|Allows commits from members who can merge to the target branch') }} {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
</p>
</section> </section>
<mr-widget-related-links <mr-widget-related-links
...@@ -369,6 +372,7 @@ export default { ...@@ -369,6 +372,7 @@ export default {
<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>
<mr-widget-pipeline-container <mr-widget-pipeline-container
......
...@@ -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';
......
...@@ -151,8 +151,7 @@ ...@@ -151,8 +151,7 @@
outline: 0; outline: 0;
.award-control-icon svg { .award-control-icon svg {
background: $award-emoji-positive-add-bg; fill: $blue-500;
fill: $award-emoji-positive-add-lines;
} }
.award-control-icon-neutral { .award-control-icon-neutral {
......
...@@ -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 {
......
...@@ -218,7 +218,7 @@ ...@@ -218,7 +218,7 @@
min-width: 200px; min-width: 200px;
padding-right: 25px; padding-right: 25px;
padding-left: 0; padding-left: 0;
height: $input-height; height: $input-height - 2;
line-height: inherit; line-height: inherit;
border-color: transparent; border-color: transparent;
......
...@@ -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;
} }
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
.detail-page-header-body { .detail-page-header-body {
position: relative; position: relative;
line-height: 35px;
display: flex; display: flex;
flex: 1 1; flex: 1 1;
min-width: 0; min-width: 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
...@@ -204,7 +212,7 @@ module Clusters ...@@ -204,7 +212,7 @@ module Clusters
end end
def kube_ingress_domain def kube_ingress_domain
@kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain @kube_ingress_domain ||= domain.presence || instance_domain
end end
def predefined_variables def predefined_variables
...@@ -221,6 +229,33 @@ module Clusters ...@@ -221,6 +229,33 @@ 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 # To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_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: # is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
......
...@@ -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)
......
...@@ -16,27 +16,8 @@ class ProjectAutoDevops < ApplicationRecord ...@@ -16,27 +16,8 @@ class ProjectAutoDevops < ApplicationRecord
after_save :create_gitlab_deploy_token, if: :needs_to_create_deploy_token? after_save :create_gitlab_deploy_token, if: :needs_to_create_deploy_token?
def instance_domain
Gitlab::CurrentSettings.auto_devops_domain
end
def has_domain?
domain.present? || instance_domain.present?
end
# From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN.
# See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580
# for more info.
#
# Suppport AUTO_DEVOPS_DOMAIN is scheduled to be removed on
# https://gitlab.com/gitlab-org/gitlab-ce/issues/52363
def predefined_variables def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
if has_domain?
variables.append(key: 'AUTO_DEVOPS_DOMAIN',
value: domain.presence || instance_domain)
end
variables.concat(deployment_strategy_default_variables) variables.concat(deployment_strategy_default_variables)
end end
end end
......
...@@ -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
......
...@@ -25,7 +25,7 @@ module SystemNoteService ...@@ -25,7 +25,7 @@ module SystemNoteService
text_parts = ["added #{commits_text}"] text_parts = ["added #{commits_text}"]
text_parts << commits_list(noteable, new_commits, existing_commits, oldrev) text_parts << commits_list(noteable, new_commits, existing_commits, oldrev)
text_parts << "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" text_parts << "[Compare with previous version](#{diff_comparison_path(noteable, project, oldrev)})"
body = text_parts.join("\n\n") body = text_parts.join("\n\n")
...@@ -41,7 +41,7 @@ module SystemNoteService ...@@ -41,7 +41,7 @@ module SystemNoteService
# #
# Returns the created Note object # Returns the created Note object
def tag_commit(noteable, project, author, tag_name) def tag_commit(noteable, project, author, tag_name)
link = url_helpers.project_tag_url(project, id: tag_name) link = url_helpers.project_tag_path(project, id: tag_name)
body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})" body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'tag')) create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
...@@ -272,7 +272,7 @@ module SystemNoteService ...@@ -272,7 +272,7 @@ module SystemNoteService
text_parts = ["changed this line in"] text_parts = ["changed this line in"]
if version_params = merge_request.version_params_for(diff_refs) if version_params = merge_request.version_params_for(diff_refs)
line_code = change_position.line_code(project.repository) line_code = change_position.line_code(project.repository)
url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code)) url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: line_code))
text_parts << "[version #{version_index} of the diff](#{url})" text_parts << "[version #{version_index} of the diff](#{url})"
else else
...@@ -405,7 +405,7 @@ module SystemNoteService ...@@ -405,7 +405,7 @@ module SystemNoteService
# #
# "created branch `201-issue-branch-button`" # "created branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch) def new_issue_branch(issue, project, author, branch)
link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch) link = url_helpers.project_compare_path(project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link}) to address this issue" body = "created branch [`#{branch}`](#{link}) to address this issue"
...@@ -668,10 +668,10 @@ module SystemNoteService ...@@ -668,10 +668,10 @@ module SystemNoteService
@url_helpers ||= Gitlab::Routing.url_helpers @url_helpers ||= Gitlab::Routing.url_helpers
end end
def diff_comparison_url(merge_request, project, oldrev) def diff_comparison_path(merge_request, project, oldrev)
diff_id = merge_request.merge_request_diff.id diff_id = merge_request.merge_request_diff.id
url_helpers.diffs_project_merge_request_url( url_helpers.diffs_project_merge_request_path(
project, project,
merge_request, merge_request,
diff_id: diff_id, diff_id: diff_id,
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button', %button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'), 'aria-label': _('Add reaction'),
data: { title: _('Add reaction') } } data: { title: _('Add reaction') } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "award-control-icon award-control-icon-neutral" }= sprite_icon('slight-smile')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley') %span{ class: "award-control-icon award-control-icon-positive" }= sprite_icon('smiley')
%span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile') %span{ class: "award-control-icon award-control-icon-super-positive" }= sprite_icon('smile')
= icon('spinner spin', class: "award-control-icon award-control-icon-loading") = icon('spinner spin', class: "award-control-icon award-control-icon-loading")
...@@ -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
......
...@@ -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
......
...@@ -47,9 +47,9 @@ ...@@ -47,9 +47,9 @@
- if @user.status - if @user.status
= emoji_icon @user.status.emoji = emoji_icon @user.status.emoji
%span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) } %span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) }
= sprite_icon('emoji_slightly_smiling_face', css_class: 'award-control-icon-neutral') = sprite_icon('slight-smile', css_class: 'award-control-icon-neutral')
= sprite_icon('emoji_smiley', css_class: 'award-control-icon-positive') = sprite_icon('smiley', css_class: 'award-control-icon-positive')
= sprite_icon('emoji_smile', css_class: 'award-control-icon-super-positive') = sprite_icon('smile', css_class: 'award-control-icon-super-positive')
- reset_message_button = button_tag type: :button, - reset_message_button = button_tag type: :button,
id: 'js-clear-user-status-button', id: 'js-clear-user-status-button',
class: 'clear-user-status btn has-tooltip', class: 'clear-user-status btn has-tooltip',
......
- formats = [['zip', 'btn-primary'], ['tar.gz'], ['tar.bz2'], ['tar']] - formats = [['zip', 'btn-primary'], ['tar.gz'], ['tar.bz2'], ['tar']]
.d-flex.justify-content-between .btn-group.ml-0.w-100
- formats.each do |(fmt, extra_class)| - formats.each do |(fmt, extra_class)|
= link_to fmt, project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt), rel: 'nofollow', download: '', class: "btn btn-xs #{extra_class}" = link_to fmt, project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt), rel: 'nofollow', download: '', class: "btn btn-xs #{extra_class}"
...@@ -41,9 +41,9 @@ ...@@ -41,9 +41,9 @@
.note-actions-item .note-actions-item
= button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile') %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable - if note_editable
.note-actions-item .note-actions-item
......
...@@ -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'
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445c.195.625.556 1.131 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg>
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg>
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="nonzero"/></svg>
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
.note-actions-item .note-actions-item
= link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do = link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile') %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable - if note_editable
.note-actions-item .note-actions-item
......
---
title: Fix border radii on diff files and repo files
merge_request:
author:
type: fixed
---
title: Reduce height of issue board input to align with buttons
merge_request:
author:
type: other
---
title: Validate Kubernetes credentials at cluster creation
merge_request: 27403
author:
type: added
---
title: Removes support for AUTO_DEVOPS_DOMAIN
merge_request: 28460
author:
type: removed
---
title: Fix padding in MR widget
merge_request: 28472
author:
type: fixed
---
title: Update favicon from next
merge_request: 28601
author: Jarek Ostrowski @jareko
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: 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: Group download buttons into a .btn-group
merge_request:
author:
type: other
---
title: Fix the height of the page headers on issues/merge request/snippets pages
merge_request: 28650
author: Erik van der Gaag
type: fixed
---
title: 'API: Allow to get and set "masked" attribute for variables'
merge_request: 28381
author: Mathieu Parent
type: added
---
title: Fix issue importing members with owner access
merge_request: 28636
author:
type: fixed
---
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: Change links in system notes to use relative paths
merge_request: 28588
author: Luke Picciau
type: fixed
---
title: Fix OmniAuth OAuth2Generic strategy not loading
merge_request: 28680
author:
type: fixed
---
title: Update new smiley icons, find n replace old names with new ones
merge_request: 28338
author: Jarek Ostrowski
type: changed
# frozen_string_literal: true
#
# Adds logging for all Rack Attack blocks and throttling events. # Adds logging for all Rack Attack blocks and throttling events.
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req| ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
if [:throttle, :blacklist].include? req.env['rack.attack.match_type'] if [:throttle, :blacklist].include? req.env['rack.attack.match_type']
Rails.logger.info("Rack_Attack: #{req.env['rack.attack.match_type']} #{req.ip} #{req.request_method} #{req.fullpath}") Gitlab::AuthLogger.error(
message: 'Rack_Attack',
env: req.env['rack.attack.match_type'],
ip: req.ip,
request_method: req.request_method,
fullpath: req.fullpath
)
end end
end end
...@@ -9,11 +9,22 @@ const IS_EE = require('./helpers/is_ee_env'); ...@@ -9,11 +9,22 @@ const IS_EE = require('./helpers/is_ee_env');
const ROOT_PATH = path.resolve(__dirname, '..'); const ROOT_PATH = path.resolve(__dirname, '..');
const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/; const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/;
function fatalError(message) { function exitError(message) {
console.error(chalk.red(`\nError: ${message}\n`)); console.error(chalk.red(`\nError: ${message}\n`));
process.exit(1); process.exit(1);
} }
function exitWarn(message) {
console.error(chalk.yellow(`\nWarn: ${message}\n`));
process.exit(0);
}
function exit(message, isError = true) {
const fn = isError ? exitError : exitWarn;
fn(message);
}
// disable problematic options // disable problematic options
webpackConfig.entry = undefined; webpackConfig.entry = undefined;
webpackConfig.mode = 'development'; webpackConfig.mode = 'development';
...@@ -31,7 +42,8 @@ webpackConfig.plugins.push( ...@@ -31,7 +42,8 @@ webpackConfig.plugins.push(
}), }),
); );
const specFilters = argumentsParser const options = argumentsParser
.option('--no-fail-on-empty-test-suite')
.option( .option(
'-f, --filter-spec [filter]', '-f, --filter-spec [filter]',
'Filter run spec files by path. Multiple filters are like a logical OR.', 'Filter run spec files by path. Multiple filters are like a logical OR.',
...@@ -41,7 +53,9 @@ const specFilters = argumentsParser ...@@ -41,7 +53,9 @@ const specFilters = argumentsParser
}, },
[], [],
) )
.parse(process.argv).filterSpec; .parse(process.argv);
const specFilters = options.filterSpec;
const createContext = (specFiles, regex, suffix) => { const createContext = (specFiles, regex, suffix) => {
const newContext = specFiles.reduce((context, file) => { const newContext = specFiles.reduce((context, file) => {
...@@ -73,11 +87,13 @@ if (specFilters.length) { ...@@ -73,11 +87,13 @@ if (specFilters.length) {
filteredSpecFiles = [...new Set(filteredSpecFiles)]; filteredSpecFiles = [...new Set(filteredSpecFiles)];
if (filteredSpecFiles.length < 1) { if (filteredSpecFiles.length < 1) {
fatalError('Your filter did not match any test files.'); const isError = options.failOnEmptyTestSuite;
exit('Your filter did not match any test files.', isError);
} }
if (!filteredSpecFiles.every(file => SPECS_PATH.test(file))) { if (!filteredSpecFiles.every(file => SPECS_PATH.test(file))) {
fatalError('Test files must be located within /spec/javascripts.'); exitError('Test files must be located within /spec/javascripts.');
} }
const CE_FILES = filteredSpecFiles.filter(file => !file.startsWith('ee')); const CE_FILES = filteredSpecFiles.filter(file => !file.startsWith('ee'));
......
...@@ -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:
......
...@@ -280,6 +280,14 @@ installations from source. ...@@ -280,6 +280,14 @@ installations from source.
Currently it logs the progress of project imports from the Bitbucket Server Currently it logs the progress of project imports from the Bitbucket Server
importer. Future importers may use this file. importer. Future importers may use this file.
## `auth.log`
Introduced in GitLab 12.0. This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/auth.log` for
installations from source.
It logs information whenever [Rack Attack] registers an abusive request.
## Reconfigure Logs ## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
...@@ -298,3 +306,4 @@ Omnibus GitLab packages or in `/home/git/gitlab/log/sidekiq_exporter.log` for ...@@ -298,3 +306,4 @@ Omnibus GitLab packages or in `/home/git/gitlab/log/sidekiq_exporter.log` for
installations from source. installations from source.
[repocheck]: repository_checks.md [repocheck]: repository_checks.md
[Rack Attack]: ../security/rack_attack.md
...@@ -125,7 +125,7 @@ POST /groups/:id/epics/:epic_iid/epics ...@@ -125,7 +125,7 @@ POST /groups/:id/epics/:epic_iid/epics
| --------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | --------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `epic_iid` | integer | yes | The internal ID of the (future parent) epic. | | `epic_iid` | integer | yes | The internal ID of the (future parent) epic. |
| `title` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. | | `title` | string | yes | The title of a newly created epic. |
```bash ```bash
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics?title=Newpic curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics?title=Newpic
...@@ -141,6 +141,7 @@ Example response: ...@@ -141,6 +141,7 @@ Example response:
"group_id": 49, "group_id": 49,
"parent_id": 23, "parent_id": 23,
"has_children": false, "has_children": false,
"has_issues": false,
"reference": "&2", "reference": "&2",
"url": "http://localhost/groups/group16/-/epics/2", "url": "http://localhost/groups/group16/-/epics/2",
"relation_url": "http://localhost/groups/group16/-/epics/1/links/24" "relation_url": "http://localhost/groups/group16/-/epics/1/links/24"
......
...@@ -52,7 +52,9 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a ...@@ -52,7 +52,9 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
{ {
"key": "TEST_VARIABLE_1", "key": "TEST_VARIABLE_1",
"variable_type": "env_var", "variable_type": "env_var",
"value": "TEST_1" "value": "TEST_1",
"protected": false,
"masked": true
} }
``` ```
...@@ -71,6 +73,7 @@ POST /projects/:id/variables ...@@ -71,6 +73,7 @@ POST /projects/:id/variables
| `value` | string | yes | The `value` of a variable | | `value` | string | yes | The `value` of a variable |
| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` | | `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
| `protected` | boolean | no | Whether the variable is protected | | `protected` | boolean | no | Whether the variable is protected |
| `masked` | boolean | no | Whether the variable is masked |
``` ```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
...@@ -81,7 +84,8 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla ...@@ -81,7 +84,8 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
"key": "NEW_VARIABLE", "key": "NEW_VARIABLE",
"value": "new value", "value": "new value",
"variable_type": "env_var", "variable_type": "env_var",
"protected": false "protected": false,
"masked": false
} }
``` ```
...@@ -100,6 +104,7 @@ PUT /projects/:id/variables/:key ...@@ -100,6 +104,7 @@ PUT /projects/:id/variables/:key
| `value` | string | yes | The `value` of a variable | | `value` | string | yes | The `value` of a variable |
| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` | | `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
| `protected` | boolean | no | Whether the variable is protected | | `protected` | boolean | no | Whether the variable is protected |
| `masked` | boolean | no | Whether the variable is masked |
``` ```
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value" curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
...@@ -110,7 +115,8 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab ...@@ -110,7 +115,8 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
"key": "NEW_VARIABLE", "key": "NEW_VARIABLE",
"value": "updated value", "value": "updated value",
"variable_type": "env_var", "variable_type": "env_var",
"protected": true "protected": true,
"masked": false
} }
``` ```
......
...@@ -1986,7 +1986,7 @@ production: ...@@ -1986,7 +1986,7 @@ production:
- deploy - deploy
environment: environment:
name: production name: production
url: https://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
only: only:
- master - master
``` ```
......
...@@ -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.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment