Commit addec47c authored by Philip Jonas's avatar Philip Jonas

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab into...

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab into 204739-app-views-discussions-remove-dormant-code

* 'master' of https://gitlab.com/gitlab-org/gitlab: (54 commits)
  Resolve Migrate '.fa-spinner' to '.spinner' for 'app/views/shared/badges'
  Improve the nuget repository documentation
  Fix typo: missing leading `.` in `.gitlab-ci.yml`
  Replace refresh_index in specs
  Refactor charts header to the panel wrapper
  Refactor FunctionURI class
  Add Delivery as an owner of the release config
  Fix use of CI_PROJECT_NAME for syncing branches
  Update pipeline documentation with DAG on `test` jobs
  Add `needs` to `test` jobs to bypass `fixtures`
  Use unique feature flag name for ISD
  Update resource boundary for ExportCsvWorker
  Reduce number of fork targets for group-managed accounts
  Update Elastic Load Balancer type
  Set initial date from backend params
  Update misleading automated tests note in Geo documentation
  Test against PG11 in nightly scheduled pipelines
  Update gitlab pipeline DAG documentation
  Remove db dependencies on frontend jobs
  Use `yarn karma` instead of `bundle exec rake karma`
  ...
parents 2c01d39f 1d4987fa
......@@ -3,6 +3,7 @@ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.1
stages:
- sync
- prepare
- fixtures
- test
- post-test
- review-prepare
......
......@@ -43,3 +43,6 @@ Dangerfile @gl-quality/eng-prod
/danger/ @gl-quality/eng-prod
/lib/gitlab/danger/ @gl-quality/eng-prod
/scripts/ @gl-quality/eng-prod
# Delivery owner files
/.gitlab/ci/releases.gitlab-ci.yml @gitlab-org/delivery
......@@ -7,7 +7,6 @@
- .use-pg9
stage: test
needs: ["setup-test-env"]
dependencies: ["setup-test-env"]
variables:
FIXTURE_PATH: "db/fixtures/development"
SEED_CYCLE_ANALYTICS: "true"
......
......@@ -46,7 +46,9 @@ docs lint:
- .docs:rules:docs-lint
image: "registry.gitlab.com/gitlab-org/gitlab-docs:docs-lint"
stage: test
dependencies: []
needs:
- job: "retrieve-tests-metadata"
artifacts: false
script:
- scripts/lint-doc.sh
# Lint Markdown
......
......@@ -120,15 +120,54 @@ compile-assets pull-cache as-if-foss:
policy: pull
key: "assets-compile:v9:foss"
.frontend-job-base:
.frontend-fixtures-base:
extends:
- .default-tags
- .default-retry
- .default-cache
- .default-before_script
- .use-pg9
stage: fixtures
needs:
- job: "setup-test-env"
artifacts: true
- job: "compile-assets pull-cache"
artifacts: true
script:
- date
- scripts/gitaly-test-spawn
- date
- bundle exec rake frontend:fixtures
artifacts:
name: frontend-fixtures
expire_in: 31d
when: always
paths:
- node_modules
- public/assets
- tmp/tests/frontend/
frontend-fixtures:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs
frontend-fixtures-as-if-foss:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
.frontend-job-base:
extends:
- .default-tags
- .default-retry
- .default-cache
- .default-before_script
variables:
USE_BUNDLE_INSTALL: "false"
SETUP_DB: "false"
stage: test
needs: ["setup-test-env", "compile-assets pull-cache"]
.karma-base:
extends: .frontend-job-base
......@@ -138,14 +177,13 @@ compile-assets pull-cache as-if-foss:
script:
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- date
- scripts/gitaly-test-spawn
- date
- bundle exec rake karma
- yarn karma
karma:
extends:
- .karma-base
- .frontend:rules:default-frontend-jobs
needs: ["frontend-fixtures"]
coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts:
name: coverage-javascript
......@@ -163,13 +201,11 @@ karma-as-if-foss:
- .karma-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
needs: ["frontend-fixtures-as-if-foss"]
.jest-base:
extends: .frontend-job-base
script:
- scripts/gitaly-test-spawn
- date
- bundle exec rake frontend:fixtures
- date
- yarn jest --ci --coverage
cache:
......@@ -182,6 +218,7 @@ jest:
extends:
- .jest-base
- .frontend:rules:default-frontend-jobs
needs: ["frontend-fixtures"]
artifacts:
name: coverage-frontend
expire_in: 31d
......@@ -198,6 +235,7 @@ jest-as-if-foss:
- .jest-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
needs: ["frontend-fixtures-as-if-foss"]
cache:
policy: pull
......
......@@ -50,6 +50,15 @@
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg9-ee:
services:
- name: postgres:9.6.17
......@@ -69,6 +78,16 @@
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.use-pg11-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- name: elasticsearch:6.4.2
variables:
POSTGRES_HOST_AUTH_METHOD: trust
.as-if-foss:
variables:
FOSS_ONLY: '1'
......@@ -3,7 +3,9 @@
- .default-tags
- .default-retry
stage: test
dependencies: []
needs:
- job: "retrieve-tests-metadata"
artifacts: false
cache:
key: "qa-framework-jobs:v1"
paths:
......
......@@ -238,6 +238,12 @@ rspec quarantine pg9:
- .rails:rules:master-refs-code-backstage
- .use-pg10
rspec migration pg10:
extends:
- .rspec-base-pg10
- .rspec-base-migration
parallel: 2
rspec unit pg10:
extends: .rspec-base-pg10
parallel: 20
......@@ -252,6 +258,34 @@ rspec system pg10:
# master-only jobs #
####################
############################
# nightly master-only jobs #
.rspec-base-pg11:
extends:
- .rspec-base
- .rails:rules:nightly-master-refs-code-backstage
- .use-pg11
rspec migration pg11:
extends:
- .rspec-base-pg11
- .rspec-base-migration
parallel: 2
rspec unit pg11:
extends: .rspec-base-pg11
parallel: 20
rspec integration pg11:
extends: .rspec-base-pg11
parallel: 8
rspec system pg11:
extends: .rspec-base-pg11
parallel: 24
# nightly master-only jobs #
############################
#########################
# ee + master-only jobs #
rspec-ee quarantine pg9:
......
......@@ -12,6 +12,9 @@ code_quality:
- .default-retry
- .reports:rules:code_quality
stage: test
needs:
- job: "retrieve-tests-metadata"
artifacts: false
image: docker:stable
allow_failure: true
services:
......@@ -39,7 +42,6 @@ code_quality:
paths:
- gl-code-quality-report.json # GitLab-specific
expire_in: 1 week # GitLab-specific
dependencies: []
# We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`.
......@@ -52,7 +54,9 @@ sast:
- .reports:rules:sast
stage: test
allow_failure: true
dependencies: [] # GitLab-specific
needs:
- job: "retrieve-tests-metadata"
artifacts: false
artifacts:
paths:
- gl-sast-report.json # GitLab-specific
......@@ -90,6 +94,9 @@ dependency_scanning:
- .default-retry
- .reports:rules:dependency_scanning
stage: test
needs:
- job: "retrieve-tests-metadata"
artifacts: false
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
......@@ -148,7 +155,6 @@ dependency_scanning:
reports:
dependency_scanning: gl-dependency-scanning-report.json
expire_in: 1 week # GitLab-specific
dependencies: []
# We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`.
......
......@@ -248,7 +248,9 @@ danger-review:
- .review:rules:danger
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test
dependencies: []
needs:
- job: "retrieve-tests-metadata"
artifacts: false
script:
- git version
- node --version
......
......@@ -22,6 +22,9 @@
.if-merge-request: &if-merge-request
if: '$CI_MERGE_REQUEST_IID'
.if-nightly-master-schedule: &if-nightly-master-schedule
if: '$NIGHTLY && $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule"'
.if-dot-com-gitlab-org-schedule: &if-dot-com-gitlab-org-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"'
......@@ -343,6 +346,12 @@
changes: *code-backstage-patterns
when: on_success
.rails:rules:nightly-master-refs-code-backstage:
rules:
- <<: *if-nightly-master-schedule
changes: *code-backstage-patterns
when: on_success
.rails:rules:ee-only:
rules:
- <<: *if-not-ee
......@@ -362,11 +371,11 @@
##################
.releases:rules:canonical-dot-com-gitlab-stable-branch-only:
rules:
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAME == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
.releases:rules:canonical-dot-com-security-gitlab-stable-branch-only:
rules:
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAME == "gitlab-org/security/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/security/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
#################
# Reports rules #
......
......@@ -355,7 +355,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 5.1.0'
gem 'rspec-rails', '~> 4.0.0.beta3'
gem 'rspec-rails', '~> 4.0.0.beta4'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.11.0'
......
......@@ -894,36 +894,36 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.4)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.1)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (~> 3.9.0)
rspec-parameterized (0.4.2)
binding_ninja (>= 0.2.3)
parser
proc_to_ast
rspec (>= 2.13, < 4)
unparser
rspec-rails (4.0.0.beta3)
rspec-rails (4.0.0.beta4)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-core (~> 3.8)
rspec-expectations (~> 3.8)
rspec-mocks (~> 3.8)
rspec-support (~> 3.8)
rspec-core (~> 3.9)
rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-retry (0.6.1)
rspec-core (> 3.3)
rspec-set (0.1.3)
rspec-support (3.8.2)
rspec-support (3.9.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rspec_profiling (0.0.5)
......@@ -1350,7 +1350,7 @@ DEPENDENCIES
rouge (~> 3.16.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 4.0.0.beta3)
rspec-rails (~> 4.0.0.beta4)
rspec-retry (~> 0.6.1)
rspec-set (~> 0.1.3)
rspec_junit_formatter
......
<script>
/* eslint-disable vue/require-default-prop */
import _ from 'underscore';
import { isEmpty, isString } from 'lodash';
import Identicon from '~/vue_shared/components/identicon.vue';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
......@@ -39,7 +39,7 @@ export default {
},
computed: {
hasAvatar() {
return _.isString(this.avatarUrl) && !_.isEmpty(this.avatarUrl);
return isString(this.avatarUrl) && !isEmpty(this.avatarUrl);
},
truncatedNamespace() {
return truncateNamespace(this.namespace);
......
<script>
import _ from 'underscore';
import { debounce } from 'lodash';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
......@@ -21,7 +21,7 @@ export default {
},
},
watch: {
searchQuery: _.debounce(function debounceSearchQuery() {
searchQuery: debounce(function debounceSearchQuery() {
this.setSearchQuery(this.searchQuery);
}, 500),
},
......
import _ from 'underscore';
import { take } from 'lodash';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import sanitize from 'sanitize-html';
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
......@@ -31,7 +31,7 @@ export const getTopFrequentItems = items => {
return 0;
});
return _.first(frequentItems, frequentItemsCount);
return take(frequentItems, frequentItemsCount);
};
export const updateExistingFrequentItem = (frequentItem, item) => {
......
<script>
import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -48,7 +47,7 @@ export default {
},
computed: {
isSearchEmpty() {
return _.isEmpty(this.search);
return !this.search.length;
},
showSuggestions() {
return !this.isSearchEmpty && this.issues.length && !this.loading;
......
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import _ from 'underscore';
import { uniqueId } from 'lodash';
import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -36,13 +36,13 @@ export default {
counts() {
return [
{
id: _.uniqueId(),
id: uniqueId(),
icon: 'thumb-up',
tooltipTitle: __('Upvotes'),
count: this.suggestion.upvotes,
},
{
id: _.uniqueId(),
id: uniqueId(),
icon: 'comment',
tooltipTitle: __('Comments'),
count: this.suggestion.userNotesCount,
......
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import updateDescription from '../utils/update_description';
......@@ -26,7 +25,7 @@ export default class Store {
'.detail-page-description.content-block',
);
const details =
!_.isNull(descriptionSection) && descriptionSection.getElementsByTagName('details');
descriptionSection != null && descriptionSection.getElementsByTagName('details');
this.state.descriptionHtml = updateDescription(data.description, details);
this.state.titleHtml = data.title;
......
import _ from 'underscore';
/**
* Function that replaces the open attribute for the <details> element.
*
......@@ -10,7 +8,7 @@ import _ from 'underscore';
const updateDescription = (descriptionHtml = '', details) => {
let detailNodes = details;
if (_.isEmpty(details)) {
if (!details.length) {
detailNodes = [];
}
......
......@@ -90,11 +90,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<div v-gl-resize-observer-directive="onResize">
<gl-column-chart
ref="columnChart"
v-bind="$attrs"
......
......@@ -27,10 +27,7 @@ export default {
};
</script>
<template>
<div class="prometheus-graph d-flex flex-column justify-content-center">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
<div class="d-flex flex-column justify-content-center">
<div
class="prepend-top-8 svg-w-100 d-flex align-items-center"
:style="svgContainerStyle"
......
......@@ -2,13 +2,11 @@
import { GlResizeObserverDirective } from '@gitlab/ui';
import { GlHeatmap } from '@gitlab/ui/dist/charts';
import dateformat from 'dateformat';
import PrometheusHeader from '../shared/prometheus_header.vue';
import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
GlHeatmap,
PrometheusHeader,
},
directives: {
GlResizeObserverDirective,
......@@ -65,8 +63,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph col-12 col-lg-6">
<prometheus-header :graph-title="graphData.title" />
<div v-gl-resize-observer-directive="onResize" class="col-12 col-lg-6">
<gl-heatmap
ref="heatmapChart"
v-bind="$attrs"
......
......@@ -42,10 +42,7 @@ export default {
};
</script>
<template>
<div class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
<div>
<gl-single-stat :value="statValue" :title="graphTitle" variant="success" />
</div>
</template>
......@@ -81,11 +81,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<div v-gl-resize-observer-directive="onResize">
<gl-stacked-column-chart
ref="chart"
v-bind="$attrs"
......
......@@ -112,7 +112,6 @@ export default {
isDeployment: false,
sha: '',
},
showTitleTooltip: false,
width: 0,
height: chartHeight,
svgs: {},
......@@ -285,12 +284,6 @@ export default {
return `${this.graphData.y_label}`;
},
},
mounted() {
const graphTitleEl = this.$refs.graphTitle;
if (graphTitleEl && graphTitleEl.scrollWidth > graphTitleEl.offsetWidth) {
this.showTitleTooltip = true;
}
},
created() {
this.setSvg('rocket');
this.setSvg('scroll-handle');
......@@ -387,24 +380,7 @@ export default {
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5
ref="graphTitle"
class="prometheus-graph-title js-graph-title text-truncate append-right-8"
>
{{ graphData.title }}
</h5>
<gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip">
{{ graphData.title }}
</gl-tooltip>
<div
class="prometheus-graph-widgets js-graph-widgets flex-fill"
data-qa-selector="prometheus_graph_widgets"
>
<slot></slot>
</div>
</div>
<div v-gl-resize-observer-directive="onResize">
<component
:is="glChartComponent"
ref="chart"
......
......@@ -3,10 +3,12 @@ import { mapState } from 'vuex';
import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
import {
GlResizeObserverDirective,
GlDropdown,
GlDropdownItem,
GlModal,
GlModalDirective,
GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui';
import { __ } from '~/locale';
......@@ -29,11 +31,13 @@ export default {
MonitorStackedColumnChart,
MonitorEmptyChart,
Icon,
GlTooltip,
GlDropdown,
GlDropdownItem,
GlModal,
},
directives: {
GlResizeObserver: GlResizeObserverDirective,
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
TrackEvent: TrackEventDirective,
......@@ -61,11 +65,15 @@ export default {
},
data() {
return {
showTitleTooltip: false,
zoomedTimeRange: null,
};
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
title() {
return this.graphData.title || '';
},
alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
},
......@@ -97,12 +105,24 @@ export default {
const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data);
},
monitorChartComponent() {
timeChartComponent() {
if (this.isPanelType('anomaly-chart')) {
return MonitorAnomalyChart;
}
return MonitorTimeSeriesChart;
},
isContextualMenuShown() {
return (
this.graphDataHasMetrics &&
!this.isPanelType('single-stat') &&
!this.isPanelType('heatmap') &&
!this.isPanelType('column') &&
!this.isPanelType('stacked-column')
);
},
},
mounted() {
this.refreshTitleTooltip();
},
methods: {
getGraphAlerts(queries) {
......@@ -119,9 +139,18 @@ export default {
showToast() {
this.$toast.show(__('Link copied'));
},
refreshTitleTooltip() {
const { graphTitle } = this.$refs;
this.showTitleTooltip =
Boolean(graphTitle) && graphTitle.scrollWidth > graphTitle.offsetWidth;
},
downloadCSVOptions,
generateLinkToChartOptions,
onResize() {
this.refreshTitleTooltip();
},
onDatazoom({ start, end }) {
this.zoomedTimeRange = { start, end };
},
......@@ -129,88 +158,109 @@ export default {
};
</script>
<template>
<monitor-single-stat-chart
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-heatmap-chart
v-else-if="isPanelType('heatmap') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-column-chart
v-else-if="isPanelType('column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-stacked-column-chart
v-else-if="isPanelType('stacked-column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<component
:is="monitorChartComponent"
v-else-if="graphDataHasMetrics"
ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
@datazoom="onDatazoom"
>
<div class="d-flex align-items-center">
<alert-widget
v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
@setAlerts="setAlerts"
/>
<gl-dropdown
v-gl-tooltip
class="ml-auto mx-3"
toggle-class="btn btn-transparent border-0"
data-qa-selector="prometheus_widgets_dropdown"
:right="true"
:no-caret="true"
:title="__('More actions')"
<div v-gl-resize-observer="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5
ref="graphTitle"
class="prometheus-graph-title gl-font-size-large font-weight-bold text-truncate append-right-8"
>
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
{{ title }}
</h5>
<gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip">
{{ title }}
</gl-tooltip>
<div
v-if="isContextualMenuShown"
class="prometheus-graph-widgets js-graph-widgets flex-fill"
data-qa-selector="prometheus_graph_widgets"
>
<div class="d-flex align-items-center">
<alert-widget
v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
@setAlerts="setAlerts"
/>
<gl-dropdown
v-gl-tooltip
class="ml-auto mx-3"
toggle-class="btn btn-transparent border-0"
data-qa-selector="prometheus_widgets_dropdown"
right
no-caret
:title="__('More actions')"
>
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
<gl-dropdown-item
v-if="logsPathWithTimeRange"
ref="viewLogsLink"
:href="logsPathWithTimeRange"
>
{{ s__('Metrics|View logs') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="logsPathWithTimeRange"
ref="viewLogsLink"
:href="logsPathWithTimeRange"
>
{{ s__('Metrics|View logs') }}
</gl-dropdown-item>
<gl-dropdown-item
v-track-event="downloadCSVOptions(graphData.title)"
:href="downloadCsv"
download="chart_metrics.csv"
>
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="clipboardText"
ref="copyChartLink"
v-track-event="generateLinkToChartOptions(clipboardText)"
:data-clipboard-text="clipboardText"
@click="showToast(clipboardText)"
>
{{ __('Generate link to chart') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${index}`"
data-qa-selector="alert_widget_menu_item"
>
{{ __('Alerts') }}
</gl-dropdown-item>
</gl-dropdown>
<gl-dropdown-item
v-if="csvText"
ref="downloadCsvLink"
v-track-event="downloadCSVOptions(title)"
:href="downloadCsv"
download="chart_metrics.csv"
>
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="clipboardText"
ref="copyChartLink"
v-track-event="generateLinkToChartOptions(clipboardText)"
:data-clipboard-text="clipboardText"
@click="showToast(clipboardText)"
>
{{ __('Generate link to chart') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${index}`"
data-qa-selector="alert_widget_menu_item"
>
{{ __('Alerts') }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
</div>
</component>
<monitor-empty-chart v-else :graph-title="graphData.title" />
<monitor-single-stat-chart
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-heatmap-chart
v-else-if="isPanelType('heatmap') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-column-chart
v-else-if="isPanelType('column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-stacked-column-chart
v-else-if="isPanelType('stacked-column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<component
:is="timeChartComponent"
v-else-if="graphDataHasMetrics"
ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
@datazoom="onDatazoom"
/>
<monitor-empty-chart v-else :graph-title="title" v-bind="$attrs" v-on="$listeners" />
</div>
</template>
<script>
export default {
props: {
graphTitle: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="prometheus-graph-header">
<h5 ref="title" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import _ from 'underscore';
import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
......@@ -50,7 +50,7 @@ export default {
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
),
{
linkStart: `<a href="${_.escape(
linkStart: `<a href="${esc(
this.updateReleaseApiDocsPath,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
......
<script>
import _ from 'underscore';
import { isEmpty } from 'lodash';
import $ from 'jquery';
import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility';
......@@ -64,7 +64,7 @@ export default {
return !this.glFeatures.releaseIssueSummary;
},
shouldRenderMilestoneInfo() {
return Boolean(this.glFeatures.releaseIssueSummary && !_.isEmpty(this.release.milestones));
return Boolean(this.glFeatures.releaseIssueSummary && !isEmpty(this.release.milestones));
},
},
......
......@@ -91,10 +91,6 @@
margin-bottom: $gl-padding-8;
}
.prometheus-graph-title {
font-size: $gl-font-size-large;
}
.alert-current-setting {
max-width: 240px;
}
......
......@@ -3,6 +3,7 @@
class Projects::ForksController < Projects::ApplicationController
include ContinueParams
include RendersMemberAccess
include Gitlab::Utils::StrongMemoize
# Authorize
before_action :whitelist_query_limiting, only: [:create]
......@@ -10,6 +11,7 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create]
before_action :authorize_fork_project!, only: [:new, :create]
before_action :authorize_fork_namespace!, only: [:create]
# rubocop: disable CodeReuse/ActiveRecord
def index
......@@ -37,18 +39,15 @@ class Projects::ForksController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
@namespaces = fork_service.valid_fork_targets
end
# rubocop: disable CodeReuse/ActiveRecord
def create
namespace = Namespace.find(params[:namespace_key])
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = fork_namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
@forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
@forked_project ||= fork_service.execute
if !@forked_project.saved? || !@forked_project.forked?
render :error
......@@ -64,6 +63,22 @@ class Projects::ForksController < Projects::ApplicationController
private
def fork_service
strong_memoize(:fork_service) do
::Projects::ForkService.new(project, current_user, namespace: fork_namespace)
end
end
def fork_namespace
strong_memoize(:fork_namespace) do
Namespace.find(params[:namespace_key]) if params[:namespace_key].present?
end
end
def authorize_fork_namespace!
access_denied! unless fork_namespace && fork_service.valid_fork_target?
end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335')
end
......
# frozen_string_literal: true
class ForkTargetsFinder
def initialize(project, user)
@project = project
@user = user
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
::Namespace.where(id: user.manageable_namespaces).where.not(id: project.namespace).sort_by_type
end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :project, :user
end
ForkTargetsFinder.prepend_if_ee('EE::ForkTargetsFinder')
# frozen_string_literal: true
class ServerlessDomainFinder
attr_reader :match, :serverless_domain_cluster, :environment
def initialize(uri)
@match = ::Serverless::Domain::REGEXP.match(uri)
end
def execute
return unless serverless?
@serverless_domain_cluster = ::Serverless::DomainCluster.for_uuid(serverless_domain_cluster_uuid)
return unless serverless_domain_cluster
@environment = ::Environment.for_id_and_slug(match[:environment_id].to_i(16), match[:environment_slug])
return unless environment
::Serverless::Domain.new(
function_name: match[:function_name],
serverless_domain_cluster: serverless_domain_cluster,
environment: environment
)
end
def serverless_domain_cluster_uuid
return unless serverless?
match[:cluster_left] + match[:cluster_middle] + match[:cluster_right]
end
def serverless?
!!match
end
end
......@@ -315,7 +315,9 @@ module ApplicationSettingsHelper
:push_event_hooks_limit,
:push_event_activities_limit,
:custom_http_clone_url_root,
:snippet_size_limit
:snippet_size_limit,
:email_restrictions_enabled,
:email_restrictions
]
end
......
......@@ -2,7 +2,6 @@
module LabelsHelper
extend self
include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil)
return true unless label.project_label?
......@@ -64,8 +63,8 @@ module LabelsHelper
# by LabelReferenceFilter
span = %(<span class="badge color-label #{"has-tooltip" if tooltip}" ) +
%(data-html="true" style="background-color: #{label.color}; color: #{text_color}" ) +
%(title="#{escape_once(title)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
%(title="#{ERB::Util.html_escape_once(title)}" data-container="body">) +
%(#{ERB::Util.html_escape_once(label.name)}#{label_suffix}</span>)
span.html_safe
end
......@@ -247,9 +246,6 @@ module LabelsHelper
def issuable_types
['issues', 'merge requests']
end
# Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once, :label_tooltip_title
end
LabelsHelper.prepend_if_ee('EE::LabelsHelper')
......@@ -243,6 +243,8 @@ class ApplicationSetting < ApplicationRecord
validates :snippet_size_limit, numericality: { only_integer: true, greater_than: 0 }
validate :email_restrictions_regex_valid?
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
......@@ -381,6 +383,14 @@ class ApplicationSetting < ApplicationRecord
def recaptcha_or_login_protection_enabled
recaptcha_enabled || login_recaptcha_protection_enabled
end
def email_restrictions_regex_valid?
return if email_restrictions.blank?
Gitlab::UntrustedRegexp.new(email_restrictions)
rescue RegexpError
errors.add(:email_restrictions, _('is not a valid regular expression'))
end
end
ApplicationSetting.prepend_if_ee('EE::ApplicationSetting')
......@@ -62,6 +62,8 @@ module ApplicationSettingImplementation
eks_account_id: nil,
eks_access_key_id: nil,
eks_secret_access_key: nil,
email_restrictions_enabled: false,
email_restrictions: nil,
first_day_of_week: 0,
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
......
......@@ -15,7 +15,7 @@ module Clusters
def set_initial_status
return unless not_installable?
self.status = status_states[:installable] if cluster&.application_helm_available? || Feature.enabled?(:managed_apps_local_tiller)
self.status = status_states[:installable] if cluster&.application_helm_available? || ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
end
def can_uninstall?
......
......@@ -23,7 +23,7 @@ module Clusters
@files ||= begin
files = { 'values.yaml': values }
files.merge!(certificate_files) if cluster.application_helm.has_ssl?
files.merge!(certificate_files) if use_tiller_ssl?
files
end
......@@ -31,6 +31,12 @@ module Clusters
private
def use_tiller_ssl?
return false if ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
cluster.application_helm.has_ssl?
end
def certificate_files
{
'ca.pem': ca_cert,
......
......@@ -92,7 +92,10 @@ module Clusters
# When installing any application we are also performing an update
# of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so
# therefore we need to reflect that in the database.
application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
unless ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
end
end
after_transition any => [:uninstalling], :use_transactions => false do |application, _|
......
......@@ -95,6 +95,10 @@ class Environment < ApplicationRecord
end
end
def self.for_id_and_slug(id, slug)
find_by(id: id, slug: slug)
end
def self.max_deployment_id_sql
Deployment.select(Deployment.arel_table[:id].maximum)
.where(Deployment.arel_table[:environment_id].eq(arel_table[:id]))
......
......@@ -68,6 +68,7 @@ class Namespace < ApplicationRecord
after_destroy :rm_dir
scope :for_user, -> { where('type IS NULL') }
scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) }
scope :with_statistics, -> do
joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id')
......@@ -326,7 +327,10 @@ class Namespace < ApplicationRecord
end
def pages_virtual_domain
Pages::VirtualDomain.new(all_projects_with_pages, trim_prefix: full_path)
Pages::VirtualDomain.new(
all_projects_with_pages.includes(:route, :project_feature),
trim_prefix: full_path
)
end
def closest_setting(name)
......
# frozen_string_literal: true
module Serverless
class Domain
include ActiveModel::Model
REGEXP = %r{^(?<scheme>https?://)?(?<function_name>[^.]+)-(?<cluster_left>\h{2})a1(?<cluster_middle>\h{10})f2(?<cluster_right>\h{2})(?<environment_id>\h+)-(?<environment_slug>[^.]+)\.(?<pages_domain_name>.+)}.freeze
UUID_LENGTH = 14
attr_accessor :function_name, :serverless_domain_cluster, :environment
validates :function_name, presence: true, allow_blank: false
validates :serverless_domain_cluster, presence: true
validates :environment, presence: true
def self.generate_uuid
SecureRandom.hex(UUID_LENGTH / 2)
end
def uri
URI("https://#{function_name}-#{serverless_domain_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}")
end
def knative_uri
URI("http://#{function_name}.#{namespace}.#{serverless_domain_cluster.knative.hostname}")
end
private
def namespace
serverless_domain_cluster.cluster.kubernetes_namespace_for(environment)
end
def serverless_domain_cluster_uuid
[
serverless_domain_cluster.uuid[0..1],
'a1',
serverless_domain_cluster.uuid[2..-3],
'f2',
serverless_domain_cluster.uuid[-2..-1]
].join
end
end
end
......@@ -16,11 +16,18 @@ module Serverless
algorithm: 'aes-256-gcm'
validates :pages_domain, :knative, presence: true
validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH },
validates :uuid, presence: true, uniqueness: true, length: { is: ::Serverless::Domain::UUID_LENGTH },
format: { with: HEX_REGEXP, message: 'only allows hex characters' }
default_value_for(:uuid, allows_nil: false) { Gitlab::Serverless::Domain.generate_uuid }
default_value_for(:uuid, allows_nil: false) { ::Serverless::Domain.generate_uuid }
delegate :domain, to: :pages_domain
delegate :cluster, to: :knative
def self.for_uuid(uuid)
joins(:pages_domain, :knative)
.includes(:pages_domain, :knative)
.find_by(uuid: uuid)
end
end
end
......@@ -189,6 +189,7 @@ class User < ApplicationRecord
validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
validate :check_email_restrictions, on: :create, if: ->(user) { !user.created_by_id }
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }
......@@ -1186,14 +1187,18 @@ class User < ApplicationRecord
Member.where(invite_email: verified_emails).invite
end
def all_emails
def all_emails(include_private_email: true)
all_emails = []
all_emails << email unless temp_oauth_email?
all_emails << private_commit_email
all_emails << private_commit_email if include_private_email
all_emails.concat(emails.map(&:email))
all_emails
end
def all_public_emails
all_emails(include_private_email: false)
end
def verified_emails
verified_emails = []
verified_emails << email if primary_email_verified?
......@@ -1747,6 +1752,18 @@ class User < ApplicationRecord
end
end
def check_email_restrictions
return unless Feature.enabled?(:email_restrictions)
return unless Gitlab::CurrentSettings.email_restrictions_enabled?
restrictions = Gitlab::CurrentSettings.email_restrictions
return if restrictions.blank?
if Gitlab::UntrustedRegexp.new(restrictions).match?(email)
errors.add(:email, _('is not allowed for sign-up'))
end
end
def self.unique_internal(scope, username, email_pattern, &block)
scope.first || create_unique_internal(scope, username, email_pattern, &block)
end
......
......@@ -3,24 +3,25 @@
module Projects
class ForkService < BaseService
def execute(fork_to_project = nil)
forked_project =
if fork_to_project
link_existing_project(fork_to_project)
else
fork_new_project
end
forked_project = fork_to_project ? link_existing_project(fork_to_project) : fork_new_project
refresh_forks_count if forked_project&.saved?
forked_project
end
private
def valid_fork_targets
@valid_fork_targets ||= ForkTargetsFinder.new(@project, current_user).execute
end
def allowed_fork?
current_user.can?(:fork_project, @project)
def valid_fork_target?
return true if current_user.admin?
valid_fork_targets.include?(target_namespace)
end
private
def link_existing_project(fork_to_project)
return if fork_to_project.forked?
......@@ -30,6 +31,21 @@ module Projects
end
def fork_new_project
new_project = CreateService.new(current_user, new_fork_params).execute
return new_project unless new_project.persisted?
# Set the forked_from_project relation after saving to avoid having to
# reload the project to reset the association information and cause an
# extra query.
new_project.forked_from_project = @project
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update(builds_access_level: builds_access_level)
new_project
end
def new_fork_params
new_params = {
visibility_level: allowed_visibility_level,
description: @project.description,
......@@ -57,18 +73,11 @@ module Projects
new_params.merge!(@project.object_pool_params)
new_project = CreateService.new(current_user, new_params).execute
return new_project unless new_project.persisted?
# Set the forked_from_project relation after saving to avoid having to
# reload the project to reset the association information and cause an
# extra query.
new_project.forked_from_project = @project
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update(builds_access_level: builds_access_level)
new_params
end
new_project
def allowed_fork?
current_user.can?(:fork_project, @project)
end
def fork_network
......
......@@ -49,6 +49,20 @@
= f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'label-bold'
= f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
.form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
- if Feature.enabled?(:email_restrictions)
.form-group
= f.label :email_restrictions_enabled, _('Email restrictions'), class: 'label-bold'
.form-check
= f.check_box :email_restrictions_enabled, class: 'form-check-input'
= f.label :email_restrictions_enabled, class: 'form-check-label' do
= _('Enable email restrictions for sign ups')
.form-group
= f.label :email_restrictions, _('Email restrictions for sign-ups'), class: 'label-bold'
= f.text_area :email_restrictions, class: 'form-control', rows: 4
.form-text.text-muted
- supported_syntax_link_url = 'https://github.com/google/re2/wiki/Syntax'
- supported_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: supported_syntax_link_url }
= _('Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information.').html_safe % { supported_syntax_link_start: supported_syntax_link_start, supported_syntax_link_end: '</a>'.html_safe }
.form-group
= f.label :after_sign_up_text, class: 'label-bold'
......
......@@ -2,7 +2,7 @@
%h4.danger-title= _('Remove group')
= form_tag(group, method: :delete) do
%p
= _('Removing group will cause all child projects and resources to be removed.')
= _('Removing this group also removes all child projects, including archived projects, and their resources.')
%br
%strong= _('Removed group can not be restored!')
......
......@@ -5,7 +5,7 @@
- help_text = email_change_disabled ? s_("Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO.") % { group_name: @user.managing_group.name } : read_only_help_text
= form.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?), help: help_text.html_safe, readonly: readonly || email_change_disabled
= form.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
= form.select :public_email, options_for_select(@user.all_public_emails, selected: @user.public_email),
{ help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
control_class: 'select2 input-lg', disabled: email_change_disabled
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
......
- form = local_assigns.fetch(:form)
.form-group
= form.label :notification_email, class: "label-bold"
= form.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
= form.select :notification_email, @user.all_public_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
.help-block
= local_assigns.fetch(:help_text, nil)
......@@ -13,4 +13,4 @@
.table-section.section-30
= form_for setting, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
= f.select :notification_email, @user.all_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'
= f.select :notification_email, @user.all_public_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'
#badge-settings{ data: { api_endpoint_url: @badge_api_endpoint,
docs_url: help_page_path('user/project/badges')} }
.text-center.prepend-top-default
= icon('spinner spin 2x')
---
title: Hide the private commit email in Notification email list
merge_request: 25099
author: briankabiro
type: changed
---
title: Add deploy tokens instance API endpoint
merge_request: 25066
author:
type: added
---
title: Remove unused loading spinner from badge_settings partial
merge_request: 25044
author: nuwe1
type: other
---
title: Add restrictions for signup email addresses
merge_request: 25122
author:
type: added
# frozen_string_literal: true
class ChangeSamlProviderOuterForksDefault < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
change_column_null :saml_providers, :prohibited_outer_forks, false
change_column_default :saml_providers, :prohibited_outer_forks, true
end
def down
change_column_default :saml_providers, :prohibited_outer_forks, false
change_column_null :saml_providers, :prohibited_outer_forks, true
end
end
# frozen_string_literal: true
class RenameSecurityDashboardFeatureFlagToInstanceSecurityDashboard < ActiveRecord::Migration[6.0]
DOWNTIME = false
class FeatureGate < ApplicationRecord
self.table_name = 'feature_gates'
end
def up
security_dashboard_feature = FeatureGate.find_by(feature_key: :security_dashboard, key: :boolean)
if security_dashboard_feature.present?
FeatureGate.safe_find_or_create_by!(
feature_key: :instance_security_dashboard,
key: :boolean,
value: security_dashboard_feature.value
)
end
end
def down
FeatureGate.find_by(feature_key: :instance_security_dashboard, key: :boolean)&.delete
end
end
# frozen_string_literal: true
class AddEmailRestrictionsToApplicationSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
add_column(:application_settings, :email_restrictions_enabled, :boolean, default: false, null: false)
add_column(:application_settings, :email_restrictions, :text, null: true)
end
def down
remove_column(:application_settings, :email_restrictions_enabled)
remove_column(:application_settings, :email_restrictions)
end
end
# frozen_string_literal: true
class RemoveSecurityDashboardFeatureFlag < ActiveRecord::Migration[6.0]
DOWNTIME = false
class FeatureGate < ApplicationRecord
self.table_name = 'feature_gates'
end
def up
FeatureGate.find_by(feature_key: :security_dashboard, key: :boolean)&.delete
end
def down
instance_security_dashboard_feature = FeatureGate.find_by(feature_key: :instance_security_dashboard, key: :boolean)
if instance_security_dashboard_feature.present?
FeatureGate.safe_find_or_create_by!(
feature_key: :security_dashboard,
key: instance_security_dashboard_feature.key,
value: instance_security_dashboard_feature.value
)
end
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_02_13_220211) do
ActiveRecord::Schema.define(version: 2020_02_14_034836) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -349,6 +349,8 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.boolean "disable_overriding_approvers_per_merge_request", default: false, null: false
t.boolean "prevent_merge_requests_author_approval", default: false, null: false
t.boolean "prevent_merge_requests_committers_approval", default: false, null: false
t.boolean "email_restrictions_enabled", default: false, null: false
t.text "email_restrictions"
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
......@@ -3768,7 +3770,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.string "sso_url", null: false
t.boolean "enforced_sso", default: false, null: false
t.boolean "enforced_group_managed_accounts", default: false, null: false
t.boolean "prohibited_outer_forks", default: false
t.boolean "prohibited_outer_forks", default: true, null: false
t.index ["group_id"], name: "index_saml_providers_on_group_id"
end
......
......@@ -30,7 +30,7 @@ Implementing Geo provides the following benefits:
- Reduce from minutes to seconds the time taken for your distributed developers to clone and fetch large repositories and projects.
- Enable all of your developers to contribute ideas and work in parallel, no matter where they are.
- Balance the load between your **primary** and **secondary** nodes, or offload your automated tests to a **secondary** node.
- Balance the read-only load between your **primary** and **secondary** nodes.
In addition, it:
......@@ -249,6 +249,7 @@ This list of limitations only reflects the latest version of GitLab. If you are
- [Selective synchronization](configuration.md#selective-synchronization) applies only to files and repositories. Other datasets are replicated to the **secondary** node in full, making it inappropriate for use as an access control mechanism.
- Object pools for forked project deduplication work only on the **primary** node, and are duplicated on the **secondary** node.
- [External merge request diffs](../../merge_request_diffs.md) will not be replicated if they are on-disk, and viewing merge requests will fail. However, external MR diffs in object storage **are** supported. The default configuration (in-database) does work.
- GitLab Runners cannot register with a **secondary** node. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/issues/3294).
### Limitations on replication/verification
......
# Deploy Tokens API
## List all deploy tokens
Get a list of all deploy tokens across all projects of the GitLab instance.
>**Note:**
> This endpoint requires admin access.
```
GET /deploy_tokens
```
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/deploy_tokens"
```
Example response:
```json
[
{
"id": 1,
"name": "MyToken",
"username": "gitlab+deploy-token-1",
"expires_at": "2020-02-14T00:00:00.000Z",
"token": "jMRvtPNxrn3crTAGukpZ",
"scopes": [
"read_repository",
"read_registry"
]
}
]
```
......@@ -18,6 +18,8 @@ GET /projects/:id/packages
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `order_by`| string | no | The field to use as order. One of `created_at` (default), `name`, `version`, or `type`. |
| `sort` | string | no | The direction of the order, either `asc` (default) for ascending order or `desc` for descending order. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/packages
......@@ -61,6 +63,8 @@ GET /groups/:id/packages
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | ID or [URL-encoded path of the group](README.md#namespaced-path-encoding). |
| `exclude_subgroups` | boolean | false | If the param is included as true, packages from projects from subgroups are not listed. Default is `false`. |
| `order_by`| string | no | The field to use as order. One of `created_at` (default), `name`, `version`, `type`, or `project_path`. |
| `sort` | string | no | The direction of the order, either `asc` (default) for ascending order or `desc` for descending order. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=true
......
......@@ -57,6 +57,7 @@ future GitLab releases.**
| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME` | 12.3 | all | The target branch name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
| `CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA` | 12.3 | all | The HEAD SHA of the target branch of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` is used and the pull request is open. |
| `CI_JOB_ID` | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
| `CI_JOB_IMAGE` | 12.9 | 12.9 | The name of the image running the CI job |
| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
......
......@@ -7,16 +7,15 @@ consistent performance of GitLab.
The process of solving performance problems is roughly as follows:
1. Make sure there's an issue open somewhere (e.g., on the GitLab CE issue
tracker), create one if there isn't. See [#15607][#15607] for an example.
1. Make sure there's an issue open somewhere (for example, on the GitLab CE issue
tracker), and create one if there is not. See [#15607][#15607] for an example.
1. Measure the performance of the code in a production environment such as
GitLab.com (see the [Tooling](#tooling) section below). Performance should be
measured over a period of _at least_ 24 hours.
1. Add your findings based on the measurement period (screenshots of graphs,
timings, etc) to the issue mentioned in step 1.
1. Solve the problem.
1. Create a merge request, assign the "Performance" label and assign it to
[@yorickpeterse][yorickpeterse] for reviewing.
1. Create a merge request, assign the "Performance" label and follow the [performance review process](merge_request_performance_guidelines.md).
1. Once a change has been deployed make sure to _again_ measure for at least 24
hours to see if your changes have any impact on the production environment.
1. Repeat until you're done.
......@@ -44,16 +43,16 @@ GitLab provides built-in tools to help improve performance and availability:
- [QueryRecoder](query_recorder.md) for preventing `N+1` regressions.
- [Chaos endpoints](chaos_endpoints.md) for testing failure scenarios. Intended mainly for testing availability.
GitLab employees can use GitLab.com's performance monitoring systems located at
GitLab team members can use [GitLab.com's performance monitoring systems](https://about.gitlab.com/handbook/engineering/monitoring/) located at
<https://dashboards.gitlab.net>, this requires you to log in using your
`@gitlab.com` Email address. Non-GitLab employees are advised to set up their
own InfluxDB + Grafana stack.
`@gitlab.com` email address. Non-GitLab team-members are advised to set up their
own InfluxDB and Grafana stack.
## Benchmarks
Benchmarks are almost always useless. Benchmarks usually only test small bits of
code in isolation and often only measure the best case scenario. On top of that,
benchmarks for libraries (e.g., a Gem) tend to be biased in favour of the
benchmarks for libraries (such as a Gem) tend to be biased in favour of the
library. After all there's little benefit to an author publishing a benchmark
that shows they perform worse than their competitors.
......@@ -68,8 +67,8 @@ When writing benchmarks you should almost always use
[benchmark-ips](https://github.com/evanphx/benchmark-ips). Ruby's `Benchmark`
module that comes with the standard library is rarely useful as it runs either a
single iteration (when using `Benchmark.bm`) or two iterations (when using
`Benchmark.bmbm`). Running this few iterations means external factors (e.g. a
video streaming in the background) can very easily skew the benchmark
`Benchmark.bmbm`). Running this few iterations means external factors, such as a
video streaming in the background, can very easily skew the benchmark
statistics.
Another problem with the `Benchmark` module is that it displays timings, not
......@@ -114,17 +113,18 @@ the behaviour of suspect code in detail.
It's important to note that profiling an application *alters its performance*,
and will generally be done *in an unrepresentative environment*. In particular,
a method is not necessarily troublesome just because it is executed many times,
a method is not necessarily troublesome just because it's executed many times,
or takes a long time to execute. Profiles are tools you can use to better
understand what is happening in an application - using that information wisely
is up to you!
Keeping that in mind, to create a profile, identify (or create) a spec that
exercises the troublesome code path, then run it using the `bin/rspec-stackprof`
helper, e.g.:
helper, for example:
```shell
$ LIMIT=10 bin/rspec-stackprof spec/policies/project_policy_spec.rb
8/8 |====== 100 ======>| Time: 00:00:18
Finished in 18.19 seconds (files took 4.8 seconds to load)
......@@ -170,10 +170,11 @@ kcachegrind project_policy_spec.callgrind # Linux
qcachegrind project_policy_spec.callgrind # Mac
```
It may be useful to zoom in on a specific method, e.g.:
It may be useful to zoom in on a specific method, for example:
```shell
$ stackprof tmp/project_policy_spec.rb.dump --method warm_asset_cache
TestEnv#warm_asset_cache (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/spec/support/test_env.rb:164)
samples: 0 self (0.0%) / 6288 total (36.9%)
callers:
......@@ -239,6 +240,7 @@ shell:
```shell
$ rake rspec_profiling:console
irb(main):001:0> results.count
=> 231
irb(main):002:0> results.last.attributes.keys
......@@ -257,9 +259,9 @@ One of the reasons of the increased memory footprint could be Ruby memory fragme
To diagnose it, you can visualize Ruby heap as described in [this post by Aaron Patterson](https://tenderlovemaking.com/2017/09/27/visualizing-your-ruby-heap.html).
To start, you want to dump the heap of the process you are investigating to a JSON file.
To start, you want to dump the heap of the process you're investigating to a JSON file.
You need to run the command inside the process you are exploring, you may do that with `rbtrace`.
You need to run the command inside the process you're exploring, you may do that with `rbtrace`.
`rbtrace` is already present in GitLab `Gemfile`, you just need to require it.
It could be achieved running webserver or Sidekiq with the environment variable set to `ENABLE_RBTRACE=1`.
......@@ -274,7 +276,7 @@ Having the JSON, you finally could render a picture using the script [provided b
```shell
ruby heapviz.rb heap.json
```
Fragmented Ruby heap snapshot could look like this:
![Ruby heap fragmentation](img/memory_ruby_heap_fragmentation.png)
......@@ -295,11 +297,11 @@ There is no clear set of steps that you can follow to determine if a certain
piece of code is worth optimizing. The only two things you can do are:
1. Think about what the code does, how it's used, how many times it's called and
how much time is spent in it relative to the total execution time (e.g., the
how much time is spent in it relative to the total execution time (for example, the
total time spent in a web request).
1. Ask others (preferably in the form of an issue).
Some examples of changes that aren't really important/worth the effort:
Some examples of changes that are not really important/worth the effort:
- Replacing double quotes with single quotes.
- Replacing usage of Array with Set when the list of values is very small.
......@@ -309,7 +311,7 @@ Some examples of changes that aren't really important/worth the effort:
## Slow Operations & Sidekiq
Slow operations (e.g. merging branches) or operations that are prone to errors
Slow operations, like merging branches, or operations that are prone to errors
(using external APIs) should be performed in a Sidekiq worker instead of
directly in a web request as much as possible. This has numerous benefits such
as:
......@@ -416,7 +418,7 @@ as omitting it may lead to style check failures.
## Anti-Patterns
This is a collection of [anti-patterns][anti-pattern] that should be avoided
unless these changes have a measurable, significant and positive impact on
unless these changes have a measurable, significant, and positive impact on
production environments.
### Moving Allocations to Constants
......@@ -458,5 +460,4 @@ You may find some useful examples in this snippet:
<https://gitlab.com/gitlab-org/gitlab-foss/snippets/33946>
[#15607]: https://gitlab.com/gitlab-org/gitlab-foss/issues/15607
[yorickpeterse]: https://gitlab.com/yorickpeterse
[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
......@@ -100,6 +100,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
| `if-master-refs` | Matches if the current branch is `master`. | |
| `if-master-or-tag` | Matches if the pipeline is for the `master` branch or for a tag. | |
| `if-merge-request` | Matches if the pipeline is for a merge request. | |
| `if-nightly-master-schedule` | Matches if the pipeline is for a `master` scheduled pipeline with `$NIGHTLY` set. | |
| `if-dot-com-gitlab-org-schedule` | Limits jobs creation to scheduled pipelines for the `gitlab-org` group on GitLab.com. | |
| `if-dot-com-gitlab-org-master` | Limits jobs creation to the `master` branch for the `gitlab-org` group on GitLab.com. | |
| `if-dot-com-gitlab-org-merge-request` | Limits jobs creation to merge requests for the `gitlab-org` group on GitLab.com. | |
......@@ -135,17 +136,23 @@ graph RL;
E[review-build-cng];
F[build-qa-image];
G[review-deploy];
I["karma, jest, webpack-dev-server, static-analysis"];
I["karma, jest"];
I2["karma-as-if-foss, jest-as-if-foss<br/>(EE default refs only)"];
J["compile-assets pull-push-cache<br/>(master only)"];
J2["compile-assets pull-push-cache as-if-foss<br/>(EE master only)"];
K[compile-assets pull-cache];
K2["compile-assets pull-cache as-if-foss<br/>(EE default refs only)"];
U[frontend-fixtures];
U2["frontend-fixtures-as-if-foss<br/>(EE default refs only)"];
V["webpack-dev-server, static-analysis"];
M[coverage];
N["pages (master only)"];
Q[package-and-qa];
S["RSpec<br/>(e.g. rspec unit pg9)"]
T[retrieve-tests-metadata];
QA["qa:internal, qa:selectors"];
QA2["qa:internal-as-if-foss, qa:selectors-as-if-foss<br/>(EE default refs only)"];
X["docs lint, code_quality, sast, dependency_scanning, danger-review"];
subgraph "`prepare` stage"
A
......@@ -159,17 +166,26 @@ subgraph "`prepare` stage"
T
end
subgraph "`fixture` stage"
U -.-> |needs and depends on| A;
U -.-> |needs and depends on| K;
U2 -.-> |needs and depends on| A;
U2 -.-> |needs and depends on| K2;
end
subgraph "`test` stage"
D -.-> |needs| A;
I -.-> |needs and depends on| A;
I -.-> |needs and depends on| K;
I2 -.-> |needs and depends on| A;
I2 -.-> |needs and depends on| K;
I -.-> |needs and depends on| U;
I2 -.-> |needs and depends on| U2;
L -.-> |needs and depends on| A;
S -.-> |needs and depends on| A;
S -.-> |needs and depends on| K;
S -.-> |needs and depends on| T;
L["db:*, gitlab:setup, graphql-docs-verify, downtime_check"] -.-> |needs| A;
V -.-> |needs and depends on| K;
X -.-> |needs| T;
QA -.-> |needs| T;
QA2 -.-> |needs| T;
end
subgraph "`post-test` stage"
......
......@@ -53,8 +53,8 @@ Here's a list of the AWS services we will use, with links to pricing information
[Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/).
- **S3**: We will use S3 to store backups, artifacts, LFS objects, etc. See the
[Amazon S3 pricing](https://aws.amazon.com/s3/pricing/).
- **ALB**: An Application Load Balancer will be used to route requests to the
GitLab instance. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/).
- **ELB**: A Classic Load Balancer will be used to route requests to the
GitLab instances. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/).
- **RDS**: An Amazon Relational Database Service using PostgreSQL will be used
to provide a High Availability database configuration. See the
[Amazon RDS pricing](https://aws.amazon.com/rds/postgresql/pricing/).
......@@ -291,27 +291,30 @@ and add a custom TCP rule for port `6379` accessible within itself.
## Load Balancer
On the EC2 dashboard, look for Load Balancer on the left column:
On the EC2 dashboard, look for Load Balancer in the left navigation bar:
1. Click the **Create Load Balancer** button.
1. Choose the Application Load Balancer.
1. Give it a name (`gitlab-loadbalancer`) and set the scheme to "internet-facing".
1. In the "Listeners" section, make sure it has HTTP and HTTPS.
1. In the "Availability Zones" section, select the `gitlab-vpc` we have created
and associate the **public subnets**.
1. Click **Configure Security Settings** to go to the next section to
select the TLS certificate. When done, go to the next step.
1. In the "Security Groups" section, create a new one by giving it a name
(`gitlab-loadbalancer-sec-group`) and allow both HTTP ad HTTPS traffic
1. Choose the **Classic Load Balancer**.
1. Give it a name (`gitlab-loadbalancer`) and for the **Create LB Inside** option, select `gitlab-vpc` from the dropdown menu.
1. In the **Listeners** section, set HTTP port 80, HTTPS port 443, and TCP port 22 for both load balancer and instance protocols and ports.
1. In the **Select Subnets** section, select both public subnets from the list.
1. Click **Assign Security Groups** and select **Create a new security group**, give it a name
(`gitlab-loadbalancer-sec-group`) and description, and allow both HTTP and HTTPS traffic
from anywhere (`0.0.0.0/0, ::/0`).
1. In the next step, configure the routing and select an existing target group
(`gitlab-public`). The Load Balancer Health will allow us to indicate where to
ping and what makes up a healthy or unhealthy instance.
1. Leave the "Register Targets" section as is, and finally review the settings
and create the ELB.
1. Click **Configure Security Settings** and select an SSL/TLS certificate from ACM or upload a certificate to IAM.
1. Click **Configure Health Check** and set up a health check for your EC2 instances.
1. For **Ping Protocol**, select HTTP.
1. For **Ping Port**, enter 80.
1. For **Ping Path**, enter `/explore`. (We use `/explore` as it's a public endpoint that does
not require authorization.)
1. Keep the default **Advanced Details** or adjust them according to your needs.
1. For now, don't click **Add EC2 Instances**, as we don't have any instances to add yet. Come back
to your load balancer after creating your GitLab instances and add them.
1. Click **Add Tags** and add any tags you need.
1. Click **Review and Create**, review all your settings, and click **Create** if you're happy.
After the Load Balancer is up and running, you can revisit your Security
Groups to refine the access only through the ELB and any other requirement
Groups to refine the access only through the ELB and any other requirements
you might have.
## Deploying GitLab inside an auto scaling group
......
......@@ -307,6 +307,7 @@ DAST can be [configured](#customizing-the-dast-settings) using environment varia
| `DAST_TARGET_AVAILABILITY_TIMEOUT` | no | Time limit in seconds to wait for target availability. Scan is attempted nevertheless if it runs out. Integer. Defaults to `60`. |
| `DAST_FULL_SCAN_ENABLED` | no | Switches the tool to execute [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | no | Requires [domain validation](#domain-validation) when running DAST full scans. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
| `DAST_AUTO_UPDATE_ADDONS` | no | Set to `false` to pin the versions of ZAProxy add-ons to those provided with the DAST image. Defaults to `true`. |
### DAST command-line options
......
......@@ -64,7 +64,7 @@ We intend to add a similar SSO requirement for [Git and API activity](https://gi
#### Group-managed accounts
[Introduced in GitLab 12.1](https://gitlab.com/groups/gitlab-org/-/epics/709).
> [Introduced in GitLab 12.1](https://gitlab.com/groups/gitlab-org/-/epics/709).
When SSO is being enforced, groups can enable an additional level of protection by enforcing the creation of dedicated user accounts to access the group.
......@@ -95,6 +95,14 @@ To access the Credentials inventory of a group, navigate to **{shield}** **Secur
This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
##### Outer forks restriction for Group-managed accounts
> [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/issues/34648)
Groups with enabled group-managed accounts can allow or disallow forking of projects outside of root group
by using separate toggle. If forking is disallowed any project of given root group or its subgroups can be forked to
a subgroup of the same root group only.
#### Assertions
When using group-managed accounts, the following user details need to be passed to GitLab as SAML
......
......@@ -13,7 +13,7 @@ The Packages feature allows GitLab to act as a repository for the following:
| [Conan Repository](conan_repository/index.md) **(PREMIUM)** | The GitLab Conan Repository enables every project in GitLab to have its own space to store [Conan](https://conan.io/) packages. | 12.6+ |
| [Maven Repository](maven_repository/index.md) **(PREMIUM)** | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ |
| [NPM Registry](npm_registry/index.md) **(PREMIUM)** | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ |
| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | *PLANNED* The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
## Suggested contributions
......
......@@ -280,7 +280,7 @@ page.
To work with NPM commands within [GitLab CI](./../../../ci/README.md), you can use
`CI_JOB_TOKEN` in place of the personal access token in your commands.
A simple example `gitlab-ci.yml` file for publishing NPM packages:
A simple example `.gitlab-ci.yml` file for publishing NPM packages:
```yml
image: node:latest
......
......@@ -2,18 +2,21 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/20050) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8.
CAUTION: **Work in progress**
This feature is in development, sections on uploading and installing packages will be coming soon, please follow along and help us make sure we're building the right solution for you in the [NuGet issue](https://gitlab.com/gitlab-org/gitlab/issues/20050).
With the GitLab NuGet Repository, every project can have its own space to store NuGet packages.
The GitLab NuGet Repository works with either [nuget CLI](https://www.nuget.org/) or [Visual Studio](https://visualstudio.microsoft.com/vs/).
The GitLab NuGet Repository works with:
- [NuGet CLI](https://docs.microsoft.com/en-us/nuget/reference/nuget-exe-cli-reference)
- [.NET Core CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/)
- [Visual Studio](https://visualstudio.microsoft.com/vs/)
## Setting up your development environment
You will need [nuget CLI](https://www.nuget.org/) 5.2 or above. Previous versions have not been tested against the GitLab NuGet Repository and might not work. You can install it by visiting the [downloads page](https://www.nuget.org/downloads).
You will need [NuGet CLI 5.2 or later](https://www.nuget.org/downloads). Earlier versions have not been tested
against the GitLab NuGet Repository and might not work. If you have [Visual Studio](https://visualstudio.microsoft.com/vs/),
NuGet CLI is probably already installed.
If you have [Visual Studio](https://visualstudio.microsoft.com/vs/), [nuget CLI](https://www.nuget.org/) is probably already installed.
Alternatively, you can use [.NET SDK 3.0 or later](https://dotnet.microsoft.com/download/dotnet-core/3.0), which installs NuGet CLI.
You can confirm that [nuget CLI](https://www.nuget.org/) is properly installed with:
......@@ -37,7 +40,7 @@ Available commands:
NOTE: **Note:**
This option is available only if your GitLab administrator has
[enabled support for the NuGet Repository](../../../administration/packages/index.md).**(PREMIUM ONLY)**
[enabled support for the Package Registry](../../../administration/packages/index.md). **(PREMIUM ONLY)**
After the NuGet Repository is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
......@@ -48,7 +51,7 @@ by default. To enable it for existing projects, or if you want to disable it:
You should then be able to see the **Packages** section on the left sidebar.
## Adding the GitLab NuGet Repository as a source to nuget
## Adding the GitLab NuGet Repository as a source to NuGet
You will need the following:
......@@ -57,23 +60,23 @@ You will need the following:
- A suitable name for your source.
- Your project ID which can be found on the home page of your project.
You can now add a new source to nuget either using [nuget CLI](https://www.nuget.org/) or [Visual Studio](https://visualstudio.microsoft.com/vs/).
You can now add a new source to NuGet with:
- [NuGet CLI](#add-nuget-repository-source-with-nuget-cli)
- [Visual Studio](#add-nuget-repository-source-with-visual-studio).
- [.NET CLI](#add-nuget-repository-source-with-net-cli)
### Using nuget CLI
### Add NuGet Repository source with NuGet CLI
To add the GitLab NuGet Repository as a source with `nuget`:
```shell
nuget source Add -Name <source_name> -Source "https://example.gitlab.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_token>
nuget source Add -Name <source_name> -Source "https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_personal_access_token>
```
Replace:
Where:
- `<source_name>` with your desired source name.
- `<your_project_id>` with your project ID.
- `<gitlab-username>` with your GitLab username.
- `<gitlab-token>` with your personal access token.
- `example.gitlab.com` with the URL of the GitLab instance you're using.
- `<source_name>` is your desired source name.
For example:
......@@ -81,7 +84,7 @@ For example:
nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/10/packages/nuget/index.json" -UserName carol -Password 12345678asdf
```
### Using Visual Studio
### Add NuGet Repository source with Visual Studio
1. Open [Visual Studio](https://visualstudio.microsoft.com/vs/).
1. Open the **FILE > OPTIONS** (Windows) or **Visual Studio > Preferences** (Mac OS).
......@@ -102,3 +105,109 @@ nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/1
![Visual Studio NuGet source added](img/visual_studio_nuget_source_added.png)
In case of any warning, please make sure that the **Location**, **Username** and **Password** are correct.
### Add NuGet Repository source with .NET CLI
To add the GitLab NuGet Repository as a source for .NET, create a file named `nuget.config` in the root of your project with the following content:
```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="gitlab" value="https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<gitlab>
<add key="Username" value="<gitlab_username>" />
<add key="ClearTextPassword" value="<gitlab_personal_access_token>" />
</gitlab>
</packageSourceCredentials>
</configuration>
```
## Uploading packages
When uploading packages, note that:
- The maximum allowed size is 50 Megabytes.
- If you upload the same package with the same version multiple times, each consecutive upload
is saved as a separate file. When installing a package, GitLab will serve the most recent file.
- When uploading packages to GitLab, they will not be displayed in the packages UI of your project
immediately. It can take up to 10 minutes to process a package.
### Upload packages with NuGet CLI
This section assumes that your project is properly built and you already [created a NuGet package with NuGet CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package).
Upload your package using the following command:
```shell
nuget push <package_file> -Source <source_name>
```
Where:
- `<package_file>` is your package filename, ending in `.nupkg`.
- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget).
### Upload packages with .NET CLI
This section assumes that your project is properly built and you already [created a NuGet package with .NET CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli.).
Upload your package using the following command:
```shell
dotnet nuget push <package_file> --source <source_name>
```
Where:
- `<package_file>` is your package filename, ending in `.nupkg`.
- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget).
For example:
```shell
dotnet nuget push MyPackage.1.0.0.nupkg --source gitlab
```
## Install packages
### Install a package with NuGet CLI
CAUTION: **Warning:**
By default, `nuget` checks the official source at `nuget.org` first. If you have a package in the
GitLab NuGet Repository with the same name as a package at `nuget.org`, you must specify the source
name or the wrong package will be installed.
Install the latest version of a package using the following command:
```shell
nuget install <package_id> -OutputDirectory <output_directory> \
-Version <package_version> \
-Source <source_name>
```
Where:
- `<package_id>` is the package id.
- `<output_directory>` is the output directory, where the package will be installed.
- `<package_version>` (Optional) is the package version.
- `<source_name>` (Optional) is the source name.
### Install a package with .NET CLI
CAUTION: **Warning:**
If you have a package in the GitLab NuGet Repository with the same name as a package at a different source,
you should verify the order in which `dotnet` checks sources during install. This is defined in the
`nuget.config` file.
Install the latest version of a package using the following command:
```shell
dotnet add package <package_id> \
-v <package_version>
```
Where:
- `<package_id>` is the package id.
- `<package_version>` (Optional) is the package version.
......@@ -483,34 +483,29 @@ A `serverless.yml` file is not required when deploying serverless applications.
With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to
**CI/CD > Pipelines** and click the most recent pipeline.
### Obtain the URL for the Knative deployment
### Function details
Go to the **CI/CD > Pipelines** and click on the pipeline that deployed your app. Once all the stages of the pipeline finish, click the **deploy** stage.
Go to the **Operations > Serverless** page to see the final URL of your functions.
![deploy stage](img/deploy-stage.png)
![function_details](img/function-list_v12_7.png)
The output will look like this:
### Invocation metrics
```shell
Running with gitlab-runner 12.1.0-rc1 (6da35412)
on prm-com-gitlab-org ae3bfce3
Using Docker executor with image registry.gitlab.com/gitlab-org/gitlabktl:latest ...
Running on runner-ae3bfc-concurrent-0 via runner-ae3bfc ...
Fetching changes...
Authenticating with credentials from job payload (GitLab Registry)
$ /usr/bin/gitlabktl application deploy
Welcome to gitlabktl tool
time="2019-07-15T10:51:07Z" level=info msg="deploying registry credentials"
Creating app-hello function
Waiting for app-hello ready state
Service app-hello URL: http://app-hello.serverless.example.com
Job succeeded
```
On the same page as above, click on one of the function
rows to bring up the function details page.
![function_details](img/function-details-loaded.png)
The pod count will give you the number of pods running the serverless function instances on a given cluster.
The second to last line, labeled **Service domain** contains the URL for the
deployment. Copy and paste the domain into your browser to see the app live.
For the Knative function invocations to appear,
[Prometheus must be installed](../index.md#installing-applications).
![knative app](img/knative-app.png)
Once Prometheus is installed, a message may appear indicating that the metrics data _is
loading or is not available at this time._ It will appear upon the first access of the
page, but should go away after a few seconds. If the message does not disappear, then it
is possible that GitLab is unable to connect to the Prometheus instance running on the
cluster.
## Configuring logging
......@@ -559,26 +554,6 @@ Or:
1. Click on **Discover**, then select `filebeat-*` from the dropdown on the left.
1. Enter `kubernetes.container.name:"queue-proxy" AND message:/httpRequest/` into the search box.
## Function details
Go to the **Operations > Serverless** page and click on one of the function
rows to bring up the function details page.
![function_details](img/function-details-loaded.png)
The pod count will give you the number of pods running the serverless function instances on a given cluster.
### Prometheus support
For the Knative function invocations to appear,
[Prometheus must be installed](../index.md#installing-applications).
Once Prometheus is installed, a message may appear indicating that the metrics data _is
loading or is not available at this time._ It will appear upon the first access of the
page, but should go away after a few seconds. If the message does not disappear, then it
is possible that GitLab is unable to connect to the Prometheus instance running on the
cluster.
## Enabling TLS for Knative services
By default, a GitLab serverless deployment will be served over `http`. In order to serve over `https` you
......
......@@ -49,9 +49,9 @@ Docker pulls and pushes and re-run any CI pipelines to retrieve any build artifa
## Migrating between two self-hosted GitLab instances
The best method for migrating a project from one GitLab instance to another,
The best method for migrating from one GitLab instance to another,
perhaps from an old server to a new server for example, is to
[back up the project](../../../raketasks/backup_restore.md),
[back up the instance](../../../raketasks/backup_restore.md),
then restore it on the new server.
In the event of merging two GitLab instances together (for example, both instances have existing data on them and one can't be wiped),
......
<script>
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { PROJECTS_PER_PAGE, DEFAULT_DAYS_IN_PAST } from '../constants';
import { PROJECTS_PER_PAGE } from '../constants';
import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter.vue';
import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue';
import Scatterplot from '../../shared/components/scatterplot.vue';
......@@ -56,7 +55,7 @@ export default {
'isCreatingCustomStage',
'isEditingCustomStage',
'selectedGroup',
'selectedProjectIds',
'selectedProjects',
'selectedStage',
'stages',
'summary',
......@@ -77,6 +76,7 @@ export default {
'tasksByTypeChartData',
'durationChartMedianData',
'activeStages',
'selectedProjectIds',
]),
shouldRenderEmptyState() {
return !this.selectedGroup;
......@@ -121,7 +121,6 @@ export default {
},
},
mounted() {
this.initDateRange();
this.setFeatureFlags({
hasDurationChart: this.glFeatures.cycleAnalyticsScatterplotEnabled,
hasDurationChartMedian: this.glFeatures.cycleAnalyticsScatterplotMedianEnabled,
......@@ -154,8 +153,7 @@ export default {
this.fetchCycleAnalyticsData();
},
onProjectsSelect(projects) {
const projectIds = projects.map(value => value.id);
this.setSelectedProjects(projectIds);
this.setSelectedProjects(projects);
this.fetchCycleAnalyticsData();
},
onStageSelect(stage) {
......@@ -169,11 +167,6 @@ export default {
onShowEditStageForm(initData = {}) {
this.showEditCustomStageForm(initData);
},
initDateRange() {
const endDate = new Date(Date.now());
const startDate = getDateInPast(endDate, DEFAULT_DAYS_IN_PAST);
this.setDateRange({ skipFetch: true, startDate, endDate });
},
onCreateCustomStage(data) {
this.createCustomStage(data);
},
......@@ -215,6 +208,7 @@ export default {
<groups-dropdown-filter
class="js-groups-dropdown-filter dropdown-select"
:query-params="$options.groupsQueryParams"
:default-group="selectedGroup"
@selected="onGroupSelect"
/>
<projects-dropdown-filter
......@@ -224,6 +218,7 @@ export default {
:group-id="selectedGroup.id"
:query-params="$options.projectsQueryParams"
:multi-select="$options.multiProjectSelect"
:default-projects="selectedProjects"
@selected="onProjectsSelect"
/>
<div
......
import Vue from 'vue';
import CycleAnalytics from './components/base.vue';
import createStore from './store';
import { buildCycleAnalyticsInitialData } from '../shared/utils';
export default () => {
const el = document.querySelector('#js-cycle-analytics-app');
const { emptyStateSvgPath, noDataSvgPath, noAccessSvgPath } = el.dataset;
const initialData = buildCycleAnalyticsInitialData(el.dataset);
const store = createStore();
store.dispatch('initializeCycleAnalytics', initialData);
return new Vue({
el,
name: 'CycleAnalyticsApp',
store: createStore(),
components: {
CycleAnalytics,
},
store,
render: createElement =>
createElement(CycleAnalytics, {
props: {
......
import dateFormat from 'dateformat';
import Api from 'ee/api';
import { getDayDifference, getDateInPast } from '~/lib/utils/datetime_utility';
import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import createFlash, { hideFlash } from '~/flash';
import { __, sprintf } from '~/locale';
import httpStatus from '~/lib/utils/http_status';
import * as types from './mutation_types';
import { dateFormats } from '../../shared/constants';
import { toYmd } from '../../shared/utils';
const removeError = () => {
const flashEl = document.querySelector('.flash-alert');
......@@ -29,17 +32,50 @@ const isStageNameExistsError = ({ status, errors }) => {
return false;
};
const updateUrlParams = (
{ getters: { currentGroupPath, selectedProjectIds } },
additionalParams = {},
) => {
historyPushState(
setUrlParams(
{
group_id: currentGroupPath,
'project_ids[]': selectedProjectIds,
...additionalParams,
},
window.location.href,
true,
),
);
};
export const setFeatureFlags = ({ commit }, featureFlags) =>
commit(types.SET_FEATURE_FLAGS, featureFlags);
export const setSelectedGroup = ({ commit }, group) => commit(types.SET_SELECTED_GROUP, group);
export const setSelectedProjects = ({ commit }, projectIds) =>
commit(types.SET_SELECTED_PROJECTS, projectIds);
export const setSelectedGroup = ({ commit, getters }, group) => {
commit(types.SET_SELECTED_GROUP, group);
updateUrlParams({ getters });
};
export const setSelectedProjects = ({ commit, getters }, projects) => {
commit(types.SET_SELECTED_PROJECTS, projects);
updateUrlParams({ getters });
};
export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
export const setDateRange = ({ commit, dispatch }, { skipFetch = false, startDate, endDate }) => {
export const setDateRange = (
{ commit, dispatch, getters },
{ skipFetch = false, startDate, endDate },
) => {
commit(types.SET_DATE_RANGE, { startDate, endDate });
updateUrlParams(
{ getters },
{
created_after: toYmd(startDate),
created_before: toYmd(endDate),
},
);
if (skipFetch) return false;
......@@ -548,3 +584,17 @@ export const setTasksByTypeFilters = ({ dispatch, commit }, data) => {
commit(types.SET_TASKS_BY_TYPE_FILTERS, data);
dispatch('fetchTasksByTypeData');
};
export const initializeCycleAnalyticsSuccess = ({ commit }) =>
commit(types.INITIALIZE_CYCLE_ANALYTICS_SUCCESS);
export const initializeCycleAnalytics = ({ dispatch, commit }, initialData = {}) => {
commit(types.INITIALIZE_CYCLE_ANALYTICS, initialData);
if (initialData?.group?.fullPath) {
return dispatch('fetchCycleAnalyticsData').then(() =>
dispatch('initializeCycleAnalyticsSuccess'),
);
}
return dispatch('initializeCycleAnalyticsSuccess');
};
......@@ -8,12 +8,11 @@ export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDE
export const currentGroupPath = ({ selectedGroup }) =>
selectedGroup && selectedGroup.fullPath ? selectedGroup.fullPath : null;
export const cycleAnalyticsRequestParams = ({
startDate = null,
endDate = null,
selectedProjectIds = [],
}) => ({
project_ids: selectedProjectIds,
export const selectedProjectIds = ({ selectedProjects }) =>
selectedProjects.length ? selectedProjects.map(({ id }) => id) : [];
export const cycleAnalyticsRequestParams = ({ startDate = null, endDate = null }, getters) => ({
project_ids: getters.selectedProjectIds,
created_after: startDate ? dateFormat(startDate, dateFormats.isoDate) : null,
created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null,
});
......
......@@ -63,3 +63,6 @@ export const RECEIVE_DURATION_MEDIAN_DATA_SUCCESS = 'RECEIVE_DURATION_MEDIAN_DAT
export const RECEIVE_DURATION_MEDIAN_DATA_ERROR = 'RECEIVE_DURATION_MEDIAN_DATA_ERROR';
export const SET_TASKS_BY_TYPE_FILTERS = 'SET_TASKS_BY_TYPE_FILTERS';
export const INITIALIZE_CYCLE_ANALYTICS = 'INITIALIZE_CYCLE_ANALYTICS';
export const INITIALIZE_CYCLE_ANALYTICS_SUCCESS = 'INITIALIZE_CYCLE_ANALYTICS_SUCCESS';
......@@ -9,10 +9,10 @@ export default {
},
[types.SET_SELECTED_GROUP](state, group) {
state.selectedGroup = convertObjectPropsToCamelCase(group, { deep: true });
state.selectedProjectIds = [];
state.selectedProjects = [];
},
[types.SET_SELECTED_PROJECTS](state, projectIds) {
state.selectedProjectIds = projectIds;
[types.SET_SELECTED_PROJECTS](state, projects) {
state.selectedProjects = projects;
},
[types.SET_SELECTED_STAGE](state, rawData) {
state.selectedStage = convertObjectPropsToCamelCase(rawData);
......@@ -236,4 +236,22 @@ export default {
}
state.tasksByType = { ...tasksByTypeRest, labelIds, ...updatedFilter };
},
[types.INITIALIZE_CYCLE_ANALYTICS](
state,
{
group: selectedGroup = null,
createdAfter: startDate = null,
createdBefore: endDate = null,
selectedProjects = [],
} = {},
) {
state.isLoading = true;
state.selectedGroup = selectedGroup;
state.selectedProjects = selectedProjects;
state.startDate = startDate;
state.endDate = endDate;
},
[types.INITIALIZE_CYCLE_ANALYTICS_SUCCESS](state) {
state.isLoading = false;
},
};
......@@ -20,7 +20,7 @@ export default () => ({
isEditingCustomStage: false,
selectedGroup: null,
selectedProjectIds: [],
selectedProjects: [],
selectedStage: null,
currentStageEvents: [],
......
......@@ -5,13 +5,9 @@ import FilterDropdowns from './components/filter_dropdowns.vue';
import DateRange from '../shared/components/daterange.vue';
import ProductivityAnalyticsApp from './components/app.vue';
import FilteredSearchProductivityAnalytics from './filtered_search_productivity_analytics';
import {
getLabelsEndpoint,
getMilestonesEndpoint,
buildGroupFromDataset,
buildProjectFromDataset,
} from './utils';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getLabelsEndpoint, getMilestonesEndpoint } from './utils';
import { buildGroupFromDataset, buildProjectFromDataset } from '../shared/utils';
export default () => {
const container = document.getElementById('js-productivity-analytics');
......
......@@ -186,45 +186,3 @@ export const getMedianLineData = (data, startDate, endDate, daysOffset) => {
return result;
};
/**
* Creates a group object from a dataset. Returns null if no groupId is present.
*
* @param {Object} dataset - The container's dataset
* @returns {Object} - A group object
*/
export const buildGroupFromDataset = dataset => {
const { groupId, groupName, groupFullPath, groupAvatarUrl } = dataset;
if (groupId) {
return {
id: Number(groupId),
name: groupName,
full_path: groupFullPath,
avatar_url: groupAvatarUrl,
};
}
return null;
};
/**
* Creates a project object from a dataset. Returns null if no projectId is present.
*
* @param {Object} dataset - The container's dataset
* @returns {Object} - A project object
*/
export const buildProjectFromDataset = dataset => {
const { projectId, projectName, projectPathWithNamespace, projectAvatarUrl } = dataset;
if (projectId) {
return {
id: Number(projectId),
name: projectName,
path_with_namespace: projectPathWithNamespace,
avatar_url: projectAvatarUrl,
};
}
return null;
};
......@@ -74,8 +74,8 @@ export default {
:default-min-date="minDate"
:max-date-range="maxDateRange"
theme="animate-picker"
start-picker-class="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-2 mb-2 mb-md-0"
end-picker-class="d-flex flex-column flex-lg-row align-items-lg-center"
start-picker-class="js-daterange-picker-from d-flex flex-column flex-lg-row align-items-lg-center mr-lg-2 mb-2 mb-md-0"
end-picker-class="js-daterange-picker-to d-flex flex-column flex-lg-row align-items-lg-center"
/>
<div
v-if="maxDateRange"
......
import dateFormat from 'dateformat';
import { dateFormats } from './constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export const toYmd = date => dateFormat(date, dateFormats.isoDate);
......@@ -8,3 +9,83 @@ export default {
};
export const formattedDate = d => dateFormat(d, dateFormats.defaultDate);
/**
* Creates a group object from a dataset. Returns null if no groupId is present.
*
* @param {Object} dataset - The container's dataset
* @returns {Object} - A group object
*/
export const buildGroupFromDataset = dataset => {
const { groupId, groupName, groupFullPath, groupAvatarUrl } = dataset;
if (groupId) {
return {
id: Number(groupId),
name: groupName,
full_path: groupFullPath,
avatar_url: groupAvatarUrl,
};
}
return null;
};
/**
* Creates a project object from a dataset. Returns null if no projectId is present.
*
* @param {Object} dataset - The container's dataset
* @returns {Object} - A project object
*/
export const buildProjectFromDataset = dataset => {
const { projectId, projectName, projectPathWithNamespace, projectAvatarUrl } = dataset;
if (projectId) {
return {
id: Number(projectId),
name: projectName,
path_with_namespace: projectPathWithNamespace,
avatar_url: projectAvatarUrl,
};
}
return null;
};
/**
* Creates an array of project objects from a json string. Returns null if no projects are present.
*
* @param {String} data - JSON encoded array of projects
* @returns {Array} - An array of project objects
*/
const buildProjectsFromJSON = (projects = []) => {
if (!projects.length) return [];
return JSON.parse(projects);
};
/**
* Builds the initial data object for cycle analytics with data loaded from the backend
*
* @param {Object} dataset - dataset object paseed to the frontend via data-* properties
* @returns {Object} - The initial data to load the app with
*/
export const buildCycleAnalyticsInitialData = ({
groupId = null,
createdBefore = null,
createdAfter = null,
projects = null,
groupName = null,
groupFullPath = null,
groupAvatarUrl = null,
} = {}) => ({
group: groupId
? convertObjectPropsToCamelCase(
buildGroupFromDataset({ groupId, groupName, groupFullPath, groupAvatarUrl }),
)
: null,
createdBefore: createdBefore ? new Date(createdBefore) : null,
createdAfter: createdAfter ? new Date(createdAfter) : null,
selectedProjects: projects
? buildProjectsFromJSON(projects).map(convertObjectPropsToCamelCase)
: [],
});
......@@ -5,7 +5,7 @@ import syncWithRouter from 'ee/security_dashboard/store/plugins/sync_with_router
import createStore from 'ee/security_dashboard/store';
import InstanceSecurityDashboard from 'ee/security_dashboard/components/instance_security_dashboard.vue';
if (gon.features && gon.features.securityDashboard) {
if (gon.features && gon.features.instanceSecurityDashboard) {
document.addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('#js-security');
const {
......
......@@ -68,7 +68,7 @@ module Analytics
end
def request_params
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(data_collector_params)
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(data_collector_params, current_user: current_user)
end
def data_collector
......
......@@ -36,7 +36,7 @@ module Analytics
end
def request_params
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(allowed_params)
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(allowed_params, current_user: current_user)
end
def allowed_params
......
......@@ -18,7 +18,7 @@ class Analytics::CycleAnalyticsController < Analytics::ApplicationController
before_action :build_request_params, only: :show
def build_request_params
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(allowed_params.merge(group: @group))
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(allowed_params.merge(group: @group), current_user: current_user)
end
def allowed_params
......
......@@ -6,13 +6,13 @@ module Security
before_action :check_feature_enabled!
before_action do
push_frontend_feature_flag(:security_dashboard, default_enabled: true)
push_frontend_feature_flag(:instance_security_dashboard, default_enabled: true)
end
protected
def check_feature_enabled!
render_404 unless Feature.enabled?(:security_dashboard, default_enabled: true)
render_404 unless Feature.enabled?(:instance_security_dashboard, default_enabled: true)
end
def vulnerable
......
# frozen_string_literal: true
module EE
module ForkTargetsFinder
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
override :execute
# rubocop: disable CodeReuse/ActiveRecord
def execute
targets = super
root_group = project.group&.root_ancestor
return targets unless root_group&.saml_provider
if root_group.saml_provider.prohibited_outer_forks?
targets = targets.where(id: root_group.self_and_descendants)
end
targets
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
......@@ -64,7 +64,7 @@ module EE
def security_dashboard_available?
security_dashboard = InstanceSecurityDashboard.new(current_user)
::Feature.enabled?(:security_dashboard, default_enabled: true) &&
::Feature.enabled?(:instance_security_dashboard, default_enabled: true) &&
security_dashboard.feature_available?(:security_dashboard) &&
can?(current_user, :read_instance_security_dashboard, security_dashboard)
end
......
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