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
/junit_*.xml
/coverage-frontend/
jsdoc/
**/tmp/rubocop_cache/**
\ No newline at end of file
......@@ -13,10 +13,8 @@ variables:
BUILD_ASSETS_IMAGE: "false"
before_script:
- bundle --version
- date
- source scripts/utils.sh
- date
- source scripts/prepare_build.sh
- date
......
......@@ -6,8 +6,9 @@
.use-pg-10: &use-pg-10
services:
- postgres:10.7
- redis:alpine
- name: postgres:10.7
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
.use-mysql: &use-mysql
services:
......@@ -52,8 +53,10 @@
script:
- JOB_NAME=( $CI_JOB_NAME )
- TEST_TOOL=${JOB_NAME[0]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- TEST_LEVEL=${JOB_NAME[1]}
- 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 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
......@@ -63,7 +66,10 @@
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- 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:
expire_in: 31d
when: always
......@@ -140,19 +146,68 @@ setup-test-env:
except:
- /(^docs[\/-].*|.*-docs$)/
rspec-pg:
rspec unit pg:
<<: *rspec-metadata-pg
parallel: 20
rspec integration pg:
<<: *rspec-metadata-pg
parallel: 50
parallel: 6
rspec system pg:
<<: *rspec-metadata-pg
parallel: 24
rspec unit pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 20
rspec integration pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 6
rspec-pg-10:
rspec system pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 50
parallel: 24
rspec-mysql:
rspec unit mysql:
<<: *rspec-metadata-mysql
<<: *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-metadata-pg
......@@ -164,16 +219,17 @@ rspec-fast-spec-helper:
script:
- export CACHE_CLASSES=true
- 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-quarantine
allow_failure: true
rspec-mysql-quarantine:
rspec quarantine mysql:
<<: *rspec-metadata-mysql
<<: *rspec-quarantine
<<: *only-schedules-master
allow_failure: true
static-analysis:
......
......@@ -40,12 +40,12 @@ update-tests-metadata:
policy: push
script:
- 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
- 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'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
- scripts/insert-rspec-profiling-data
only:
......
......@@ -361,7 +361,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.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 'benchmark-ips', '~> 2.3.0', require: false
......@@ -372,6 +372,7 @@ group :development, :test do
gem 'activerecord_sane_schema_dumper', '1.0'
gem 'stackprof', '~> 0.2.10', require: false
gem 'derailed_benchmarks', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false
......@@ -399,6 +400,8 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.17.0'
gem 'rbtrace', '~> 0.4', require: false
gem 'memory_profiler', '~> 0.9', require: false
gem 'benchmark-memory', '~> 0.1', require: false
# OAuth
gem 'oauth2', '~> 1.4'
......
......@@ -82,6 +82,8 @@ GEM
bcrypt (3.1.12)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
benchmark-memory (0.1.2)
memory_profiler (~> 0.9)
better_errors (2.5.0)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
......@@ -155,6 +157,14 @@ GEM
html-pipeline
declarative (0.0.10)
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)
thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0)
......@@ -174,7 +184,7 @@ GEM
diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
docile (1.1.5)
docile (1.3.1)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2)
......@@ -377,6 +387,7 @@ GEM
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
heapy (0.1.4)
hipchat (1.5.2)
httparty
mimemagic
......@@ -478,6 +489,7 @@ GEM
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
memory_profiler (0.9.13)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
......@@ -873,11 +885,11 @@ GEM
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simple_po_parser (1.1.2)
simplecov (0.14.1)
docile (~> 1.1.0)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
simplecov-html (0.10.2)
slack-notifier (1.5.1)
spring (2.0.2)
activesupport (>= 4.2)
......@@ -1014,6 +1026,7 @@ DEPENDENCIES
batch-loader (~> 1.4.0)
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1)
better_errors (~> 2.5.0)
binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4)
......@@ -1034,6 +1047,7 @@ DEPENDENCIES
creole (~> 0.5.0)
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0)
derailed_benchmarks
device_detector
devise (~> 4.6)
devise-two-factor (~> 3.0.0)
......@@ -1111,6 +1125,7 @@ DEPENDENCIES
lograge (~> 0.5)
loofah (~> 2.2)
mail_room (~> 0.9.1)
memory_profiler (~> 0.9)
method_source (~> 0.8)
mimemagic (~> 0.3.2)
mini_magick
......@@ -1203,7 +1218,7 @@ DEPENDENCIES
sidekiq (~> 5.2.7)
sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0)
simplecov (~> 0.16.1)
slack-notifier (~> 1.5.1)
spring (~> 2.0.0)
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 {
computed: {
resolveButtonTitle() {
let title = 'Mark comment as resolved';
let title = __('Mark comment as resolved');
if (this.resolvedBy) {
title = `Resolved by ${this.resolvedBy.name}`;
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
}
return title;
......
import Visibility from 'visibilityjs';
import Vue from 'vue';
import AccessorUtilities from '~/lib/utils/accessor';
import { GlToast } from '@gitlab/ui';
import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
......@@ -43,8 +44,10 @@ export default class Clusters {
helpPath,
ingressHelpPath,
ingressDnsHelpPath,
clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset;
this.clusterId = clusterId;
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
......@@ -69,6 +72,10 @@ export default class Clusters {
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
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.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
......@@ -125,6 +132,13 @@ export default class Clusters {
PersistentUserCallout.factory(callout);
}
addBannerCloseHandler(el, status) {
el.querySelector('.js-close-banner').addEventListener('click', () => {
el.classList.add('hidden');
this.setBannerDismissedState(status, true);
});
}
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
......@@ -133,6 +147,9 @@ export default class Clusters {
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(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() {
......@@ -205,6 +222,8 @@ export default class Clusters {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden');
this.unreachableContainer.classList.add('hidden');
this.authenticationFailureContainer.classList.add('hidden');
}
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
......@@ -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) {
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
if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
switch (status) {
......@@ -241,6 +283,12 @@ export default class Clusters {
this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error;
break;
case 'unreachable':
this.unreachableContainer.classList.remove('hidden');
break;
case 'authentication_failure':
this.authenticationFailureContainer.classList.remove('hidden');
break;
case 'scheduled':
case 'creating':
this.creatingContainer.classList.remove('hidden');
......
......@@ -6,6 +6,7 @@ import 'core-js/fn/array/from';
import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign';
import 'core-js/fn/object/values';
import 'core-js/fn/object/entries';
import 'core-js/fn/promise';
import 'core-js/fn/promise/finally';
import 'core-js/fn/string/code-point-at';
......
import Vue from 'vue';
import ErrorTrackingSettings from './components/app.vue';
import createStore from './store';
import initSettingsPanels from '~/settings_panels';
export default () => {
initSettingsPanels();
const formContainerEl = document.querySelector('.js-error-tracking-form');
const {
dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint },
......
......@@ -3,12 +3,12 @@ import { InMemoryCache } from 'apollo-cache-inmemory';
import { createUploadLink } from 'apollo-upload-client';
import csrf from '~/lib/utils/csrf';
export default (resolvers = {}, baseUrl = '') => {
export default (resolvers = {}, config = {}) => {
let uri = `${gon.relative_url_root}/api/graphql`;
if (baseUrl) {
if (config.baseUrl) {
// Prepend baseUrl and ensure that `///` are replaced with `/`
uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/');
uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/');
}
return new ApolloClient({
......@@ -18,7 +18,7 @@ export default (resolvers = {}, baseUrl = '') => {
[csrf.headerKey]: csrf.token,
},
}),
cache: new InMemoryCache(),
cache: new InMemoryCache(config.cacheConfig),
resolvers,
});
};
......@@ -3,7 +3,7 @@ import _ from 'underscore';
import timeago from 'timeago.js';
import dateFormat from 'dateformat';
import { pluralize } from './text_utility';
import { languageCode, s__ } from '../../locale';
import { languageCode, s__, __ } from '../../locale';
window.timeago = timeago;
......@@ -63,7 +63,15 @@ export const pad = (val, len = 2) => `0${val}`.slice(-len);
* @returns {String}
*/
export const getDayName = date =>
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
[
__('Sunday'),
__('Monday'),
__('Tuesday'),
__('Wednesday'),
__('Thursday'),
__('Friday'),
__('Saturday'),
][date.getDay()];
/**
* @example
......@@ -320,13 +328,13 @@ export const getSundays = date => {
}
const daysToSunday = [
'Saturday',
'Friday',
'Thursday',
'Wednesday',
'Tuesday',
'Monday',
'Sunday',
__('Saturday'),
__('Friday'),
__('Thursday'),
__('Wednesday'),
__('Tuesday'),
__('Monday'),
__('Sunday'),
];
const month = date.getMonth();
......@@ -336,7 +344,7 @@ export const getSundays = date => {
while (dateOfMonth.getMonth() === month) {
const dayName = getDayName(dateOfMonth);
if (dayName === 'Sunday') {
if (dayName === __('Sunday')) {
sundays.push(new Date(dateOfMonth.getTime()));
}
......
import { BYTES_IN_KIB } from './constants';
import { sprintf, __ } from '~/locale';
/**
* Function that allows a number with an X amount of decimals
......@@ -72,13 +73,13 @@ export function bytesToGiB(number) {
*/
export function numberToHumanSize(size) {
if (size < BYTES_IN_KIB) {
return `${size} bytes`;
return sprintf(__('%{size} bytes'), { size });
} 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) {
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 {
href="#"
title="Add reaction"
>
<icon
css-classes="link-highlight award-control-icon-neutral"
name="emoji_slightly_smiling_face"
/>
<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" />
<icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" />
<icon css-classes="link-highlight award-control-icon-positive" name="smiley" />
<icon css-classes="link-highlight award-control-icon-super-positive" name="smiley" />
</a>
</div>
<reply-button
......
......@@ -189,13 +189,13 @@ export default {
type="button"
>
<span class="award-control-icon award-control-icon-neutral">
<icon name="emoji_slightly_smiling_face" />
<icon name="slight-smile" />
</span>
<span class="award-control-icon award-control-icon-positive">
<icon name="emoji_smiley" />
<icon name="smiley" />
</span>
<span class="award-control-icon award-control-icon-super-positive">
<icon name="emoji_smiley" />
<icon name="smiley" />
</span>
<i
aria-hidden="true"
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
import getFiles from '../../queries/getFiles.graphql';
import getProjectPath from '../../queries/getProjectPath.graphql';
import TableHeader from './header.vue';
import TableRow from './row.vue';
const PAGE_SIZE = 100;
export default {
components: {
GlLoadingIcon,
......@@ -14,14 +18,8 @@ export default {
},
mixins: [getRefMixin],
apollo: {
files: {
query: getFiles,
variables() {
return {
ref: this.ref,
path: this.path,
};
},
projectPath: {
query: getProjectPath,
},
},
props: {
......@@ -32,7 +30,14 @@ export default {
},
data() {
return {
files: [],
projectPath: '',
nextPageCursor: '',
entries: {
trees: [],
submodules: [],
blobs: [],
},
isLoadingFiles: false,
};
},
computed: {
......@@ -42,8 +47,63 @@ export default {
{ 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 {
tableCaption
}}
</caption>
<table-header />
<table-header v-once />
<tbody>
<table-row
v-for="entry in files"
:id="entry.id"
:key="entry.id"
:path="entry.flatPath"
:type="entry.type"
/>
<template v-for="val in entries">
<table-row
v-for="entry in val"
:id="entry.id"
:key="`${entry.flatPath}-${entry.id}`"
:current-path="path"
:path="entry.flatPath"
:type="entry.type"
/>
</template>
</tbody>
</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>
</template>
......@@ -6,7 +6,11 @@ export default {
mixins: [getRefMixin],
props: {
id: {
type: Number,
type: String,
required: true,
},
currentPath: {
type: String,
required: true,
},
path: {
......@@ -26,7 +30,7 @@ export default {
return `fa-${getIconName(this.type, this.path)}`;
},
isFolder() {
return this.type === 'folder';
return this.type === 'tree';
},
isSubmodule() {
return this.type === 'commit';
......@@ -34,6 +38,12 @@ export default {
linkComponent() {
return this.isFolder ? 'router-link' : 'a';
},
fullPath() {
return this.path.replace(new RegExp(`^${this.currentPath}/`), '');
},
shortSha() {
return this.id.slice(0, 8);
},
},
methods: {
openRow() {
......@@ -49,9 +59,11 @@ export default {
<tr v-once :class="`file_${id}`" class="tree-item" @click="openRow">
<td class="tree-item-file-name">
<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">
@ <a href="#" class="commit-sha">{{ id }}</a>
@ <a href="#" class="commit-sha">{{ shortSha }}</a>
</template>
</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 VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
Vue.use(VueApollo);
const defaultClient = createDefaultClient({
Query: {
files() {
return [
{
__typename: 'file',
id: 1,
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',
id: 4,
name: 'test.pdf',
flatPath: 'fixtures/test.pdf',
type: 'blob',
},
];
// We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
const defaultClient = createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
dataIdFromObject: obj => {
// eslint-disable-next-line no-underscore-dangle
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({
defaultClient,
......
query getFiles($path: String!, $ref: String!) {
files(path: $path, ref: $ref) @client {
id
flatPath
type
fragment TreeEntry on Entry {
id
flatPath
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) {
mode: 'history',
base: joinPaths(gon.relative_url_root || '', base),
routes: [
{
path: '/',
name: 'projectRoot',
component: IndexPage,
},
{
path: `/tree/${baseRef}(/.*)?`,
name: 'treePath',
component: TreePage,
props: route => ({
path: route.params.pathMatch,
path: route.params.pathMatch.replace(/^\//, ''),
}),
beforeEnter(to, from, next) {
document
......@@ -31,6 +26,11 @@ export default function createRouter(base, baseRef) {
next();
},
},
{
path: '/',
name: 'projectRoot',
component: IndexPage,
},
],
});
}
const entryTypeIcons = {
folder: 'folder',
tree: 'folder',
commit: 'archive',
};
......
......@@ -194,9 +194,9 @@ export default {
v-show="noEmoji"
class="js-no-emoji-placeholder no-emoji-placeholder position-relative"
>
<icon name="emoji_slightly_smiling_face" css-classes="award-control-icon-neutral" />
<icon name="emoji_smiley" css-classes="award-control-icon-positive" />
<icon name="emoji_smile" css-classes="award-control-icon-super-positive" />
<icon name="slight-smile" css-classes="award-control-icon-neutral" />
<icon name="smiley" css-classes="award-control-icon-positive" />
<icon name="smile" css-classes="award-control-icon-super-positive" />
</span>
</button>
</span>
......
......@@ -2,6 +2,7 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Flash, { hideFlash } from './flash';
import { parseBoolean } from './lib/utils/common_utils';
import { __ } from './locale';
export default () => {
$('body').on('click', '.js-usage-consent-action', e => {
......@@ -25,7 +26,7 @@ export default () => {
})
.catch(() => {
hideConsentMessage();
Flash('Something went wrong. Try again later.');
Flash(__('Something went wrong. Try again later.'));
});
});
};
......@@ -5,7 +5,7 @@
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { __ } from './locale';
import { s__, __, sprintf } from './locale';
import ModalStore from './boards/stores/modal_store';
// TODO: remove eventHub hack after code splitting refactor
......@@ -157,14 +157,20 @@ function UsersSelect(currentUser, els, options = {}) {
.get(0);
if (selectedUsers.length === 0) {
return 'Unassigned';
return s__('UsersSelect|Unassigned');
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
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 {
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 = {}) {
tooltipTitle = _.escape(user.name);
} else {
user = {
name: 'Unassigned',
name: s__('UsersSelect|Unassigned'),
username: '',
avatar: '',
};
tooltipTitle = __('Assignee');
tooltipTitle = s__('UsersSelect|Assignee');
}
$value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle');
......@@ -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> <% } %>',
);
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({
showMenuAbove: showMenuAbove,
......@@ -302,7 +312,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1;
users.unshift({
beforeDivider: true,
name: 'Unassigned',
name: s__('UsersSelect|Unassigned'),
id: 0,
});
}
......@@ -310,7 +320,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1;
name = showAnyUser;
if (name === true) {
name = 'Any User';
name = s__('UsersSelect|Any User');
}
anyUser = {
beforeDivider: true,
......@@ -596,7 +606,7 @@ function UsersSelect(currentUser, els, options = {}) {
showEmailUser = $(select).data('emailUser');
firstUser = $(select).data('firstUser');
return $(select).select2({
placeholder: 'Search for a user',
placeholder: __('Search for a user'),
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
......@@ -621,7 +631,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
if (showNullUser) {
nullUser = {
name: 'Unassigned',
name: s__('UsersSelect|Unassigned'),
id: 0,
};
data.results.unshift(nullUser);
......@@ -629,7 +639,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showAnyUser) {
name = showAnyUser;
if (name === true) {
name = 'Any User';
name = s__('UsersSelect|Any User');
}
anyUser = {
name: name,
......@@ -645,7 +655,7 @@ function UsersSelect(currentUser, els, options = {}) {
) {
var trimmed = query.term.trim();
emailUser = {
name: 'Invite "' + trimmed + '" by email',
name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
username: trimmed,
id: trimmed,
invite: true,
......@@ -688,7 +698,7 @@ UsersSelect.prototype.initSelection = function(element, callback) {
id = $(element).val();
if (id === '0') {
nullUser = {
name: 'Unassigned',
name: s__('UsersSelect|Unassigned'),
};
return callback(nullUser);
} else if (id !== '') {
......
......@@ -49,7 +49,7 @@ export default {
required: false,
default: () => ({
sourceProjectId: '',
issueId: '',
mergeRequestId: '',
appUrl: '',
}),
},
......
......@@ -48,7 +48,7 @@ export default {
visualReviewAppMeta() {
return {
appUrl: this.mr.appUrl,
issueId: this.mr.iid,
mergeRequestId: this.mr.iid,
sourceProjectId: this.mr.sourceProjectId,
};
},
......
......@@ -14,7 +14,7 @@ export default {
</script>
<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>
<i v-tooltip :title="tooltipTitle" :aria-label="tooltipTitle" class="fa fa-question-circle">
</i>
......
......@@ -333,41 +333,45 @@ export default {
<div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
{{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
</section>
<div class="mr-widget-info">
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
<p>
{{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
</p>
</section>
<mr-widget-related-links
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks"
/>
<mr-widget-related-links
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks"
/>
<mr-widget-alert-message
v-if="showMergePipelineForkWarning"
type="warning"
:help-path="mr.mergeRequestPipelinesHelpPath"
>
{{
s__(
'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result',
)
}}
</mr-widget-alert-message>
<mr-widget-alert-message
v-if="showMergePipelineForkWarning"
type="warning"
:help-path="mr.mergeRequestPipelinesHelpPath"
>
{{
s__(
'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result',
)
}}
</mr-widget-alert-message>
<mr-widget-alert-message
v-if="showTargetBranchAdvancedError"
type="danger"
:help-path="mr.mergeRequestPipelinesHelpPath"
>
{{
s__(
'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging',
)
}}
</mr-widget-alert-message>
<mr-widget-alert-message
v-if="showTargetBranchAdvancedError"
type="danger"
:help-path="mr.mergeRequestPipelinesHelpPath"
>
{{
s__(
'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging',
)
}}
</mr-widget-alert-message>
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
</div>
</div>
<div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div>
</div>
......
......@@ -71,11 +71,15 @@ export default {
</div>
<div class="text-secondary">
<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>
</div>
<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>
</div>
<gl-skeleton-loading
......@@ -88,7 +92,7 @@ export default {
<icon
v-show="!locationIsLoading && user.location"
name="location"
css-classes="category-icon"
css-classes="category-icon flex-shrink-0"
/>
<span class="ml-1">{{ user.location }}</span>
<gl-skeleton-loading
......
@mixin avatar-size($size, $margin-right) {
width: $size;
height: $size;
margin-right: $margin-right;
}
.avatar-circle {
float: left;
margin-right: 15px;
border-radius: $avatar-radius;
border: 1px solid $gray-normal;
&.s16 { @include avatar-size(16px, 6px); }
&.s18 { @include avatar-size(18px, 6px); }
&.s19 { @include avatar-size(19px, 6px); }
&.s20 { @include avatar-size(20px, 7px); }
&.s16 { @include avatar-size(16px, 8px); }
&.s18 { @include avatar-size(18px, 8px); }
&.s19 { @include avatar-size(19px, 8px); }
&.s20 { @include avatar-size(20px, 8px); }
&.s24 { @include avatar-size(24px, 8px); }
&.s26 { @include avatar-size(26px, 8px); }
&.s32 { @include avatar-size(32px, 10px); }
&.s36 { @include avatar-size(36px, 10px); }
&.s40 { @include avatar-size(40px, 10px); }
&.s46 { @include avatar-size(46px, 15px); }
&.s48 { @include avatar-size(48px, 10px); }
&.s60 { @include avatar-size(60px, 12px); }
&.s64 { @include avatar-size(64px, 14px); }
&.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); }
&.s100 { @include avatar-size(100px, 15px); }
&.s110 { @include avatar-size(110px, 15px); }
&.s140 { @include avatar-size(140px, 15px); }
&.s160 { @include avatar-size(160px, 20px); }
&.s32 { @include avatar-size(32px, 8px); }
&.s36 { @include avatar-size(36px, 16px); }
&.s40 { @include avatar-size(40px, 16px); }
&.s46 { @include avatar-size(46px, 16px); }
&.s48 { @include avatar-size(48px, 16px); }
&.s60 { @include avatar-size(60px, 16px); }
&.s64 { @include avatar-size(64px, 16px); }
&.s70 { @include avatar-size(70px, 16px); }
&.s90 { @include avatar-size(90px, 16px); }
&.s96 { @include avatar-size(96px, 16px); }
&.s100 { @include avatar-size(100px, 16px); }
&.s110 { @include avatar-size(110px, 16px); }
&.s140 { @include avatar-size(140px, 16px); }
&.s160 { @include avatar-size(160px, 16px); }
}
.avatar {
......@@ -39,6 +34,7 @@
padding: 0;
background: $gray-lightest;
overflow: hidden;
border-color: rgba($black, $gl-avatar-border-opacity);
&.avatar-inline {
float: none;
......@@ -64,41 +60,37 @@
&.avatar-placeholder {
border: 0;
}
&:not([href]):hover {
border-color: darken($gray-normal, 10%);
}
}
.identicon {
text-align: center;
vertical-align: top;
color: $gl-gray-700;
color: $gray-800;
background-color: $gray-darker;
// Sizes
&.s16 { font-size: 12px;
line-height: 1.33; }
&.s16 { font-size: 10px;
line-height: 16px; }
&.s24 { font-size: 13px;
line-height: 1.8; }
&.s24 { font-size: 12px;
line-height: 24px; }
&.s26 { font-size: 20px;
line-height: 1.33; }
&.s32 { font-size: 20px;
line-height: 30px; }
&.s32 { font-size: 14px;
line-height: 32px; }
&.s40 { font-size: 16px;
line-height: 38px; }
&.s48 { font-size: 20px;
line-height: 46px; }
line-height: 48px; }
&.s60 { font-size: 32px;
line-height: 58px; }
&.s64 { font-size: 32px;
&.s64 { font-size: 28px;
line-height: 64px; }
&.s70 { font-size: 34px;
......@@ -107,6 +99,9 @@
&.s90 { font-size: 36px;
line-height: 88px; }
&.s96 { font-size: 48px;
line-height: 96px; }
&.s100 { font-size: 36px;
line-height: 98px; }
......@@ -144,7 +139,6 @@
.avatar {
border-radius: 0;
border: 0;
height: auto;
width: 100%;
margin: 0;
......
......@@ -8,7 +8,6 @@
@import 'framework/animations';
@import 'framework/vue_transitions';
@import 'framework/avatar';
@import 'framework/asciidoctor';
@import 'framework/banner';
@import 'framework/blocks';
......
......@@ -151,8 +151,7 @@
outline: 0;
.award-control-icon svg {
background: $award-emoji-positive-add-bg;
fill: $award-emoji-positive-add-lines;
fill: $blue-500;
}
.award-control-icon-neutral {
......
......@@ -241,6 +241,7 @@
*/
&.code {
padding: 0;
border-radius: 0 0 $border-radius-default $border-radius-default;
}
.list-inline.previews {
......
......@@ -218,7 +218,7 @@
min-width: 200px;
padding-right: 25px;
padding-left: 0;
height: $input-height;
height: $input-height - 2;
line-height: inherit;
border-color: transparent;
......
......@@ -280,3 +280,7 @@ label {
max-width: $input-lg-width;
width: 100%;
}
.input-group-text {
max-height: $input-height;
}
......@@ -8,7 +8,7 @@
pre {
padding: 10px 0;
border: 0;
border-radius: 0;
border-radius: 0 0 $border-radius-default $border-radius-default;
font-family: $monospace-font;
font-size: $code-font-size;
line-height: 19px;
......@@ -42,6 +42,7 @@
padding: 10px;
text-align: right;
float: left;
border-bottom-left-radius: $border-radius-default;
a {
font-family: $monospace-font;
......
......@@ -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
*/
$avatar-radius: 50%;
$gl-avatar-size: 40px;
$gl-avatar-border-opacity: 0.1;
/*
* Blame
......
......@@ -7,6 +7,7 @@ $secondary: $gray-light;
$input-disabled-bg: $gray-light;
$input-border-color: $gray-200;
$input-color: $gl-text-color;
$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
$btn-line-height: 20px;
......
......@@ -69,6 +69,8 @@
align-self: flex-start;
font-weight: 500;
font-size: 20px;
color: $orange-900;
opacity: 1;
margin: $gl-padding-8 14px 0 0;
}
......
......@@ -21,7 +21,6 @@
.detail-page-header-body {
position: relative;
line-height: 35px;
display: flex;
flex: 1 1;
min-width: 0;
......
......@@ -494,6 +494,12 @@ table.code {
}
}
.line_holder:last-of-type {
td:first-child {
border-bottom-left-radius: $border-radius-default;
}
}
&.left-side-selected {
td.line_content.parallel.right-side {
user-select: none;
......
......@@ -21,13 +21,6 @@
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 {
font-size: 22px;
}
......
......@@ -87,6 +87,11 @@
padding: $gl-padding;
}
.mr-widget-info {
padding-left: $gl-padding-50 - $gl-padding-32;
padding-right: $gl-padding;
}
.mr-state-widget {
color: $gl-text-color;
......@@ -560,6 +565,10 @@
.mr-links {
padding-left: $status-icon-size + $gl-btn-padding;
&:last-child {
padding-bottom: $gl-padding;
}
}
.mr-info-list {
......@@ -1030,11 +1039,6 @@
background: $black-transparent;
}
.source-branch-removal-status {
padding-left: 50px;
padding-bottom: $gl-padding;
}
.mr-compare {
.diff-file .file-title-flex-parent {
top: $header-height + 51px;
......
......@@ -5,8 +5,10 @@ module Clusters
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
include ReactiveCaching
self.table_name = 'clusters'
self.reactive_cache_key = -> (cluster) { [cluster.class.model_name.singular, cluster.id] }
PROJECT_ONLY_APPLICATIONS = {
Applications::Jupyter.application_name => Applications::Jupyter,
......@@ -57,6 +59,8 @@ module Clusters
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
after_save :clear_reactive_cache!
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
......@@ -123,15 +127,19 @@ module Clusters
end
def status_name
if provider
provider.status_name
else
:created
provider&.status_name || connection_status.presence || :created
end
def connection_status
with_reactive_cache do |data|
data[:connection_status]
end
end
def created?
status_name == :created
def calculate_reactive_cache
return unless enabled?
{ connection_status: retrieve_connection_status }
end
def applications
......@@ -204,7 +212,7 @@ module Clusters
end
def kube_ingress_domain
@kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain
@kube_ingress_domain ||= domain.presence || instance_domain
end
def predefined_variables
......@@ -221,6 +229,33 @@ module Clusters
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
def retrieve_connection_status
kubeclient.core_client.discover
rescue *Gitlab::Kubernetes::Errors::CONNECTION
:unreachable
rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
:authentication_failure
rescue Kubeclient::HttpError => e
kubeclient_error_status(e.message)
rescue => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
:unknown_failure
else
:connected
end
# KubeClient uses the same error class
# For connection errors (eg. timeout) and
# for Kubernetes errors.
def kubeclient_error_status(message)
if message&.match?(/timed out|timeout/i)
:unreachable
else
:authentication_failure
end
end
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
......
......@@ -13,6 +13,7 @@ class Identity < ApplicationRecord
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
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_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
......
......@@ -16,27 +16,8 @@ class ProjectAutoDevops < ApplicationRecord
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
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)
end
end
......
......@@ -22,10 +22,6 @@ module Clusters
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end
def can_toggle_cluster?
can?(current_user, :update_cluster, cluster) && created?
end
def can_read_cluster?
can?(current_user, :read_cluster, cluster)
end
......
......@@ -25,7 +25,7 @@ module SystemNoteService
text_parts = ["added #{commits_text}"]
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")
......@@ -41,7 +41,7 @@ module SystemNoteService
#
# Returns the created Note object
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})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
......@@ -272,7 +272,7 @@ module SystemNoteService
text_parts = ["changed this line in"]
if version_params = merge_request.version_params_for(diff_refs)
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})"
else
......@@ -405,7 +405,7 @@ module SystemNoteService
#
# "created branch `201-issue-branch-button`"
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"
......@@ -668,10 +668,10 @@ module SystemNoteService
@url_helpers ||= Gitlab::Routing.url_helpers
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
url_helpers.diffs_project_merge_request_url(
url_helpers.diffs_project_merge_request_path(
project,
merge_request,
diff_id: diff_id,
......
......@@ -13,7 +13,7 @@
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('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-positive" }= custom_icon('emoji_smiley')
%span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile')
%span{ class: "award-control-icon award-control-icon-neutral" }= sprite_icon('slight-smile')
%span{ class: "award-control-icon award-control-icon-positive" }= sprite_icon('smiley')
%span{ class: "award-control-icon award-control-icon-super-positive" }= sprite_icon('smile')
= icon('spinner spin', class: "award-control-icon award-control-icon-loading")
......@@ -59,7 +59,7 @@
.append-right-default
= s_("CiVariable|Masked")
%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") }
%input{ type: "hidden",
class: 'js-ci-variable-input-masked js-project-feature-toggle-input',
......
......@@ -5,5 +5,17 @@
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
= 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' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
......@@ -24,7 +24,8 @@
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_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
.flash-container
......
......@@ -10,15 +10,17 @@
.container.navless-container
.content
= render "layouts/flash"
.row.append-bottom-15
.col-sm-7.brand-holder
%h1
.row.mt-3
.col-sm-12
%h1.mb-3.font-weight-normal
= brand_title
.row.mb-3
.col-sm-7.order-12.order-sm-1.brand-holder
= brand_image
- if current_appearance&.description?
= brand_text
- else
%h3
%h3.mt-sm-0
= _('Open source software to collaborate on code')
%p
......@@ -29,7 +31,7 @@
= 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
%hr.footer-fixed
......
......@@ -47,9 +47,9 @@
- if @user.status
= emoji_icon @user.status.emoji
%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('emoji_smiley', css_class: 'award-control-icon-positive')
= sprite_icon('emoji_smile', css_class: 'award-control-icon-super-positive')
= sprite_icon('slight-smile', css_class: 'award-control-icon-neutral')
= sprite_icon('smiley', css_class: 'award-control-icon-positive')
= sprite_icon('smile', css_class: 'award-control-icon-super-positive')
- reset_message_button = button_tag type: :button,
id: 'js-clear-user-status-button',
class: 'clear-user-status btn has-tooltip',
......
- 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)|
= 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 @@
.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
= icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
%span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
%span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable
.note-actions-item
......
......@@ -2,10 +2,12 @@
- setting = error_tracking_setting
%section.settings.expanded.no-animate
%section.settings.no-animate.js-error-tracking-settings
.settings-header
%h4
= _('Error Tracking')
%button.btn.js-settings-toggle{ type: 'button' }
= _('Expand')
%p
= _('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'
......
......@@ -3,6 +3,6 @@
- breadcrumb_title _('Operations Settings')
= 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_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 @@
.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
= icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
%span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
%span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable
.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.
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
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
......@@ -9,11 +9,22 @@ const IS_EE = require('./helpers/is_ee_env');
const ROOT_PATH = path.resolve(__dirname, '..');
const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/;
function fatalError(message) {
function exitError(message) {
console.error(chalk.red(`\nError: ${message}\n`));
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
webpackConfig.entry = undefined;
webpackConfig.mode = 'development';
......@@ -31,7 +42,8 @@ webpackConfig.plugins.push(
}),
);
const specFilters = argumentsParser
const options = argumentsParser
.option('--no-fail-on-empty-test-suite')
.option(
'-f, --filter-spec [filter]',
'Filter run spec files by path. Multiple filters are like a logical OR.',
......@@ -41,7 +53,9 @@ const specFilters = argumentsParser
},
[],
)
.parse(process.argv).filterSpec;
.parse(process.argv);
const specFilters = options.filterSpec;
const createContext = (specFiles, regex, suffix) => {
const newContext = specFiles.reduce((context, file) => {
......@@ -73,11 +87,13 @@ if (specFilters.length) {
filteredSpecFiles = [...new Set(filteredSpecFiles)];
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))) {
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'));
......
......@@ -23,6 +23,8 @@ to help identify if something is wrong:
![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
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**
......@@ -40,7 +42,8 @@ Checking Geo ...
GitLab Geo is available ... yes
GitLab Geo is enabled ... 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 Foreign Data Wrapper schema is up-to-date? ... yes
GitLab Geo HTTP(S) connectivity ...
......@@ -68,22 +71,22 @@ Example output:
```
http://secondary.example.com/
-----------------------------------------------------
GitLab Version: 11.8.1-ee
GitLab Version: 11.10.4-ee
Geo Role: Secondary
Health Status: Healthy
Repositories: 190/190 (100%)
Verified Repositories: 190/190 (100%)
Wikis: 190/190 (100%)
Verified Wikis: 190/190 (100%)
LFS Objects: 35/35 (100%)
Attachments: 528/528 (100%)
CI job artifacts: 477/477 (100%)
Repositories Checked: 0/190 (0%)
Repositories: 289/289 (100%)
Verified Repositories: 289/289 (100%)
Wikis: 289/289 (100%)
Verified Wikis: 289/289 (100%)
LFS Objects: 8/8 (100%)
Attachments: 5/5 (100%)
CI job artifacts: 0/0 (0%)
Repositories Checked: 0/289 (0%)
Sync Settings: Full
Database replication lag: 0 seconds
Last event ID seen from primary: 2158 (about 2 minute ago)
Last event ID processed by cursor: 2158 (about 2 minute ago)
Last status report was: 4 minutes ago
Last event ID seen from primary: 10215 (about 2 minutes ago)
Last event ID processed by cursor: 10215 (about 2 minutes ago)
Last status report was: 2 minutes ago
```
## Is Postgres replication working?
......@@ -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-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.
- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq, PGBouncer)
- 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
......@@ -153,7 +153,7 @@ contention due to certain workloads.
- 1 or more NFS/Gitaly servers
- 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
......@@ -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 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
separately:
......
......@@ -134,7 +134,7 @@ otherwise the networks will become a single point of failure.
#### Architecture
![PG HA Architecture](pg_ha_architecture.png)
![PG HA Architecture](img/pg_ha_architecture.png)
Database nodes run two services with PostgreSQL:
......
......@@ -280,6 +280,14 @@ installations from source.
Currently it logs the progress of project imports from the Bitbucket Server
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 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
installations from source.
[repocheck]: repository_checks.md
[Rack Attack]: ../security/rack_attack.md
......@@ -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 |
| `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
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:
"group_id": 49,
"parent_id": 23,
"has_children": false,
"has_issues": false,
"reference": "&2",
"url": "http://localhost/groups/group16/-/epics/2",
"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
{
"key": "TEST_VARIABLE_1",
"variable_type": "env_var",
"value": "TEST_1"
"value": "TEST_1",
"protected": false,
"masked": true
}
```
......@@ -71,6 +73,7 @@ POST /projects/:id/variables
| `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` |
| `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"
......@@ -81,7 +84,8 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
"key": "NEW_VARIABLE",
"value": "new value",
"variable_type": "env_var",
"protected": false
"protected": false,
"masked": false
}
```
......@@ -100,6 +104,7 @@ PUT /projects/:id/variables/:key
| `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` |
| `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"
......@@ -110,7 +115,8 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
"key": "NEW_VARIABLE",
"value": "updated value",
"variable_type": "env_var",
"protected": true
"protected": true,
"masked": false
}
```
......
......@@ -1986,7 +1986,7 @@ production:
- deploy
environment:
name: production
url: https://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
only:
- master
```
......
......@@ -59,6 +59,7 @@ description: 'Learn how to contribute to GitLab.'
- [DeclarativePolicy framework](policies.md)
- [How Git object deduplication works in GitLab](git_object_deduplication.md)
- [Geo development](geo.md)
- [Routing](routing.md)
## 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