summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml4
-rw-r--r--.gitlab/issue_templates/Database Reviewer.md34
-rw-r--r--Dangerfile1
-rw-r--r--PROCESS.md43
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue14
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue1
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js10
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue31
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue8
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue5
-rw-r--r--app/assets/javascripts/notes/stores/getters.js6
-rw-r--r--app/assets/javascripts/performance_bar/components/detailed_metric.vue4
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue14
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js3
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/groups/clusters_controller.rb9
-rw-r--r--app/controllers/projects/application_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb7
-rw-r--r--app/controllers/projects/merge_requests/content_controller.rb22
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/graphql/mutations/.keep0
-rw-r--r--app/graphql/mutations/award_emojis/add.rb25
-rw-r--r--app/graphql/mutations/award_emojis/base.rb41
-rw-r--r--app/graphql/mutations/award_emojis/remove.rb33
-rw-r--r--app/graphql/mutations/award_emojis/toggle.rb40
-rw-r--r--app/graphql/mutations/base_mutation.rb7
-rw-r--r--app/graphql/types/award_emojis/award_emoji_type.rb46
-rw-r--r--app/graphql/types/mutation_type.rb3
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/models/board.rb5
-rw-r--r--app/models/clusters/instance.rb4
-rw-r--r--app/models/concerns/deployment_platform.rb16
-rw-r--r--app/models/concerns/relative_positioning.rb4
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/list.rb1
-rw-r--r--app/models/postgresql/replication_slot.rb2
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/policies/award_emoji_policy.rb11
-rw-r--r--app/policies/clusters/instance_policy.rb3
-rw-r--r--app/presenters/award_emoji_presenter.rb27
-rw-r--r--app/serializers/merge_request_widget_entity.rb8
-rw-r--r--app/services/issuable_base_service.rb13
-rw-r--r--app/services/users/update_service.rb10
-rw-r--r--app/views/projects/_merge_request_settings_description_text.html.haml1
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/environments/show.html.haml28
-rw-r--r--app/views/projects/tree/_tree_header.html.haml1
-rw-r--r--app/views/shared/projects/_list.html.haml2
-rw-r--r--changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml5
-rw-r--r--changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml5
-rw-r--r--changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml6
-rw-r--r--changelogs/unreleased/62826-graphql-emoji-mutations.yml5
-rw-r--r--changelogs/unreleased/62968-environment-details-header-border-misaligned.yml5
-rw-r--r--changelogs/unreleased/63200-reply-button-broken.yml5
-rw-r--r--changelogs/unreleased/add-metrics-dashboard-permission-check.yml5
-rw-r--r--changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml5
-rw-r--r--changelogs/unreleased/dohtaset.yml5
-rw-r--r--changelogs/unreleased/id-extract-widget-into-different-request.yml5
-rw-r--r--changelogs/unreleased/po-raw-changes-encoding.yml5
-rw-r--r--changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml5
-rw-r--r--changelogs/unreleased/sh-add-force-random-password-user-api.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml5
-rw-r--r--changelogs/unreleased/sh-avoid-loading-pipeline-status.yml5
-rw-r--r--changelogs/unreleased/sh-handle-nil-replication-lag.yml5
-rw-r--r--changelogs/unreleased/support-jsonb-default-value.yml5
-rw-r--r--config/routes/project.rb1
-rw-r--r--danger/only_documentation/Dangerfile24
-rw-r--r--doc/api/users.md56
-rw-r--r--doc/development/chatops_on_gitlabcom.md9
-rw-r--r--doc/development/contributing/issue_workflow.md2
-rw-r--r--doc/development/contributing/style_guides.md4
-rw-r--r--doc/development/documentation/site_architecture/index.md42
-rw-r--r--doc/development/documentation/styleguide.md92
-rw-r--r--doc/development/fe_guide/accessibility.md12
-rw-r--r--doc/development/feature_flags.md128
-rw-r--r--doc/development/feature_flags/controls.md123
-rw-r--r--doc/development/feature_flags/development.md131
-rw-r--r--doc/development/feature_flags/index.md12
-rw-r--r--doc/development/feature_flags/process.md130
-rw-r--r--doc/development/i18n/externalization.md45
-rw-r--r--doc/development/new_fe_guide/tips.md10
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md226
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md12
-rw-r--r--doc/gitlab-basics/command-line-commands.md16
-rw-r--r--doc/topics/application_development_platform/index.md17
-rw-r--r--doc/user/admin_area/index.md65
-rw-r--r--doc/user/project/clusters/index.md18
-rw-r--r--doc/user/project/clusters/serverless/img/function-endpoint.pngbin0 -> 42436 bytes
-rw-r--r--doc/user/project/clusters/serverless/index.md272
-rw-r--r--lib/api/boards.rb2
-rw-r--r--lib/api/boards_responses.rb2
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/group_boards.rb2
-rw-r--r--lib/api/users.rb1
-rw-r--r--lib/gitlab/database/migration_helpers.rb3
-rw-r--r--lib/gitlab/git/raw_diff_change.rb4
-rw-r--r--lib/gitlab/graphql/copy_field_description.rb21
-rw-r--r--lib/gitlab/graphql/errors.rb1
-rw-r--r--lib/gitlab/metrics/dashboard/base_service.rb8
-rw-r--r--locale/gitlab.pot21
-rw-r--r--spec/controllers/admin/clusters/applications_controller_spec.rb10
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb76
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb80
-rw-r--r--spec/controllers/projects/merge_requests/content_controller_spec.rb60
-rw-r--r--spec/controllers/search_controller_spec.rb17
-rw-r--r--spec/factories/award_emoji.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb4
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json3
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js38
-rw-r--r--spec/graphql/types/award_emojis/award_emoji_type_spec.rb11
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js15
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js20
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js3
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js2
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/copy_field_description_spec.rb21
-rw-r--r--spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb11
-rw-r--r--spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb10
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb10
-rw-r--r--spec/models/group_spec.rb29
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb8
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/repository_spec.rb13
-rw-r--r--spec/policies/award_emoji_policy_spec.rb54
-rw-r--r--spec/policies/clusters/instance_policy_spec.rb18
-rw-r--r--spec/presenters/award_emoji_presenter_spec.rb36
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb100
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb80
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb142
-rw-r--r--spec/requests/api/users_spec.rb19
-rw-r--r--spec/support/api/boards_shared_examples.rb10
-rw-r--r--spec/support/helpers/graphql_helpers.rb5
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb8
-rw-r--r--spec/support/helpers/test_env.rb3
-rw-r--r--spec/support/shared_examples/graphql/mutation_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb62
145 files changed, 2425 insertions, 817 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 529a0de696b..6551f47d3be 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -139,7 +139,7 @@ setup-test-env:
rspec unit pg:
<<: *rspec-metadata-pg
- parallel: 20
+ parallel: 25
rspec integration pg:
<<: *rspec-metadata-pg
@@ -152,7 +152,7 @@ rspec system pg:
rspec unit pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
- parallel: 20
+ parallel: 25
rspec integration pg-10:
<<: *rspec-metadata-pg-10
diff --git a/.gitlab/issue_templates/Database Reviewer.md b/.gitlab/issue_templates/Database Reviewer.md
deleted file mode 100644
index acbaf5c1965..00000000000
--- a/.gitlab/issue_templates/Database Reviewer.md
+++ /dev/null
@@ -1,34 +0,0 @@
-#### Database Reviewer Checklist
-
-Thank you for becoming a ~database reviewer! Please work on the list
-below to complete your setup. For any question, reach out to #database
-an mention `@abrandl`.
-
-- [ ] Change issue title to include your name: `Database Reviewer Checklist: Your Name`
-- [ ] Review general [code review guide](https://docs.gitlab.com/ee/development/code_review.html)
-- [ ] Review [database review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html)
-- [ ] Familiarize with [migration helpers](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/database/migration_helpers.rb) and review usage in existing migrations
-- [ ] Read [database migration style guide](https://docs.gitlab.com/ee/development/migration_style_guide.html)
-- [ ] Familiarize with best practices in [database guides](https://docs.gitlab.com/ee/development/#database-guides)
-- [ ] Watch [Optimising Rails Database Queries: Episode 1](https://www.youtube.com/watch?v=79GurlaxhsI)
-- [ ] Read [Understanding EXPLAIN plans](https://docs.gitlab.com/ee/development/understanding_explain_plans.html)
-- [ ] Review [database best practices](https://docs.gitlab.com/ee/development/#best-practices)
-- [ ] Review how we use [database instances restored from a backup](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd) for testing and make sure you're set up to execute pipelines (check [README.md](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd/blob/master/README.md) and reach out to @abrandl since this is currently subject to being changed)
-- [ ] Get yourself added to [`@gl-database`](https://gitlab.com/groups/gl-database/-/group_members) group and respond to @-mentions to the group (reach out to any maintainer on the group to get added). You will get TODOs on gitlab.com for group mentions.
-- [ ] Make sure you have proper access to at least a read-only replica in staging and production
-- [ ] Indicate in `data/team.yml` your role as a database reviewer ([example MR](https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/19600/diffs)). Assign MR to your manager for merge.
-- [ ] Send one MR to improve the [review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html) or the [issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Database%20Reviewer.md)
-
-Note that *approving and accepting* merge requests is *restricted* to
-Database Maintainers only. As a reviewer, pass the MR to a maintainer
-for approval.
-
-You're all set! Watch out for TODOs on GitLab.com.
-
-###### Where to go for questions?
-
-Reach out to `#database` on Slack and mention `@abrandl` for any questions.
-
-cc @abrandl
-
-/label ~meta ~database
diff --git a/Dangerfile b/Dangerfile
index d0a605f8d8e..094d55e8652 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -19,4 +19,5 @@ unless helper.release_automation?
danger.import_dangerfile(path: 'danger/single_codebase')
danger.import_dangerfile(path: 'danger/gitlab_ui_wg')
danger.import_dangerfile(path: 'danger/ce_ee_vue_templates')
+ danger.import_dangerfile(path: 'danger/only_documentation')
end
diff --git a/PROCESS.md b/PROCESS.md
index 07b150ea463..22b68b0aaca 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -84,43 +84,28 @@ star, smile, etc.). Some good tips about code reviews can be found in our
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
-## Feature freeze on the 7th for the release on the 22nd
-
-The feature freeze on the 7th has been discontinued. The [transition period overview](https://gitlab.com/gitlab-org/release/docs/blob/21cbd409dd5f157fe252f254f3e897f01908abe2/general/deploy/auto-deploy-transition.md#transition)
-describes the change to this process. During the transition period, the only guarantee that
-a change will be included in the release on the 22nd is if the change has been
-deployed to GitLab.com prior to this date.
+## Feature flags
-### Feature flags
+Overview and details of feature flag processes in development of GitLab itself is described in [feature flags process documentation](https://docs.gitlab.com/ee/development/feature_flags/process.html).
-Merge requests that make changes hidden behind a feature flag, or remove an
-existing feature flag because a feature is deemed stable, may be merged (and
-picked into the stable branches) up to the 19th of the month. Such merge
-requests should have the ~"feature flag" label assigned, and don't require a
-corresponding exception request to be created.
+Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/developing.html).
-A level of common sense should be applied when deciding whether to have a feature
-behind a feature flag off or on by default.
+Getting access and how to expose the feature to users is detailed in [controlling feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/controls.html).
-The following guidelines can be applied to help make this decision:
+## Feature proposals from the 22nd to the 1st
-* If the feature is not fully ready or functioning, the feature flag should be disabled by default.
-* If the feature is ready but there are concerns about performance or impact, the feature flag should be enabled by default, but
-disabled via chatops before deployment on GitLab.com environments. If the performance concern is confirmed, the final release should have the feature flag disabled by default.
-* In most other cases, the feature flag can be enabled by default.
+To allow the Product and Engineering teams time to discuss issues that will be placed into an upcoming milestone,
+Product Managers must have their proposal for that milestone ready by the 22nd of each month.
-For more information on rolling out changes using feature flags, read [through the documentation](https://docs.gitlab.com/ee/development/rolling_out_changes_using_feature_flags.html).
+This proposal will be shared with Engineering for discussion, feedback, and planning.
+The plan for the upcoming milestone must be finalized by the 1st of the month, one week before kickoff on the 8th.
-In order to build the final package and present the feature for self-hosted
-customers, the feature flag should be removed. This should happen before the
-22nd, ideally _at least_ 2 days before. That means MRs with feature
-flags being picked at the 19th would have quite a tight schedule, so picking
-these _earlier_ is preferable.
+## Feature freeze on the 7th for the release on the 22nd
-While rare, release managers may decide to reject picking a change into a stable
-branch, even when feature flags are used. This might be necessary if the changes
-are deemed problematic, too invasive, or there simply isn't enough time to
-properly test how the changes behave on GitLab.com.
+The feature freeze on the 7th has been discontinued. [Transition period overview](https://gitlab.com/gitlab-org/release/docs/blob/21cbd409dd5f157fe252f254f3e897f01908abe2/general/deploy/auto-deploy-transition.md#transition)
+describes the change to this process. During the transition period, the only guarantee that
+a change will be included in the release on the 22nd is if the change has been
+deployed to GitLab.com prior to this date.
### Between the 1st and the 7th
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 9a3ce5174db..81773bd140e 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -1,5 +1,6 @@
<script>
import { __ } from '~/locale';
+import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
@@ -14,6 +15,7 @@ export default {
components: {
GlAreaChart,
GlChartSeriesLabel,
+ GlLink,
Icon,
},
inheritAttrs: false,
@@ -44,6 +46,10 @@ export default {
required: false,
default: () => [],
},
+ projectPath: {
+ type: String,
+ required: true,
+ },
thresholds: {
type: Array,
required: false,
@@ -55,6 +61,7 @@ export default {
tooltip: {
title: '',
content: [],
+ commitUrl: '',
isDeployment: false,
sha: '',
},
@@ -195,12 +202,13 @@ export default {
this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
this.tooltip.content = [];
params.seriesData.forEach(seriesData => {
- if (seriesData.componentSubType === graphTypes.deploymentData) {
- this.tooltip.isDeployment = true;
+ this.tooltip.isDeployment = seriesData.componentSubType === graphTypes.deploymentData;
+ if (this.tooltip.isDeployment) {
const [deploy] = this.recentDeployments.filter(
deployment => deployment.createdAt === seriesData.value[0],
);
this.tooltip.sha = deploy.sha.substring(0, 8);
+ this.tooltip.commitUrl = deploy.commitUrl;
} else {
const { seriesName, color } = seriesData;
// seriesData.value contains the chart's [x, y] value pair
@@ -259,7 +267,7 @@ export default {
</template>
<div slot="tooltipContent" class="d-flex align-items-center">
<icon name="commit" class="mr-2" />
- {{ tooltip.sha }}
+ <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link>
</div>
</template>
<template v-else>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 23687c54fd3..2cbda8ea05d 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -359,6 +359,7 @@ export default {
<monitor-area-chart
v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
:key="graphIndex"
+ :project-path="projectPath"
:graph-data="graphData"
:deployment-data="deploymentData"
:thresholds="getGraphAlertValues(graphData.queries)"
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index edbcf84b342..97d149e9ad5 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -8,10 +8,12 @@ export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
if (el && el.dataset) {
- store.dispatch('monitoringDashboard/setFeatureFlags', {
- prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
- multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
- });
+ if (gon.features) {
+ store.dispatch('monitoringDashboard/setFeatureFlags', {
+ prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
+ multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
+ });
+ }
const [currentDashboard] = getParameterValues('dashboard');
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
index 22cca756ef6..1357a5268d6 100644
--- a/app/assets/javascripts/notes/components/discussion_actions.vue
+++ b/app/assets/javascripts/notes/components/discussion_actions.vue
@@ -39,20 +39,27 @@ export default {
</script>
<template>
- <div class="discussion-with-resolve-btn">
+ <div class="discussion-with-resolve-btn clearfix">
<reply-placeholder class="qa-discussion-reply" @onClick="$emit('showReplyForm')" />
- <resolve-discussion-button
- v-if="discussion.resolvable"
- :is-resolving="isResolving"
- :button-title="resolveButtonTitle"
- @onClick="$emit('resolve')"
- />
- <div v-if="discussion.resolvable" class="btn-group discussion-actions ml-sm-2" role="group">
- <resolve-with-issue-button v-if="resolveWithIssuePath" :url="resolveWithIssuePath" />
- <jump-to-next-discussion-button
- v-if="shouldShowJumpToNextDiscussion"
- @onClick="$emit('jumpToNextDiscussion')"
+
+ <div class="btn-group discussion-actions" role="group">
+ <resolve-discussion-button
+ v-if="discussion.resolvable"
+ :is-resolving="isResolving"
+ :button-title="resolveButtonTitle"
+ @onClick="$emit('resolve')"
+ />
+ <resolve-with-issue-button
+ v-if="discussion.resolvable && resolveWithIssuePath"
+ :url="resolveWithIssuePath"
/>
</div>
+
+ <div
+ v-if="discussion.resolvable && shouldShowJumpToNextDiscussion"
+ class="btn-group discussion-actions ml-sm-2"
+ >
+ <jump-to-next-discussion-button @onClick="$emit('jumpToNextDiscussion')" />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
index 228bb652597..30971ad5227 100644
--- a/app/assets/javascripts/notes/components/discussion_notes.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -105,8 +105,8 @@ export default {
:commit="commit"
:help-page-path="helpPagePath"
:show-reply-button="userCanReply"
- @handle-delete-note="$emit('deleteNote')"
- @start-replying="$emit('startReplying')"
+ @handleDeleteNote="$emit('deleteNote')"
+ @startReplying="$emit('startReplying')"
>
<note-edited-text
v-if="discussion.resolved"
@@ -132,7 +132,7 @@ export default {
:note="componentData(note)"
:help-page-path="helpPagePath"
:line="line"
- @handle-delete-note="$emit('deleteNote')"
+ @handleDeleteNote="$emit('deleteNote')"
/>
</template>
</template>
@@ -144,7 +144,7 @@ export default {
:note="componentData(note)"
:help-page-path="helpPagePath"
:line="diffLine"
- @handle-delete-note="$emit('deleteNote')"
+ @handleDeleteNote="$emit('deleteNote')"
>
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
</component>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 10b15a9c38c..b8eaff32cce 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -126,10 +126,7 @@ export default {
return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
},
shouldShowJumpToNextDiscussion() {
- return this.showJumpToNextDiscussion(
- this.discussion.id,
- this.discussionsByDiffOrder ? 'diff' : 'discussion',
- );
+ return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion');
},
shouldRenderDiffs() {
return this.discussion.diff_discussion && this.renderDiffFile;
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index d7982be3e4b..8aa8f5037b3 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -61,15 +61,13 @@ export const unresolvedDiscussionsCount = state => state.unresolvedDiscussionsCo
export const resolvableDiscussionsCount = state => state.resolvableDiscussionsCount;
export const hasUnresolvedDiscussions = state => state.hasUnresolvedDiscussions;
-export const showJumpToNextDiscussion = (state, getters) => (discussionId, mode = 'discussion') => {
+export const showJumpToNextDiscussion = (state, getters) => (mode = 'discussion') => {
const orderedDiffs =
mode !== 'discussion'
? getters.unresolvedDiscussionsIdsByDiff
: getters.unresolvedDiscussionsIdsByDate;
- const indexOf = orderedDiffs.indexOf(discussionId);
-
- return indexOf !== -1 && indexOf < orderedDiffs.length - 1;
+ return orderedDiffs.length > 1;
};
export const isDiscussionResolved = (state, getters) => discussionId =>
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
index 8f3ba9779fb..d5f1cea8356 100644
--- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -92,7 +92,9 @@ export default {
</template>
<template v-else>
<tr>
- <td>No {{ header.toLowerCase() }} for this request.</td>
+ <td>
+ {{ sprintf(__('No %{header} for this request.'), { header: header.toLowerCase() }) }}
+ </td>
</tr>
</template>
</table>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 48515cf785c..185003c306e 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -5,6 +5,7 @@ import { glEmojiTag } from '~/emoji';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue';
+import { s__ } from '~/locale';
export default {
components: {
@@ -35,10 +36,10 @@ export default {
},
},
detailedMetrics: [
- { metric: 'pg', header: 'SQL queries', details: 'queries', keys: ['sql'] },
+ { metric: 'pg', header: s__('PerformanceBar|SQL queries'), details: 'queries', keys: ['sql'] },
{
metric: 'gitaly',
- header: 'Gitaly calls',
+ header: s__('PerformanceBar|Gitaly calls'),
details: 'details',
keys: ['feature', 'request'],
},
@@ -99,7 +100,8 @@ export default {
class="current-host"
:class="{ canary: currentRequest.details.host.canary }"
>
- <span v-html="birdEmoji"></span> {{ currentRequest.details.host.hostname }}
+ <span v-html="birdEmoji"></span>
+ {{ currentRequest.details.host.hostname }}
</span>
</div>
<detailed-metric
@@ -118,9 +120,9 @@ export default {
data-toggle="modal"
data-target="#modal-peek-line-profile"
>
- profile
+ {{ s__('PerformanceBar|profile') }}
</button>
- <a v-else :href="profileUrl"> profile </a>
+ <a v-else :href="profileUrl">{{ s__('PerformanceBar|profile') }}</a>
</div>
<simple-metric
v-for="metric in $options.simpleMetrics"
@@ -139,7 +141,7 @@ export default {
id="peek-view-trace"
class="view"
>
- <a :href="currentRequest.details.tracing.tracing_url"> trace </a>
+ <a :href="currentRequest.details.tracing.tracing_url">{{ s__('PerformanceBar|trace') }}</a>
</div>
<request-selector
v-if="currentRequest"
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index 01a30809e1a..2be9c37b00a 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -1,6 +1,6 @@
<script>
import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
-import { components, componentNames } from '~/reports/components/issue_body';
+import { components, componentNames } from 'ee_else_ce/reports/components/issue_body';
export default {
name: 'ReportItem',
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 41386178a1e..a79da476890 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -162,7 +162,8 @@ export default {
removeWIPPath: store.removeWIPPath,
sourceBranchPath: store.sourceBranchPath,
ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath,
- statusPath: store.statusPath,
+ mergeRequestBasicPath: store.mergeRequestBasicPath,
+ mergeRequestWidgetPath: store.mergeRequestWidgetPath,
mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index 0bb70bfd658..1dae53039d5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -30,11 +30,11 @@ export default class MRWidgetService {
}
poll() {
- return axios.get(`${this.endpoints.statusPath}?serializer=basic`);
+ return axios.get(this.endpoints.mergeRequestBasicPath);
}
checkStatus() {
- return axios.get(`${this.endpoints.statusPath}?serializer=widget`);
+ return axios.get(this.endpoints.mergeRequestWidgetPath);
}
fetchMergeActionsContent() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index bfa3e7f4a59..581fee7477f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -86,7 +86,8 @@ export default class MergeRequestStore {
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
- this.statusPath = data.status_path;
+ this.mergeRequestBasicPath = data.merge_request_basic_path;
+ this.mergeRequestWidgetPath = data.merge_request_widget_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
this.newBlobPath = data.new_blob_path;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 824edb2869f..e880b941d67 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -657,6 +657,10 @@ $note-form-margin-left: 72px;
margin-left: -1px;
}
+ .btn-group > .discussion-create-issue-btn {
+ margin-left: -2px;
+ }
+
svg {
height: 15px;
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7321f719deb..75108bf2646 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -516,4 +516,10 @@ class ApplicationController < ActionController::Base
def sentry_context
Gitlab::Sentry.context(current_user)
end
+
+ def allow_gitaly_ref_name_caching
+ ::Gitlab::GitalyClient.allow_ref_name_caching do
+ yield
+ end
+ end
end
diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb
index b846fb21266..92602fd8096 100644
--- a/app/controllers/groups/clusters_controller.rb
+++ b/app/controllers/groups/clusters_controller.rb
@@ -4,7 +4,6 @@ class Groups::ClustersController < Clusters::ClustersController
include ControllerWithCrossProjectAccessCheck
prepend_before_action :group
- prepend_before_action :check_group_clusters_feature_flag!
requires_cross_project_access
layout 'group'
@@ -18,12 +17,4 @@ class Groups::ClustersController < Clusters::ClustersController
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id])
end
-
- def check_group_clusters_feature_flag!
- render_404 unless group_clusters_enabled?
- end
-
- def group_clusters_enabled?
- group.group_clusters_enabled?
- end
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 80e4f54bbf4..90396c15375 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -87,10 +87,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
-
- def allow_gitaly_ref_name_caching
- ::Gitlab::GitalyClient.allow_ref_name_caching do
- yield
- end
- end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index f2a6268b3e9..dcc272aecff 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -51,4 +51,11 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
Ci::Pipeline.none
end
end
+
+ def close_merge_request_if_no_source_project
+ return if @merge_request.source_project
+ return unless @merge_request.open?
+
+ @merge_request.close
+ end
end
diff --git a/app/controllers/projects/merge_requests/content_controller.rb b/app/controllers/projects/merge_requests/content_controller.rb
new file mode 100644
index 00000000000..6e026b83ee3
--- /dev/null
+++ b/app/controllers/projects/merge_requests/content_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class Projects::MergeRequests::ContentController < Projects::MergeRequests::ApplicationController
+ # @merge_request.check_mergeability is not executed here since
+ # widget serializer calls it via mergeable? method
+ # but we might want to call @merge_request.check_mergeability
+ # for other types of serialization
+
+ before_action :close_merge_request_if_no_source_project
+ around_action :allow_gitaly_ref_name_caching
+
+ def widget
+ respond_to do |format|
+ format.json do
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
+
+ serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
+ render json: serializer.represent(merge_request, serializer: 'widget')
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index fc37ce1dbc4..7ee8e0ea8f8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -235,12 +235,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end
- def close_merge_request_if_no_source_project
- if !@merge_request.source_project && @merge_request.open?
- @merge_request.close
- end
- end
-
private
def ci_environments_status_on_merge_result?
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index a80ab3bcd28..8c674be58c5 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -5,6 +5,8 @@ class SearchController < ApplicationController
include SearchHelper
include RendersCommits
+ around_action :allow_gitaly_ref_name_caching
+
skip_before_action :authenticate_user!
requires_cross_project_access if: -> do
search_term_present = params[:search].present? || params[:term].present?
diff --git a/app/graphql/mutations/.keep b/app/graphql/mutations/.keep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/graphql/mutations/.keep
+++ /dev/null
diff --git a/app/graphql/mutations/award_emojis/add.rb b/app/graphql/mutations/award_emojis/add.rb
new file mode 100644
index 00000000000..8e050dd6d29
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/add.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Add < Base
+ graphql_name 'AddAwardEmoji'
+
+ def resolve(args)
+ awardable = authorized_find!(id: args[:awardable_id])
+
+ check_object_is_awardable!(awardable)
+
+ # TODO this will be handled by AwardEmoji::AddService
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ award = awardable.create_award_emoji(args[:name], current_user)
+
+ {
+ award_emoji: (award if award.persisted?),
+ errors: errors_on_object(award)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/award_emojis/base.rb b/app/graphql/mutations/award_emojis/base.rb
new file mode 100644
index 00000000000..d868db84f9d
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/base.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Base < BaseMutation
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorize :award_emoji
+
+ argument :awardable_id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the awardable resource'
+
+ argument :name,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::AwardEmojis::AwardEmojiType, :name)
+
+ field :award_emoji,
+ Types::AwardEmojis::AwardEmojiType,
+ null: true,
+ description: 'The award emoji after mutation'
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+
+ # Called by mutations methods after performing an authorization check
+ # of an awardable object.
+ def check_object_is_awardable!(object)
+ unless object.is_a?(Awardable) && object.emoji_awardable?
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Cannot award emoji to this resource'
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/award_emojis/remove.rb b/app/graphql/mutations/award_emojis/remove.rb
new file mode 100644
index 00000000000..3ba85e445b8
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/remove.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Remove < Base
+ graphql_name 'RemoveAwardEmoji'
+
+ def resolve(args)
+ awardable = authorized_find!(id: args[:awardable_id])
+
+ check_object_is_awardable!(awardable)
+
+ # TODO this check can be removed once AwardEmoji services are available.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ unless awardable.awarded_emoji?(args[:name], current_user)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'You have not awarded emoji of type name to the awardable'
+ end
+
+ # TODO this will be handled by AwardEmoji::DestroyService
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ awardable.remove_award_emoji(args[:name], current_user)
+
+ {
+ # Mutation response is always a `nil` award_emoji
+ errors: []
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb
new file mode 100644
index 00000000000..c03902e8035
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/toggle.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Toggle < Base
+ graphql_name 'ToggleAwardEmoji'
+
+ field :toggledOn,
+ GraphQL::BOOLEAN_TYPE,
+ null: false,
+ description: 'True when the emoji was awarded, false when it was removed'
+
+ def resolve(args)
+ awardable = authorized_find!(id: args[:awardable_id])
+
+ check_object_is_awardable!(awardable)
+
+ # TODO this will be handled by AwardEmoji::ToggleService
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ award = awardable.toggle_award_emoji(args[:name], current_user)
+
+ # Destroy returns a collection :(
+ award = award.first if award.is_a?(Array)
+
+ errors = errors_on_object(award)
+
+ toggled_on = awardable.awarded_emoji?(args[:name], current_user)
+
+ {
+ # For consistency with the AwardEmojis::Remove mutation, only return
+ # the AwardEmoji if it was created and not destroyed
+ award_emoji: (award if toggled_on),
+ errors: errors,
+ toggled_on: toggled_on
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index eb03dfe1624..08d2a1f18a3 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -2,6 +2,8 @@
module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation
+ prepend Gitlab::Graphql::CopyFieldDescription
+
field :errors, [GraphQL::STRING_TYPE],
null: false,
description: "Reasons why the mutation failed."
@@ -9,5 +11,10 @@ module Mutations
def current_user
context[:current_user]
end
+
+ # Returns Array of errors on an ActiveRecord object
+ def errors_on_object(record)
+ record.errors.full_messages
+ end
end
end
diff --git a/app/graphql/types/award_emojis/award_emoji_type.rb b/app/graphql/types/award_emojis/award_emoji_type.rb
new file mode 100644
index 00000000000..8daf699a112
--- /dev/null
+++ b/app/graphql/types/award_emojis/award_emoji_type.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Types
+ module AwardEmojis
+ class AwardEmojiType < BaseObject
+ graphql_name 'AwardEmoji'
+
+ authorize :read_emoji
+
+ present_using AwardEmojiPresenter
+
+ field :name,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji name'
+
+ field :description,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji description'
+
+ field :unicode,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji in unicode'
+
+ field :emoji,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji as an icon'
+
+ field :unicode_version,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The unicode version for this emoji'
+
+ field :user,
+ Types::UserType,
+ null: false,
+ description: 'The user who awarded the emoji',
+ resolve: -> (award_emoji, _args, _context) {
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, award_emoji.user_id).find
+ }
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 2b4ef299296..6ef1d816b7c 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -6,6 +6,9 @@ module Types
graphql_name "Mutation"
+ mount_mutation Mutations::AwardEmojis::Add
+ mount_mutation Mutations::AwardEmojis::Remove
+ mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetWip
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index a3f53ca8dd6..5aed7e313e6 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -142,7 +142,7 @@ module GroupsHelper
can?(current_user, "read_group_#{resource}".to_sym, @group)
end
- if can?(current_user, :read_cluster, @group) && @group.group_clusters_enabled?
+ if can?(current_user, :read_cluster, @group)
links << :kubernetes
end
diff --git a/app/models/board.rb b/app/models/board.rb
index e08db764f65..50b6ca9b70f 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -4,11 +4,14 @@ class Board < ApplicationRecord
belongs_to :group
belongs_to :project
- has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :lists, -> { ordered }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :destroyable_lists, -> { destroyable.ordered }, class_name: "List"
validates :project, presence: true, if: :project_needed?
validates :group, presence: true, unless: :project
+ scope :with_associations, -> { preload(:destroyable_lists) }
+
def project_needed?
!group
end
diff --git a/app/models/clusters/instance.rb b/app/models/clusters/instance.rb
index d8a888d53ba..f21dbdf7f26 100644
--- a/app/models/clusters/instance.rb
+++ b/app/models/clusters/instance.rb
@@ -9,9 +9,5 @@ module Clusters
def feature_available?(feature)
::Feature.enabled?(feature, default_enabled: true)
end
-
- def self.enabled?
- ::Feature.enabled?(:instance_clusters, default_enabled: true)
- end
end
end
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 1bd8a799f0d..5a358ae2ef6 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -13,8 +13,8 @@ module DeploymentPlatform
def find_deployment_platform(environment)
find_cluster_platform_kubernetes(environment: environment) ||
- find_group_cluster_platform_kubernetes_with_feature_guard(environment: environment) ||
- find_instance_cluster_platform_kubernetes_with_feature_guard(environment: environment)
+ find_group_cluster_platform_kubernetes(environment: environment) ||
+ find_instance_cluster_platform_kubernetes(environment: environment)
end
# EE would override this and utilize environment argument
@@ -23,24 +23,12 @@ module DeploymentPlatform
.last&.platform_kubernetes
end
- def find_group_cluster_platform_kubernetes_with_feature_guard(environment: nil)
- return unless group_clusters_enabled?
-
- find_group_cluster_platform_kubernetes(environment: environment)
- end
-
# EE would override this and utilize environment argument
def find_group_cluster_platform_kubernetes(environment: nil)
Clusters::Cluster.enabled.default_environment.ancestor_clusters_for_clusterable(self)
.first&.platform_kubernetes
end
- def find_instance_cluster_platform_kubernetes_with_feature_guard(environment: nil)
- return unless Clusters::Instance.enabled?
-
- find_instance_cluster_platform_kubernetes(environment: environment)
- end
-
# EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
Clusters::Instance.new.clusters.enabled.default_environment
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 46d2c345758..22b6b1d720c 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -25,7 +25,7 @@ module RelativePositioning
relative_position = position_between(max_relative_position, MAX_POSITION)
object.relative_position = relative_position
max_relative_position = relative_position
- object.save
+ object.save(touch: false)
end
end
end
@@ -159,7 +159,7 @@ module RelativePositioning
def save_positionable_neighbours
return unless @positionable_neighbours
- status = @positionable_neighbours.all?(&:save)
+ status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
@positionable_neighbours = nil
status
diff --git a/app/models/group.rb b/app/models/group.rb
index dbec211935d..8e89c7ecfb1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -410,10 +410,6 @@ class Group < Namespace
ensure_runners_token!
end
- def group_clusters_enabled?
- Feature.enabled?(:group_clusters, root_ancestor, default_enabled: true)
- end
-
def project_creation_level
super || ::Gitlab::CurrentSettings.default_project_creation
end
diff --git a/app/models/list.rb b/app/models/list.rb
index 17b1a8510cf..d28a9bda82d 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -16,6 +16,7 @@ class List < ApplicationRecord
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
scope :preload_associations, -> { preload(:board, :label) }
+ scope :ordered, -> { order(:list_type, :position) }
class << self
def destroyable_types
diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb
index 74ccf23cf69..7a123deb719 100644
--- a/app/models/postgresql/replication_slot.rb
+++ b/app/models/postgresql/replication_slot.rb
@@ -28,7 +28,7 @@ module Postgresql
# We force the use of a transaction here so the query always goes to the
# primary, even when using the EE DB load balancer.
sizes = transaction { pluck(lag_function) }
- too_great = sizes.count { |size| size >= max }
+ too_great = sizes.compact.count { |size| size >= max }
# If too many replicas are falling behind too much, the availability of a
# GitLab instance might suffer. To prevent this from happening we require
diff --git a/app/models/project.rb b/app/models/project.rb
index c642b65f674..b102e0580e7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -306,7 +306,6 @@ class Project < ApplicationRecord
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team
delegate :add_master, to: :team # @deprecated
delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings
- delegate :group_clusters_enabled?, to: :group, allow_nil: true
delegate :root_ancestor, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f4fdac2558c..00931457344 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -194,6 +194,10 @@ class Snippet < ApplicationRecord
'snippet'
end
+ def to_ability_name
+ model_name.singular
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/policies/award_emoji_policy.rb b/app/policies/award_emoji_policy.rb
new file mode 100644
index 00000000000..21e382e24b3
--- /dev/null
+++ b/app/policies/award_emoji_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AwardEmojiPolicy < BasePolicy
+ delegate { @subject.awardable if DeclarativePolicy.has_policy?(@subject.awardable) }
+
+ condition(:can_read_awardable) do
+ can?(:"read_#{@subject.awardable.to_ability_name}")
+ end
+
+ rule { can_read_awardable }.enable :read_emoji
+end
diff --git a/app/policies/clusters/instance_policy.rb b/app/policies/clusters/instance_policy.rb
index e1045c85e6d..f72096e8fc6 100644
--- a/app/policies/clusters/instance_policy.rb
+++ b/app/policies/clusters/instance_policy.rb
@@ -6,9 +6,8 @@ module Clusters
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
condition(:can_have_multiple_clusters) { multiple_clusters_available? }
- condition(:instance_clusters_enabled) { Instance.enabled? }
- rule { admin & instance_clusters_enabled }.policy do
+ rule { admin }.policy do
enable :read_cluster
enable :add_cluster
enable :create_cluster
diff --git a/app/presenters/award_emoji_presenter.rb b/app/presenters/award_emoji_presenter.rb
new file mode 100644
index 00000000000..98713855d35
--- /dev/null
+++ b/app/presenters/award_emoji_presenter.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class AwardEmojiPresenter < Gitlab::View::Presenter::Delegated
+ presents :award_emoji
+
+ def description
+ as_emoji['description']
+ end
+
+ def unicode
+ as_emoji['unicode']
+ end
+
+ def emoji
+ as_emoji['moji']
+ end
+
+ def unicode_version
+ Gitlab::Emoji.emoji_unicode_version(award_emoji.name)
+ end
+
+ private
+
+ def as_emoji
+ @emoji ||= Gitlab::Emoji.emojis[award_emoji.name] || {}
+ end
+end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 43aced598a9..fd2673fa0cc 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -217,8 +217,12 @@ class MergeRequestWidgetEntity < IssuableEntity
project_merge_request_path(merge_request.project, merge_request, format: :diff)
end
- expose :status_path do |merge_request|
- project_merge_request_path(merge_request.target_project, merge_request, format: :json)
+ expose :merge_request_basic_path do |merge_request|
+ project_merge_request_path(merge_request.target_project, merge_request, serializer: :basic, format: :json)
+ end
+
+ expose :merge_request_widget_path do |merge_request|
+ widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
expose :ci_environments_status_path do |merge_request|
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 26132f1824a..02de080e0ba 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -205,7 +205,7 @@ class IssuableBaseService < BaseService
end
if issuable.changed? || params.present?
- issuable.assign_attributes(params.merge(updated_by: current_user))
+ issuable.assign_attributes(params)
if has_title_or_description_changed?(issuable)
issuable.assign_attributes(last_edited_at: Time.now, last_edited_by: current_user)
@@ -213,11 +213,16 @@ class IssuableBaseService < BaseService
before_update(issuable)
+ # Do not touch when saving the issuable if only changes position within a list. We should call
+ # this method at this point to capture all possible changes.
+ should_touch = update_timestamp?(issuable)
+
+ issuable.updated_by = current_user if should_touch
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
- if issuable.with_transaction_returning_status { issuable.save }
+ if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
@@ -402,4 +407,8 @@ class IssuableBaseService < BaseService
def ensure_milestone_available(issuable)
issuable.milestone_id = nil unless issuable.milestone_available?
end
+
+ def update_timestamp?(issuable)
+ issuable.changes.keys != ["relative_position"]
+ end
end
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index 15c13a452ad..8f52e9cb23f 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -63,12 +63,20 @@ module Users
def assign_identity
return unless identity_params.present?
- identity = user.identities.find_or_create_by(provider: identity_params[:provider]) # rubocop: disable CodeReuse/ActiveRecord
+ identity = user.identities.find_or_create_by(provider_params) # rubocop: disable CodeReuse/ActiveRecord
identity.update(identity_params)
end
def identity_attributes
[:provider, :extern_uid]
end
+
+ def provider_attributes
+ [:provider]
+ end
+
+ def provider_params
+ identity_params.slice(*provider_attributes)
+ end
end
end
diff --git a/app/views/projects/_merge_request_settings_description_text.html.haml b/app/views/projects/_merge_request_settings_description_text.html.haml
new file mode 100644
index 00000000000..42964c900b3
--- /dev/null
+++ b/app/views/projects/_merge_request_settings_description_text.html.haml
@@ -0,0 +1 @@
+%p= s_('ProjectSettings|Choose your merge method, merge options, and merge checks.')
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c15b84d0aac..29b7c45201c 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -27,7 +27,7 @@
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Choose your merge method, options, checks, and set up a default merge request description template.')
+ = render_if_exists 'projects/merge_request_settings_description_text'
.settings-content
= render_if_exists 'shared/promotions/promote_mr_features'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index d59b2d4fb01..c13a47b0b09 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -31,21 +31,19 @@
= button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do
= s_('Environments|Stop environment')
- .row.top-area.adjust
- .col-md-7
- %h3.page-title= @environment.name
- .col-md-5
- .nav-controls
- = render 'projects/environments/terminal_button', environment: @environment
- = render 'projects/environments/external_url', environment: @environment
- = render 'projects/environments/metrics_button', environment: @environment
- - if can?(current_user, :update_environment, @environment)
- = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
- - if can?(current_user, :stop_environment, @environment)
- = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
- target: '#stop-environment-modal' } do
- = sprite_icon('stop')
- = s_('Environments|Stop')
+ .top-area
+ %h3.page-title= @environment.name
+ .nav-controls.ml-auto.my-2
+ = render 'projects/environments/terminal_button', environment: @environment
+ = render 'projects/environments/external_url', environment: @environment
+ = render 'projects/environments/metrics_button', environment: @environment
+ - if can?(current_user, :update_environment, @environment)
+ = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
+ - if can?(current_user, :stop_environment, @environment)
+ = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
+ target: '#stop-environment-modal' } do
+ = sprite_icon('stop')
+ = s_('Environments|Stop')
.environments-container
- if @deployments.blank?
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ea6349f2f57..1d0bc588c9c 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -76,6 +76,7 @@
#{ _('New tag') }
.tree-controls
+ = render_if_exists 'projects/tree/lock_link'
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link'
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 13847cd9be1..576ec3e1782 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -28,7 +28,7 @@
.js-projects-list-holder
- if any_projects?(projects)
- - load_pipeline_status(projects)
+ - load_pipeline_status(projects) if pipeline_status
%ul.projects-list{ class: css_classes }
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
diff --git a/changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml b/changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml
new file mode 100644
index 00000000000..606abe818b4
--- /dev/null
+++ b/changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml
@@ -0,0 +1,5 @@
+---
+title: Turn commit sha in monitor charts popover to link
+merge_request: 29914
+author:
+type: fixed
diff --git a/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml b/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml
new file mode 100644
index 00000000000..efc6af7845c
--- /dev/null
+++ b/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml
@@ -0,0 +1,5 @@
+---
+title: Will not update issue timestamps when changing positions in a list
+merge_request: 29677
+author:
+type: changed
diff --git a/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml b/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml
new file mode 100644
index 00000000000..bf6f314f0ce
--- /dev/null
+++ b/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml
@@ -0,0 +1,6 @@
+---
+title: Improve discussion reply buttons layout and how jump to next discussion button
+ appears
+merge_request: 29779
+author:
+type: changed
diff --git a/changelogs/unreleased/62826-graphql-emoji-mutations.yml b/changelogs/unreleased/62826-graphql-emoji-mutations.yml
new file mode 100644
index 00000000000..0c0aaedf844
--- /dev/null
+++ b/changelogs/unreleased/62826-graphql-emoji-mutations.yml
@@ -0,0 +1,5 @@
+---
+title: GraphQL mutations for add, remove and toggle emoji
+merge_request: 29919
+author:
+type: added
diff --git a/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml b/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml
new file mode 100644
index 00000000000..749fe6a9cb0
--- /dev/null
+++ b/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Environment details header border misaligned
+merge_request: 30011
+author:
+type: fixed
diff --git a/changelogs/unreleased/63200-reply-button-broken.yml b/changelogs/unreleased/63200-reply-button-broken.yml
new file mode 100644
index 00000000000..11f81dbd925
--- /dev/null
+++ b/changelogs/unreleased/63200-reply-button-broken.yml
@@ -0,0 +1,5 @@
+---
+title: Fix unresponsive reply button in discussions
+merge_request: 29936
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-metrics-dashboard-permission-check.yml b/changelogs/unreleased/add-metrics-dashboard-permission-check.yml
new file mode 100644
index 00000000000..0ea2c4c8e41
--- /dev/null
+++ b/changelogs/unreleased/add-metrics-dashboard-permission-check.yml
@@ -0,0 +1,5 @@
+---
+title: Add permission check to metrics dashboards endpoint
+merge_request: 30017
+author:
+type: added
diff --git a/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml b/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml
new file mode 100644
index 00000000000..9f6a2040095
--- /dev/null
+++ b/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge requests section description text on project settings page
+merge_request: 27838
+author:
+type: changed \ No newline at end of file
diff --git a/changelogs/unreleased/dohtaset.yml b/changelogs/unreleased/dohtaset.yml
new file mode 100644
index 00000000000..5b917bd06d8
--- /dev/null
+++ b/changelogs/unreleased/dohtaset.yml
@@ -0,0 +1,5 @@
+---
+title: Fix charts on Cluster health page
+merge_request: 30073
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-extract-widget-into-different-request.yml b/changelogs/unreleased/id-extract-widget-into-different-request.yml
new file mode 100644
index 00000000000..3b9f5fdd6bd
--- /dev/null
+++ b/changelogs/unreleased/id-extract-widget-into-different-request.yml
@@ -0,0 +1,5 @@
+---
+title: Add a separate endpoint for fetching MRs serialized as widgets
+merge_request: 29979
+author:
+type: performance
diff --git a/changelogs/unreleased/po-raw-changes-encoding.yml b/changelogs/unreleased/po-raw-changes-encoding.yml
new file mode 100644
index 00000000000..051d18f50c7
--- /dev/null
+++ b/changelogs/unreleased/po-raw-changes-encoding.yml
@@ -0,0 +1,5 @@
+---
+title: Expect bytes from Gitaly RPC GetRawChanges
+merge_request: 28164
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml b/changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml
new file mode 100644
index 00000000000..fcc6c564345
--- /dev/null
+++ b/changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove group and instance clusters feature flag
+merge_request: 30124
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-add-force-random-password-user-api.yml b/changelogs/unreleased/sh-add-force-random-password-user-api.yml
new file mode 100644
index 00000000000..29f36978a0f
--- /dev/null
+++ b/changelogs/unreleased/sh-add-force-random-password-user-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for creating random passwords in user creation API
+merge_request: 30138
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml b/changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml
new file mode 100644
index 00000000000..d4be28e9883
--- /dev/null
+++ b/changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Gitaly ref caching for SearchController
+merge_request: 30105
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-avoid-loading-pipeline-status.yml b/changelogs/unreleased/sh-avoid-loading-pipeline-status.yml
new file mode 100644
index 00000000000..2dead948786
--- /dev/null
+++ b/changelogs/unreleased/sh-avoid-loading-pipeline-status.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid loading pipeline status in search results
+merge_request: 30111
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-handle-nil-replication-lag.yml b/changelogs/unreleased/sh-handle-nil-replication-lag.yml
new file mode 100644
index 00000000000..5638d7e79e3
--- /dev/null
+++ b/changelogs/unreleased/sh-handle-nil-replication-lag.yml
@@ -0,0 +1,5 @@
+---
+title: Fix background migrations failing with unused replication slot
+merge_request: 30042
+author:
+type: fixed
diff --git a/changelogs/unreleased/support-jsonb-default-value.yml b/changelogs/unreleased/support-jsonb-default-value.yml
new file mode 100644
index 00000000000..d46156276f9
--- /dev/null
+++ b/changelogs/unreleased/support-jsonb-default-value.yml
@@ -0,0 +1,5 @@
+---
+title: Support jsonb default in add_column_with_default migration helper
+merge_request: 29871
+author:
+type: other
diff --git a/config/routes/project.rb b/config/routes/project.rb
index bcbbd7222e0..91613e3333f 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -261,6 +261,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :commits
get :pipelines
get :diffs, to: 'merge_requests/diffs#show'
+ get :widget, to: 'merge_requests/content#widget'
end
get :diff_for_path, controller: 'merge_requests/diffs'
diff --git a/danger/only_documentation/Dangerfile b/danger/only_documentation/Dangerfile
new file mode 100644
index 00000000000..8e4564f22b6
--- /dev/null
+++ b/danger/only_documentation/Dangerfile
@@ -0,0 +1,24 @@
+# rubocop:disable Style/SignalException
+# frozen_string_literal: true
+
+has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/') }
+is_docs_only_branch = gitlab.branch_for_head =~ /(^docs[\/-].*|.*-docs$)/
+
+if is_docs_only_branch && !has_only_docs_changes
+ fail "It seems like your branch name has a `docs` prefix or suffix. "\
+ "The CI won't run the full pipeline, but you also have changed non-docs files. "\
+ "Please recreate this MR with a new branch name."
+end
+
+if has_only_docs_changes && !is_docs_only_branch
+ markdown(<<~MARKDOWN)
+
+ ## Documentation only changes
+
+ Hey! Seems your merge request contains only docs changes.
+ Tired of waiting 2 hours for the pipeline to finish?
+ Next time, prepend `docs-` to [your branch name](https://docs.gitlab.com/ee/development/documentation/#branch-naming)
+ and the pipeline will finish before you say GitLab (x300)!
+
+ MARKDOWN
+end
diff --git a/doc/api/users.md b/doc/api/users.md
index 4bc0335ae33..4667a985eb9 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -272,7 +272,14 @@ GET /users/:id?with_custom_attributes=true
## User creation
-Creates a new user. Note only administrators can create new users. Either `password` or `reset_password` should be specified (`reset_password` takes priority). If `reset_password` is `false`, then `password` is required.
+Creates a new user. Note only administrators can create new
+users. Either `password`, `reset_password`, or `force_random_password`
+must be specified. If `reset_password` and `force_random_password` are
+both `false`, then `password` is required.
+
+Note that `force_random_password` and `reset_password` take priority
+over `password`. In addition, `reset_password` and
+`force_random_password` can be used together.
```
POST /users
@@ -280,29 +287,30 @@ POST /users
Parameters:
-- `email` (required) - Email
-- `password` (optional) - Password
-- `reset_password` (optional) - Send user password reset link - true or false(default)
-- `username` (required) - Username
-- `name` (required) - Name
-- `skype` (optional) - Skype ID
-- `linkedin` (optional) - LinkedIn
-- `twitter` (optional) - Twitter account
-- `website_url` (optional) - Website URL
-- `organization` (optional) - Organization name
-- `projects_limit` (optional) - Number of projects user can create
-- `extern_uid` (optional) - External UID
-- `provider` (optional) - External provider name
-- `group_id_for_saml` (optional) - ID of group where SAML has been configured
-- `bio` (optional) - User's biography
-- `location` (optional) - User's location
-- `public_email` (optional) - The public email of the user
-- `admin` (optional) - User is admin - true or false (default)
-- `can_create_group` (optional) - User can create groups - true or false
-- `skip_confirmation` (optional) - Skip confirmation - true or false (default)
-- `external` (optional) - Flags the user as external - true or false(default)
-- `avatar` (optional) - Image file for user's avatar
-- `private_profile` (optional) - User's profile is private - true or false
+- `email` (required) - Email
+- `password` (optional) - Password
+- `reset_password` (optional) - Send user password reset link - true or false (default)
+- `force_random_password` (optional) - Set user password to a random value - true or false (default)
+- `username` (required) - Username
+- `name` (required) - Name
+- `skype` (optional) - Skype ID
+- `linkedin` (optional) - LinkedIn
+- `twitter` (optional) - Twitter account
+- `website_url` (optional) - Website URL
+- `organization` (optional) - Organization name
+- `projects_limit` (optional) - Number of projects user can create
+- `extern_uid` (optional) - External UID
+- `provider` (optional) - External provider name
+- `group_id_for_saml` (optional) - ID of group where SAML has been configured
+- `bio` (optional) - User's biography
+- `location` (optional) - User's location
+- `public_email` (optional) - The public email of the user
+- `admin` (optional) - User is admin - true or false (default)
+- `can_create_group` (optional) - User can create groups - true or false
+- `skip_confirmation` (optional) - Skip confirmation - true or false (default)
+- `external` (optional) - Flags the user as external - true or false(default)
+- `avatar` (optional) - Image file for user's avatar
+- `private_profile` (optional) - User's profile is private - true or false
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
index c63ec53414c..a7b402c3fb0 100644
--- a/doc/development/chatops_on_gitlabcom.md
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -1,12 +1,13 @@
# Chatops on GitLab.com
-Chatops on GitLab.com allows GitLabbers to run various automation tasks on GitLab.com using Slack.
+ChatOps on GitLab.com allows GitLab team members to run various automation tasks on GitLab.com using Slack.
## Requesting access
-GitLabbers may need access to Chatops on GitLab.com for administration tasks such as:
+GitLab team-members may need access to Chatops on GitLab.com for administration
+tasks such as:
-- Configuring feature flags on staging.
+- Configuring feature flags.
- Running `EXPLAIN` queries against the GitLab.com production replica.
To request access to Chatops on GitLab.com:
@@ -18,4 +19,4 @@ To request access to Chatops on GitLab.com:
- [Chatops Usage](https://docs.gitlab.com/ee/ci/chatops/README.html)
- [Understanding EXPLAIN plans](understanding_explain_plans.md)
- - [Feature Groups](feature_flags.md#feature-groups)
+ - [Feature Groups](feature_flags/development.md#feature-groups)
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 3d36a7bf3b1..2f1ad5fa910 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -65,10 +65,12 @@ The current team labels are:
- ~Defend
- ~Distribution
- ~Documentation
+- ~Ecosystem
- ~Geo
- ~Gitaly
- ~Growth
- ~Manage
+- ~Memory
- ~Monitor
- ~Plan
- ~Quality
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index f319d00d7fe..87e61a7476f 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -31,8 +31,8 @@ This is also the style used by linting tools such as
[Return to Contributing documentation](index.md)
-[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
-[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
+[rss-source]: https://github.com/rubocop-hq/ruby-style-guide/blob/master/README.adoc#source-code-layout
+[rss-naming]: https://github.com/rubocop-hq/ruby-style-guide/blob/master/README.adoc#naming-conventions
[doc-guidelines]: ../documentation/index.md "Documentation guidelines"
[js-styleguide]: ../fe_guide/style_guide_js.md "JavaScript styleguide"
[scss-styleguide]: ../fe_guide/style_guide_scss.md "SCSS styleguide"
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index ee3a9caf9a0..6dd12b5efa7 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -11,8 +11,40 @@ and deploy it to <https://docs.gitlab.com>.
While the source of the documentation content is stored in GitLab's respective product
repositories, the source that is used to build the documentation site _from that content_
-is located at <https://gitlab.com/gitlab-com/gitlab-docs>. See the README there for
-detailed information.
+is located at <https://gitlab.com/gitlab-com/gitlab-docs>.
+
+The following diagram illustrates the relationship between the repositories
+from where content is sourced, the `gitlab-docs` project, and the published output.
+
+```mermaid
+ graph LR
+ A[gitlab-ce/doc]
+ B[gitlab-ee/doc]
+ C[gitlab-runner/docs]
+ D[omnibus-gitlab/doc]
+ E[charts/doc]
+ F[gitlab-docs]
+ A --> F
+ B --> F
+ C --> F
+ D --> F
+ E --> F
+ F -- Build pipeline --> G
+ G[docs.gitlab.com]
+ H[/ce/]
+ I[/ee/]
+ J[/runner/]
+ K[/omnibus/]
+ L[/charts/]
+ G --> H
+ G --> I
+ G --> J
+ G --> K
+ G --> L
+```
+
+See the [README there](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
+for detailed information.
## Assets
@@ -22,9 +54,9 @@ the GitLab Documentation website.
### Libraries
-- [Bootstrap 3.3 components](https://getbootstrap.com/docs/3.3/components/)
-- [Bootstrap 3.3 JS](https://getbootstrap.com/docs/3.3/javascript/)
-- [jQuery](https://jquery.com/) 3.2.1
+- [Bootstrap 4.3.1 components](https://getbootstrap.com/docs/4.3/components/)
+- [Bootstrap 4.3.1 JS](https://getbootstrap.com/docs/4.3/getting-started/javascript/)
+- [jQuery](https://jquery.com/) 3.3.1
- [Clipboard JS](https://clipboardjs.com/)
- [Font Awesome 4.7.0](https://fontawesome.com/v4.7.0/icons/)
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index ff6dc16d1a0..7cd3d82ec4e 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -100,6 +100,13 @@ use regular Markdown markup, following the rules in the linked style guide.
Note that Kramdown-specific markup (e.g., `{:.class}`) will not render properly on GitLab instances under [`/help`](index.md#gitlab-help).
+Hard-coded HTML is valid, although it's discouraged to be used while we have `/help`. HTML is permitted as long as:
+
+- There's no equivalent markup in markdown.
+- Advanced tables are necessary.
+- Special styling is required.
+- Reviewed and approved by a technical writer.
+
## Structure
### Organize by topic, not by type
@@ -143,7 +150,8 @@ The table below shows what kind of documentation goes where.
a proper naming would be `import_projects_from_github.md`. The same rule
applies to images.
1. For image files, do not exceed 100KB.
-1. We do not yet support embedded videos. Please link out.
+1. Do not upload video files to the product repositories.
+[Link or embed videos](#videos) instead.
1. There are four main directories, `user`, `administration`, `api` and `development`.
1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
`profile/`, `dashboard/` and `admin_area/`.
@@ -207,6 +215,7 @@ Do not include the same information in multiple places. [Link to a SSOT instead.
## Text
+- [Write in markdown](#markdown).
- Splitting long lines (preferably up to 100 characters) can make it easier to provide feedback on small chunks of text.
- Insert an empty line for new paragraphs.
- Use sentence case for titles, headings, labels, menu items, and buttons.
@@ -430,7 +439,7 @@ To indicate the steps of navigation through the UI:
- Images should be used (only when necessary) to _illustrate_ the description
of a process, not to _replace_ it.
- Max image size: 100KB (gifs included).
-- The GitLab docs do not support videos yet.
+- See also how to link and embed [videos](#videos) to illustrate the docs.
Inside the document:
@@ -455,6 +464,85 @@ directly to an HTML `img` tag:
<img src="path/to/image.jpg" alt="Alt text (required)" class="image-noshadow">
```
+## Videos
+
+Adding GitLab's existing YouTube video tutorials to the documentation is
+highly encouraged, unless the video is outdated. Videos should not
+replace documentation, but complement or illustrate it. If content in a video is
+fundamental to a feature and its key use cases, but this is not adequately covered in the documentation,
+add this detail to the documentation text or create an issue to review the video and do so.
+
+Do not upload videos to the product repositories. [Link](#link-to-video) or [embed](#embed-videos) them instead.
+
+### Link to video
+
+To link out to a video, include a YouTube icon so that readers can
+quickly and easily scan the page for videos before reading:
+
+```md
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, see [Video Title](link-to-video).
+```
+
+You can link any up-to-date video that is useful to the GitLab user.
+
+### Embed videos
+
+> [Introduced](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/472) in GitLab 12.1.
+
+GitLab docs (docs.gitlab.com) support embedded videos.
+
+You can only embed videos from
+[GitLab's official YouTube account](https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg).
+For videos from other sources, [link](#link-to-video) them instead.
+
+In most cases, it is better to [link to video](#link-to-video) instead,
+because an embed takes up a lot of space on the page and can be distracting
+to readers.
+
+To embed a video, follow the instructions below and make sure
+you have your MR reviewed and approved by a technical writer.
+
+1. Copy the code below and paste it into your markdown file.
+ Leave a blank line above and below it. Do NOT edit the code
+ (don't remove or add any spaces, etc).
+1. On YouTube, visit the video URL you want to display. Copy
+ the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
+ and replace the video title and link in the line under `<div class="video-fallback">`.
+1. On YouTube, click **Share**, then **Embed**.
+1. Copy the `<iframe>` source (`src`) **URL only**
+ (`https://www.youtube.com/embed/VIDEO-ID`),
+ and paste it, replacing the content of the `src` field in the
+ `iframe` tag.
+
+```html
+leave a blank line here
+<div class="video-fallback">
+ See the video: [Video title](https://www.youtube.com/watch?v=MqL6BMOySIQ).
+</div>
+<figure class="video-container">
+ <iframe src="https://www.youtube.com/embed/MqL6BMOySIQ" frameborder="0" allowfullscreen="true"> </iframe>
+</figure>
+leave a blank line here
+```
+
+This is how it renders on docs.gitlab.com:
+
+<div class="video-fallback">
+ See the video: [What is GitLab](https://www.youtube.com/watch?v=enMumwvLAug).
+</div>
+<figure class="video-container">
+ <iframe src="https://www.youtube.com/embed/MqL6BMOySIQ" frameborder="0" allowfullscreen="true"> </iframe>
+</figure>
+
+> Notes:
+>
+> - The `figure` tag is required for semantic SEO and the `video_container`
+class is necessary to make sure the video is responsive and displays
+nicely on different mobile devices.
+> - The `<div class="video-fallback">` is a fallback necessary for GitLab's
+`/help`, as GitLab's markdown processor does not support iframes. It's hidden on the docs site but will be displayed on GitLab's `/help`.
+
## Code blocks
- Always wrap code added to a sentence in inline code blocks (``` ` ```).
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index df32242a522..64c793cfd64 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -5,8 +5,16 @@
[Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools]
are useful for testing for potential accessibility problems in GitLab.
-Accessibility best-practices and more in-depth information is available on
-[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools.
+The [axe][axe-website] browser extension (available for [Firefox][axe-firefox-extension] and [Chrome][axe-chrome-extension]) is
+also a handy tool for running audits and getting feedback on markup, CSS and even potentially problematic color usages.
+
+Accessibility best-practices and more in-depth information are available on
+[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools. The "[awesome a11y][awesome-a11y]" list is also a
+useful compilation of accessibility-related material.
[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools
[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
+[axe-website]: https://www.deque.com/axe/
+[axe-firefox-extension]: https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/
+[axe-chrome-extension]: https://chrome.google.com/webstore/detail/axe/lhdoppojpmngadmnindnejefpokejbdd
+[awesome-a11y]: https://github.com/brunopulis/awesome-a11y
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 13f0c5cc33e..6bad91d6287 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -1,127 +1 @@
-# Manage feature flags
-
-Starting from GitLab 9.3 we support feature flags for features in GitLab via
-[Flipper](https://github.com/jnunemaker/flipper/). You should use the `Feature`
-class (defined in `lib/feature.rb`) in your code to get, set and list feature
-flags.
-
-During runtime you can set the values for the gates via the
-[features API](../api/features.md) (accessible to admins only).
-
-## Feature groups
-
-Starting from GitLab 9.4 we support feature groups via
-[Flipper groups](https://github.com/jnunemaker/flipper/blob/v0.10.2/docs/Gates.md#2-group).
-
-Feature groups must be defined statically in `lib/feature.rb` (in the
-`.register_feature_groups` method), but their implementation can obviously be
-dynamic (querying the DB etc.).
-
-Once defined in `lib/feature.rb`, you will be able to activate a
-feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
-
-For GitLab.com, [team members have access to feature flags through Chatops](chatops_on_gitlabcom.md). Only
-percentage gates are supported at this time. Setting a feature to be used 50% of
-the time, you should execute `/chatops run feature set my_feature_flag 50`.
-
-## Feature flags for user applications
-
-This document only covers feature flags used in the development of GitLab
-itself. Feature flags in deployed user applications can be found at
-[Feature Flags](../user/project/operations/feature_flags.md)
-
-## Developing with feature flags
-
-In general, it's better to have a group- or user-based gate, and you should prefer
-it over the use of percentage gates. This would make debugging easier, as you
-filter for example logs and errors based on actors too. Furthermore, this allows
-for enabling for the `gitlab-org` group first, while the rest of the users
-aren't impacted.
-
-```ruby
-# Good
-Feature.enabled?(:feature_flag, project)
-
-# Avoid, if possible
-Feature.enabled?(:feature_flag)
-```
-
-To use feature gates based on actors, the model needs to respond to
-`flipper_id`. For example, to enable for the Foo model:
-
-```ruby
-class Foo < ActiveRecord::Base
- include FeatureGate
-end
-```
-
-Features that are developed and are intended to be merged behind a feature flag
-should not include a changelog entry. The entry should be added in the merge
-request removing the feature flags.
-
-In the rare case that you need the feature flag to be on automatically, use
-`default_enabled: true` when checking:
-
-```ruby
-Feature.enabled?(:feature_flag, project, default_enabled: true)
-```
-
-For more information about rolling out changes using feature flags, refer to the
-[Rolling out changes using feature flags](rolling_out_changes_using_feature_flags.md)
-guide.
-
-### Frontend
-
-For frontend code you can use the method `push_frontend_feature_flag`, which is
-available to all controllers that inherit from `ApplicationController`. Using
-this method you can expose the state of a feature flag as follows:
-
-```ruby
-before_action do
- push_frontend_feature_flag(:vim_bindings)
-end
-
-def index
- # ...
-end
-
-def edit
- # ...
-end
-```
-
-You can then check for the state of the feature flag in JavaScript as follows:
-
-```javascript
-if ( gon.features.vimBindings ) {
- // ...
-}
-```
-
-The name of the feature flag in JavaScript will always be camelCased, meaning
-that checking for `gon.features.vim_bindings` would not work.
-
-### Specs
-
-In the test environment `Feature.enabled?` is stubbed to always respond to `true`,
-so we make sure behavior under feature flag doesn't go untested in some non-specific
-contexts.
-
-Whenever a feature flag is present, make sure to test _both_ states of the
-feature flag.
-
-See the
-[testing guide](testing_guide/best_practices.md#feature-flags-in-tests)
-for information and examples on how to stub feature flags in tests.
-
-## Enabling a feature flag (in development)
-
-In the rails console (`rails c`), enter the following command to enable your feature flag
-
-```ruby
-Feature.enable(:feature_flag_name)
-```
-
-## Enabling a feature flag (in production)
-
-Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md).
+This document was moved to [another location](feature_flags/index.md).
diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md
new file mode 100644
index 00000000000..c67467b7c11
--- /dev/null
+++ b/doc/development/feature_flags/controls.md
@@ -0,0 +1,123 @@
+# Access for enabling a feature flag in production
+
+In order to be able to turn on/off features behind feature flags in any of the
+GitLab Inc. provided environments such as staging and production, you need to
+have access to the chatops bot. Chatops bot is currently running on the ops instance,
+which is different from GitLab.com or dev.gitlab.org.
+
+Follow the Chatops document to [request access](https://docs.gitlab.com/ee/development/chatops_on_gitlabcom.html#requesting-access).
+
+Once you are added to the project test if your access propagated,
+run:
+
+```
+/chatops run feature --help
+```
+
+## Rolling out changes
+
+When the changes are deployed to the environments it is time to start
+rolling out the feature to our users. The exact procedure of rolling out a
+change is unspecified, as this can vary from change to change. However, in
+general we recommend rolling out changes incrementally, instead of enabling them
+for everybody right away. We also recommend you to _not_ enable a feature
+_before_ the code is being deployed.
+This allows you to separate rolling out a feature from a deploy, making it
+easier to measure the impact of both separately.
+
+GitLab's feature library (using
+[Flipper](https://github.com/jnunemaker/flipper), and covered in the [Feature
+Flags process](process.md) guide) supports rolling out changes to a percentage of
+users. This in turn can be controlled using [GitLab chatops](../../ci/chatops/README.md).
+
+For an up to date list of feature flag commands please see [the source
+code](https://gitlab.com/gitlab-com/chatops/blob/master/lib/chatops/commands/feature.rb).
+Note that all the examples in that file must be preceded by
+`/chatops run`.
+
+If you get an error "Whoops! This action is not allowed. This incident
+will be reported." that means your Slack account is not allowed to
+change feature flags or you do not [have access](#access-for-enabling-a-feature-flag-in-production).
+
+### Enabling feature for staging and dev.gitlab.org
+
+As a first step in a feature rollout, you should enable the feature on <https://staging.gitlab.com>
+and <https://dev.gitlab.org>.
+
+For example, to enable a feature for 25% of all users, run the following in
+Slack:
+
+```
+/chatops run feature set new_navigation_bar 25 --dev
+/chatops run feature set new_navigation_bar 25 --staging
+```
+
+These two environments have different scopes.
+`dev.gitlab.org` is a production CE environment that has internal GitLab Inc.
+traffic and is used for some development and other related work.
+`staging.gitlab.com` has a smaller subset of GitLab.com database and repositories
+and does not have regular traffic. Staging is an EE instance and can give you
+a (very) rough estimate of how your feature will look/behave on GitLab.com.
+Both of these instances are connected to Sentry so make sure you check the projects
+there for any exceptions while testing your feature after enabling the feature flag.
+
+Once you are confident enough that these environments are in a good state with your
+feature enabled, you can roll out the change to GitLab.com.
+
+## Enabling feature for GitLab.com
+
+Similar to above, to enable a feature for 25% of all users, run the following in
+Slack:
+
+```
+/chatops run feature set new_navigation_bar 25
+```
+
+This will enable the feature for GitLab.com, with `new_navigation_bar` being the
+name of the feature.
+
+If you are not certain what percentages to use, simply use the following steps:
+
+1. 25%
+1. 50%
+1. 75%
+1. 100%
+
+Between every step you'll want to wait a little while and monitor the
+appropriate graphs on <https://dashboards.gitlab.net>. The exact time to wait
+may differ. For some features a few minutes is enough, while for others you may
+want to wait several hours or even days. This is entirely up to you, just make
+sure it is clearly communicated to your team, and the Production team if you
+anticipate any potential problems.
+
+Feature gates can also be actor based, for example a feature could first be
+enabled for only the `gitlab-ce` project. The project is passed by supplying a
+`--project` flag:
+
+```
+/chatops run feature set --project=gitlab-org/gitlab-ce some_feature true
+```
+
+For groups the `--group` flag is available:
+
+```
+/chatops run feature set --group=gitlab-org some_feature true
+```
+
+## Cleaning up
+
+Once the change is deemed stable, submit a new merge request to remove the
+feature flag. This ensures the change is available to all users and self-hosted
+instances. Make sure to add the ~"feature flag" label to this merge request so
+release managers are aware the changes are hidden behind a feature flag. If the
+merge request has to be picked into a stable branch, make sure to also add the
+appropriate "Pick into X" label (e.g. "Pick into XX.X").
+See [the process document](https://docs.gitlab.com/ee/development/feature_flags/process.html#including-a-feature-behind-feature-flag-in-the-final-release) for further details.
+
+When a feature gate has been removed from the code base, the value still exists
+in the database.
+This can be removed through ChatOps:
+
+```
+/chatops run feature delete some_feature
+```
diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md
new file mode 100644
index 00000000000..238052529d9
--- /dev/null
+++ b/doc/development/feature_flags/development.md
@@ -0,0 +1,131 @@
+# Developing with feature flags
+
+In general, it's better to have a group- or user-based gate, and you should prefer
+it over the use of percentage gates. This would make debugging easier, as you
+filter for example logs and errors based on actors too. Furthermore, this allows
+for enabling for the `gitlab-org` or `gitlab-com` group first, while the rest of
+the users aren't impacted.
+
+```ruby
+# Good
+Feature.enabled?(:feature_flag, project)
+
+# Avoid, if possible
+Feature.enabled?(:feature_flag)
+```
+
+To use feature gates based on actors, the model needs to respond to
+`flipper_id`. For example, to enable for the Foo model:
+
+```ruby
+class Foo < ActiveRecord::Base
+ include FeatureGate
+end
+```
+
+Features that are developed and are intended to be merged behind a feature flag
+should not include a changelog entry. The entry should be added in the merge
+request removing the feature flags.
+
+In the rare case that you need the feature flag to be on automatically, use
+`default_enabled: true` when checking:
+
+```ruby
+Feature.enabled?(:feature_flag, project, default_enabled: true)
+```
+
+The [`Project#feature_available?`][project-fa],
+[`Namespace#feature_available?`][namespace-fa] (EE), and
+[`License.feature_available?`][license-fa] (EE) methods all implicitly check for
+a feature flag by the same name as the provided argument.
+
+For example if a feature is license-gated, there's no need to add an additional
+explicit feature flag check since the flag will be checked as part of the
+`License.feature_available?` call. Similarly, there's no need to "clean up" a
+feature flag once the feature has reached general availability.
+
+You'd still want to use an explicit `Feature.enabled?` check if your new feature
+isn't gated by a License or Plan.
+
+[project-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68
+[namespace-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85
+[license-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300
+
+An important side-effect of the implicit feature flags mentioned above is that
+unless the feature is explicitly disabled or limited to a percentage of users,
+the feature flag check will default to `true`.
+
+As an example, if you were to ship the backend half of a feature behind a flag,
+you'd want to explicitly disable that flag until the frontend half is also ready
+to be shipped. [You can do this via Chatops](https://docs.gitlab.com/ee/development/feature_flags/controls.html):
+
+```
+/chatops run feature set some_feature 0
+```
+
+Note that you can do this at any time, even before the merge request using the
+flag has been merged!
+
+## Feature groups
+
+Starting from GitLab 9.4 we support feature groups via
+[Flipper groups](https://github.com/jnunemaker/flipper/blob/v0.10.2/docs/Gates.md#2-group).
+
+Feature groups must be defined statically in `lib/feature.rb` (in the
+`.register_feature_groups` method), but their implementation can obviously be
+dynamic (querying the DB etc.).
+
+Once defined in `lib/feature.rb`, you will be able to activate a
+feature for a given feature group via the [`feature_group` param of the features API](../../api/features.md#set-or-create-a-feature)
+
+### Frontend
+
+For frontend code you can use the method `push_frontend_feature_flag`, which is
+available to all controllers that inherit from `ApplicationController`. Using
+this method you can expose the state of a feature flag as follows:
+
+```ruby
+before_action do
+ push_frontend_feature_flag(:vim_bindings)
+end
+
+def index
+ # ...
+end
+
+def edit
+ # ...
+end
+```
+
+You can then check for the state of the feature flag in JavaScript as follows:
+
+```javascript
+if ( gon.features.vimBindings ) {
+ // ...
+}
+```
+
+The name of the feature flag in JavaScript will always be camelCased, meaning
+that checking for `gon.features.vim_bindings` would not work.
+
+### Specs
+
+In the test environment `Feature.enabled?` is stubbed to always respond to `true`,
+so we make sure behavior under feature flag doesn't go untested in some non-specific
+contexts.
+
+Whenever a feature flag is present, make sure to test _both_ states of the
+feature flag.
+
+See the
+[testing guide](../testing_guide/best_practices.md#feature-flags-in-tests)
+for information and examples on how to stub feature flags in tests.
+
+### Enabling a feature flag (in development)
+
+In the rails console (`rails c`), enter the following command to enable your feature flag
+
+```ruby
+Feature.enable(:feature_flag_name)
+```
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
new file mode 100644
index 00000000000..56872f8c075
--- /dev/null
+++ b/doc/development/feature_flags/index.md
@@ -0,0 +1,12 @@
+# Feature flags in development of GitLab
+
+Feature flags can be used to gradually roll out changes, be
+it a new feature, or a performance improvement. By using feature flags, we can
+comfortably measure the impact of our changes, while still being able to easily
+disable those changes, without having to revert an entire release.
+
+Before using feature flags for GitLab's development, read through the following:
+
+- [Process for using features flags](process.md).
+- [Developing with feature flags documentation](development.md).
+- [Controlling feature flags documentation](controls.md).
diff --git a/doc/development/feature_flags/process.md b/doc/development/feature_flags/process.md
new file mode 100644
index 00000000000..ee142b0da66
--- /dev/null
+++ b/doc/development/feature_flags/process.md
@@ -0,0 +1,130 @@
+# Feature flags process
+## Feature flags for user applications
+
+This document only covers feature flags used in the development of GitLab
+itself. Feature flags in deployed user applications can be found at
+[Feature Flags feature documentation](../../user/project/operations/feature_flags.md).
+
+## Feature flags in GitLab development
+
+The following highlights should be considered when deciding if feature flags
+should be leveraged:
+
+- By default, the feature flags should be **off**.
+- Feature flags should remain in the codebase for as short period as possible
+to reduce the need for feature flag accounting.
+- The person operating with feature flags is responsible for clearly communicating
+the status of a feature behind the feature flag with responsible stakeholders.
+- Merge requests that make changes hidden behind a feature flag, or remove an
+existing feature flag because a feature is deemed stable must have the
+~"feature flag" label assigned.
+
+One might be tempted to think that feature flags will delay the release of a
+feature by at least one month (= one release). This is not the case. A feature
+flag does not have to stick around for a specific amount of time
+(e.g. at least one release), instead they should stick around until the feature
+is deemed stable. Stable means it works on GitLab.com without causing any
+problems, such as outages.
+
+### When to use feature flags
+
+Starting with GitLab 11.4, developers are required to use feature flags for
+non-trivial changes. Such changes include:
+
+- New features (e.g. a new merge request widget, epics, etc).
+- Complex performance improvements that may require additional testing in
+ production, such as rewriting complex queries.
+- Invasive changes to the user interface, such as a new navigation bar or the
+ removal of a sidebar.
+- Adding support for importing projects from a third-party service.
+
+In all cases, those working on the changes can best decide if a feature flag is
+necessary. For example, changing the color of a button doesn't need a feature
+flag, while changing the navigation bar definitely needs one. In case you are
+uncertain if a feature flag is necessary, simply ask about this in the merge
+request, and those reviewing the changes will likely provide you with an answer.
+
+When using a feature flag for UI elements, make sure to _also_ use a feature
+flag for the underlying backend code, if there is any. This ensures there is
+absolutely no way to use the feature until it is enabled.
+
+### Including a feature behind feature flag in the final release
+
+In order to build a final release and present the feature for self-hosted
+users, the feature flag should be at least defaulted to **on**. If the feature
+is deemed stable and there is confidence that removing the feature flag is safe,
+consider removing the feature flag altogether. Take into consideration that such
+action can make the feature available on GitLab.com shortly after the change to
+the feature flag is merged.
+
+Changing the default state or removing the feature flag has to be done before
+the 22nd of the month, _at least_ 2 working days before, in order for the change
+to be included in the final self-managed release.
+
+In addition to this, the feature behind feature flag should:
+
+- Run in all GitLab.com environments for a sufficient period of time. This time
+period depends on the feature behind the feature flag, but as a general rule of
+thumb 2-4 working days should be sufficient to gather enough feedback.
+- The feature should be exposed to all users within the GitLab.com plan during
+the above mentioned period of time. Exposing the feature to a smaller percentage
+or only a group of users might not expose a sufficient amount of information to aid in
+making a decision on feature stability.
+
+While rare, release managers may decide to reject picking or revert a change in
+a stable branch, even when feature flags are used. This might be necessary if
+the changes are deemed problematic, too invasive, or there simply isn't enough
+time to properly measure how the changes behave on GitLab.com.
+
+### The cost of feature flags
+
+When reading the above, one might be tempted to think this procedure is going to
+add a lot of work. Fortunately, this is not the case, and we'll show why. For
+this example we'll specify the cost of the work to do as a number, ranging from
+0 to infinity. The greater the number, the more expensive the work is. The cost
+does _not_ translate to time, it's just a way of measuring complexity of one
+change relative to another.
+
+Let's say we are building a new feature, and we have determined that the cost of
+this is 10. We have also determined that the cost of adding a feature flag check
+in a variety of places is 1. If we do not use feature flags, and our feature
+works as intended, our total cost is 10. This however is the best case scenario.
+Optimizing for the best case scenario is guaranteed to lead to trouble, whereas
+optimizing for the worst case scenario is almost always better.
+
+To illustrate this, let's say our feature causes an outage, and there's no
+immediate way to resolve it. This means we'd have to take the following steps to
+resolve the outage:
+
+1. Revert the release.
+1. Perform any cleanups that might be necessary, depending on the changes that
+ were made.
+1. Revert the commit, ensuring the "master" branch remains stable. This is
+ especially necessary if solving the problem can take days or even weeks.
+1. Pick the revert commit into the appropriate stable branches, ensuring we
+ don't block any future releases until the problem is resolved.
+
+As history has shown, these steps are time consuming, complex, often involve
+many developers, and worst of all: our users will have a bad experience using
+GitLab.com until the problem is resolved.
+
+Now let's say that all of this has an associated cost of 10. This means that in
+the worst case scenario, which we should optimize for, our total cost is now 20.
+
+If we had used a feature flag, things would have been very different. We don't
+need to revert a release, and because feature flags are disabled by default we
+don't need to revert and pick any Git commits. In fact, all we have to do is
+disable the feature, and in the worst case, perform cleanup. Let's say that
+the cost of this is 2. In this case, our best case cost is 11: 10 to build the
+feature, and 1 to add the feature flag. The worst case cost is now 13: 10 to
+build the feature, 1 to add the feature flag, and 2 to disable and clean up.
+
+Here we can see that in the best case scenario the work necessary is only a tiny
+bit more compared to not using a feature flag. Meanwhile, the process of
+reverting our changes has been made significantly and reliably cheaper.
+
+In other words, feature flags do not slow down the development process. Instead,
+they speed up the process as managing incidents now becomes _much_ easier. Once
+continuous deployments are easier to perform, the time to iterate on a feature
+is reduced even further, as you no longer need to wait weeks before your changes
+are available on GitLab.com.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index ce310672dad..17462887162 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -135,7 +135,7 @@ There is also and alternative method to [translate messages from validation erro
### Interpolation
Placeholders in translated text should match the code style of the respective source file.
-For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to [avoid splitting sentences when adding links](#avoid-splitting-sentences-when-adding-links).
- In Ruby/HAML:
@@ -267,20 +267,41 @@ should be externalized as follows:
This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
-Instead of:
+- In Ruby/HAML, instead of:
+
+ ```haml
+ - zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
+ ```
+
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```haml
+ - zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
+ - zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
+ = s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
+ ```
-```haml
-- zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
-= s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
-```
+- In JavaScript, instead of:
-Set the link starting and ending HTML fragments as variables like so:
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
+ link: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">zones</a>'
+ })
+ }}
+ ```
-```haml
-- zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
-- zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
-= s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
-```
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), {
+ linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">'
+ linkEnd: '</a>',
+ })
+ }}
+ ```
The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index 4564f678ec0..879b54bd93c 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -10,16 +10,16 @@ yarn clean
## Creating feature flags in development
-The process for creating a feature flag is the same as [enabling a feature flag in development](../feature_flags.md#enabling-a-feature-flag-in-development).
+The process for creating a feature flag is the same as [enabling a feature flag in development](../feature_flags/development.md#enabling-a-feature-flag-in-development).
Your feature flag can now be:
-- [made available to the frontend](../feature_flags.md#frontend) via the `gon`
-- queried in [tests](../feature_flags.md#specs)
-- queried in HAML templates and ruby files via the `Feature.enabled?(:my_shiny_new_feature_flag)` method
+- [Made available to the frontend](../feature_flags/development.md#frontend) via the `gon`
+- Queried in [tests](../feature_flags/development.md#specs)
+- Queried in HAML templates and ruby files via the `Feature.enabled?(:my_shiny_new_feature_flag)` method
### More on feature flags
- [Deleting a feature flag](../../api/features.md#delete-a-feature)
-- [Manage feature flags](../feature_flags.md)
+- [Manage feature flags](../feature_flags/process.md)
- [Feature flags API](../../api/features.md)
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index 84028b1b342..6bad91d6287 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -1,225 +1 @@
-# Rolling out changes using feature flags
-
-[Feature flags](feature_flags.md) can be used to gradually roll out changes, be
-it a new feature, or a performance improvement. By using feature flags, we can
-comfortably measure the impact of our changes, while still being able to easily
-disable those changes, without having to revert an entire release.
-
-## When to use feature flags
-
-Starting with GitLab 11.4, developers are required to use feature flags for
-non-trivial changes. Such changes include:
-
-- New features (e.g. a new merge request widget, epics, etc).
-- Complex performance improvements that may require additional testing in
- production, such as rewriting complex queries.
-- Invasive changes to the user interface, such as a new navigation bar or the
- removal of a sidebar.
-- Adding support for importing projects from a third-party service.
-
-In all cases, those working on the changes can best decide if a feature flag is
-necessary. For example, changing the color of a button doesn't need a feature
-flag, while changing the navigation bar definitely needs one. In case you are
-uncertain if a feature flag is necessary, simply ask about this in the merge
-request, and those reviewing the changes will likely provide you with an answer.
-
-When using a feature flag for UI elements, make sure to _also_ use a feature
-flag for the underlying backend code, if there is any. This ensures there is
-absolutely no way to use the feature until it is enabled.
-
-## The cost of feature flags
-
-When reading the above, one might be tempted to think this procedure is going to
-add a lot of work. Fortunately, this is not the case, and we'll show why. For
-this example we'll specify the cost of the work to do as a number, ranging from
-0 to infinity. The greater the number, the more expensive the work is. The cost
-does _not_ translate to time, it's just a way of measuring complexity of one
-change relative to another.
-
-Let's say we are building a new feature, and we have determined that the cost of
-this is 10. We have also determined that the cost of adding a feature flag check
-in a variety of places is 1. If we do not use feature flags, and our feature
-works as intended, our total cost is 10. This however is the best case scenario.
-Optimising for the best case scenario is guaranteed to lead to trouble, whereas
-optimising for the worst case scenario is almost always better.
-
-To illustrate this, let's say our feature causes an outage, and there's no
-immediate way to resolve it. This means we'd have to take the following steps to
-resolve the outage:
-
-1. Revert the release.
-1. Perform any cleanups that might be necessary, depending on the changes that
- were made.
-1. Revert the commit, ensuring the "master" branch remains stable. This is
- especially necessary if solving the problem can take days or even weeks.
-1. Pick the revert commit into the appropriate stable branches, ensuring we
- don't block any future releases until the problem is resolved.
-
-As history has shown, these steps are time consuming, complex, often involve
-many developers, and worst of all: our users will have a bad experience using
-GitLab.com until the problem is resolved.
-
-Now let's say that all of this has an associated cost of 10. This means that in
-the worst case scenario, which we should optimise for, our total cost is now 20.
-
-If we had used a feature flag, things would have been very different. We don't
-need to revert a release, and because feature flags are disabled by default we
-don't need to revert and pick any Git commits. In fact, all we have to do is
-disable the feature, and in the worst case, perform cleanup. Let's say that
-the cost of this is 2. In this case, our best case cost is 11: 10 to build the
-feature, and 1 to add the feature flag. The worst case cost is now 13: 10 to
-build the feature, 1 to add the feature flag, and 2 to disable and clean up.
-
-Here we can see that in the best case scenario the work necessary is only a tiny
-bit more compared to not using a feature flag. Meanwhile, the process of
-reverting our changes has been made significantly and reliably cheaper.
-
-In other words, feature flags do not slow down the development process. Instead,
-they speed up the process as managing incidents now becomes _much_ easier. Once
-continuous deployments are easier to perform, the time to iterate on a feature
-is reduced even further, as you no longer need to wait weeks before your changes
-are available on GitLab.com.
-
-## Rolling out changes
-
-The procedure of using feature flags is straightforward, and similar to not
-using them. You add the necessary tests (make sure to test both the on and off
-states of your feature flag(s)), make sure they all pass, have the code
-reviewed, etc. You then submit your merge request, and add the ~"feature flag"
-label. This label is used to signal to release managers that your changes are
-hidden behind a feature flag and that it is safe to pick the MR into a stable
-branch, without the need for an exception request.
-
-When the changes are deployed it is time to start rolling out the feature to our
-users. The exact procedure of rolling out a change is unspecified, as this can
-vary from change to change. However, in general we recommend rolling out changes
-incrementally, instead of enabling them for everybody right away. We also
-recommend you to _not_ enable a feature _before_ the code is being deployed.
-This allows you to separate rolling out a feature from a deploy, making it
-easier to measure the impact of both separately.
-
-GitLab's feature library (using
-[Flipper](https://github.com/jnunemaker/flipper), and covered in the [Feature
-Flags](feature_flags.md) guide) supports rolling out changes to a percentage of
-users. This in turn can be controlled using [GitLab
-chatops](../ci/chatops/README.md).
-
-For an up to date list of feature flag commands please see [the source
-code](https://gitlab.com/gitlab-com/chatops/blob/master/lib/chatops/commands/feature.rb).
-Note that all the examples in that file must be preceded by
-`/chatops run`.
-
-If you get an error "Whoops! This action is not allowed. This incident
-will be reported." that means your Slack account is not allowed to
-change feature flags. To test if you are allowed to do anything at all,
-run:
-
-```
-/chatops run feature --help
-```
-
-For example, to enable a feature for 25% of all users, run the following in
-Slack:
-
-```
-/chatops run feature set new_navigation_bar 25
-```
-
-This will enable the feature for GitLab.com, with `new_navigation_bar` being the
-name of the feature. We can also enable the feature for <https://dev.gitlab.org>
-or <https://staging.gitlab.com>:
-
-```
-/chatops run feature set new_navigation_bar 25 --dev
-/chatops run feature set new_navigation_bar 25 --staging
-```
-
-If you are not certain what percentages to use, simply use the following steps:
-
-1. 25%
-1. 50%
-1. 75%
-1. 100%
-
-Between every step you'll want to wait a little while and monitor the
-appropriate graphs on <https://dashboards.gitlab.net>. The exact time to wait
-may differ. For some features a few minutes is enough, while for others you may
-want to wait several hours or even days. This is entirely up to you, just make
-sure it is clearly communicated to your team, and the Production team if you
-anticipate any potential problems.
-
-Feature gates can also be actor based, for example a feature could first be
-enabled for only the `gitlab-ce` project. The project is passed by supplying a
-`--project` flag:
-
-```
-/chatops run feature set --project=gitlab-org/gitlab-ce some_feature true
-```
-
-For groups the `--group` flag is available:
-
-```
-/chatops run feature set --group=gitlab-org some_feature true
-```
-
-Once a change is deemed stable, submit a new merge request to remove the
-feature flag. This ensures the change is available to all users and self-hosted
-instances. Make sure to add the ~"feature flag" label to this merge request so
-release managers are aware the changes are hidden behind a feature flag. If the
-merge request has to be picked into a stable branch (e.g. after the 7th), make
-sure to also add the appropriate "Pick into X" label (e.g. "Pick into 11.4").
-
-One might be tempted to think this will delay the release of a feature by at
-least one month (= one release). This is not the case. A feature flag does not
-have to stick around for a specific amount of time (e.g. at least one release),
-instead they should stick around until the feature is deemed stable. Stable
-means it works on GitLab.com without causing any problems, such as outages. In
-most cases this will translate to a feature (with a feature flag) being shipped
-in RC1, followed by the feature flag being removed in RC2. This in turn means
-the feature will be stable by the time we publish a stable package around the
-22nd of the month.
-
-## Implicit feature flags
-
-The [`Project#feature_available?`][project-fa],
-[`Namespace#feature_available?`][namespace-fa] (EE), and
-[`License.feature_available?`][license-fa] (EE) methods all implicitly check for
-a feature flag by the same name as the provided argument.
-
-For example if a feature is license-gated, there's no need to add an additional
-explicit feature flag check since the flag will be checked as part of the
-`License.feature_available?` call. Similarly, there's no need to "clean up" a
-feature flag once the feature has reached general availability.
-
-You'd still want to use an explicit `Feature.enabled?` check if your new feature
-isn't gated by a License or Plan.
-
-[project-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68
-[namespace-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85
-[license-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300
-
-### Undefined feature flags default to "on"
-
-An important side-effect of the [implicit feature flags](#implicit-feature-flags)
-mentioned above is that unless the feature is explicitly disabled or limited to a
-percentage of users, the feature flag check will default to `true`.
-
-As an example, if you were to ship the backend half of a feature behind a flag,
-you'd want to explicitly disable that flag until the frontend half is also ready
-to be shipped. You can do this via ChatOps:
-
-```
-/chatops run feature set some_feature 0
-```
-
-Note that you can do this at any time, even before the merge request using the
-flag has been merged!
-
-### Cleaning up
-
-When a feature gate has been removed from the code base, the value still exists
-in the database. This can be removed through ChatOps:
-
-```
-/chatops run feature delete some_feature
-```
+This document was moved to [another location](feature_flags/index.md).
diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
index f96c85be1ba..041bdf716b3 100644
--- a/doc/development/testing_guide/end_to_end/quick_start_guide.md
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -242,7 +242,7 @@ module QA
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = 'Issue to test the scoped labels'
- issue.labels = @initial_label
+ issue.labels = [@initial_label]
end
[@new_label_same_scope, @new_label_different_scope].each do |label|
@@ -365,6 +365,14 @@ Add the following `attribute :id` and `attribute :labels` right above the [`attr
> We add the attributes above the existing attribute to keep them alphabetically organized.
+Then, let's initialize an instance variable for labels to allow an empty array as default value when such information is not passed during the resource fabrication, since this optional. [Between the attributes and the `fabricate!` method](https://gitlab.com/gitlab-org/gitlab-ee/blob/1a1f1408728f19b2aa15887cd20bddab7e70c8bd/qa/qa/resource/issue.rb#L18), add the following:
+
+```ruby
+def initialize
+ @labels = []
+end
+```
+
Next, add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L27) method.
```ruby
@@ -378,7 +386,7 @@ end
def api_post_body
{
- labels: [labels],
+ labels: labels,
title: title
}
end
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index 1cf883679d7..b7e6844f43a 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -29,7 +29,9 @@ git clone PASTE HTTPS OR SSH HERE
A clone of the project will be created in your computer.
->**Note:** If you clone your project via a URL that contains special characters, make sure that characters are URL-encoded.
+NOTE: **Note:**
+If you clone your project via a URL that contains special characters, make sure
+that characters are URL-encoded.
### Go into a project directory to work in it
@@ -86,12 +88,18 @@ cat README.md
### Remove a file
+DANGER: **Danger:**
+This will permanently delete the file.
+
```
rm NAME-OF-FILE
```
### Remove a directory and all of its contents
+DANGER: **Danger:**
+This will permanently delete the directory and all of its contents.
+
```
rm -r NAME-OF-DIRECTORY
```
@@ -113,9 +121,13 @@ history
You will be asked for an administrator’s password.
```
-sudo
+sudo COMMAND
```
+CAUTION: **Caution:**
+Be careful of the commands you run with `sudo`. Certain commands may cause
+damage to your data and system.
+
### Show which directory I am in
```
diff --git a/doc/topics/application_development_platform/index.md b/doc/topics/application_development_platform/index.md
index e8960a76dd9..8742606479d 100644
--- a/doc/topics/application_development_platform/index.md
+++ b/doc/topics/application_development_platform/index.md
@@ -1,12 +1,21 @@
# Application Development Platform
-The GitLab Application Development Platform refers to the set of GitLab features that can be used by operations teams to
-provide a full development environment to internal software development teams.
+The GitLab Application Development Platform refers to the set of GitLab features used to create, configure, and manage
+a complete software development environment. It provides development, operations, and security teams with a robust feature set aimed at supporting best practices out of the box.
## Overview
-The GitLab Application Development Platform aims to reduce and even eliminate the time it takes for an Operations team
-to provide a full environment for software developers. It comprises the following high-level elements:
+The GitLab Application Development Platform aims to:
+
+- Reduce and even eliminate the time it takes for an Operations team
+ to provide a full environment for software developers.
+- Get developers up and running fast so they can focus on writing
+ great applications with a robust development feature set.
+- Provide best-of-breed security features so that applications developed
+ with GitLab are not affected by vulnerabilities that may lead to security
+ problems and unintended use.
+
+It is comprised of the following high-level elements:
1. Compute
1. Build, test, and deploy a wide range of applications
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index fa60ee96cf7..d2947ae3371 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -21,7 +21,7 @@ The Admin Area is made up of the following sections:
| Section | Description |
|:------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Overview](#overview-section) | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [jobs](#administering-jobs), [Runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). |
-| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
+| Monitoring | View GitLab [system information](#system-info), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), [requests profiles](#requests-profiles), and [audit logs](#audit-log-premium-only). |
| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
@@ -229,3 +229,66 @@ For each Gitaly server, the following details are listed:
| Server version | Gitaly version |
| Git version | Version of Git installed on the Gitaly server |
| Up to date | Indicates if the Gitaly server version is the latest version available. A green dot indicates the server is up to date. |
+
+## Monitoring section
+
+The following topics document the **Monitoring** section of the Admin Area.
+
+### System Info
+
+The **System Info** page provides the following statistics:
+
+| Field | Description |
+| :----------- | :---------- |
+| CPU | Number of CPU cores available |
+| Memory Usage | Memory in use, and total memory available |
+| Disk Usage | Disk space in use, and total disk space available |
+| Uptime | Approximate uptime of the GitLab instance |
+
+These statistics are updated only when you navigate to the **System Info** page, or you refresh the page in your browser.
+
+### Background Jobs
+
+The **Background Jobs** page displays the Sidekiq dashboard. Sidekiq is used by GitLab to
+perform processing in the background.
+
+The Sidekiq dashboard consists of the following elements:
+
+- A tab per jobs' status.
+- A breakdown of background job statistics.
+- A live graph of **Processed** and **Failed** jobs, with a selectable polling interval.
+- An historical graph of **Processed** and **Failed** jobs, with a selectable time span.
+- Redis statistics, including:
+ - Version number
+ - Uptime, measured in days
+ - Number of connections
+ - Current memory usage, measured in MB
+ - Peak memory usage, measured in MB
+
+### Logs
+
+The **Logs** page provides access to the following log files:
+
+| Log file | Contents |
+| :---------------------- | :------- |
+| `application.log` | GitLab user activity |
+| `githost.log` | Failed GitLab interaction with Git repositories |
+| `production.log` | Requests received from Unicorn, and the actions taken to serve those requests |
+| `sidekiq.log` | Background jobs |
+| `repocheck.log` | Repository activity |
+| `integrations_json.log` | Activity between GitLab and integrated systems |
+| `kubernetes.log` | Kubernetes activity |
+
+The contents of these log files can be useful when troubleshooting a problem. Access is available to GitLab admins, without requiring direct access to the log files.
+
+For details of these log files and their contents, see [Log system](../../administration/logs.md).
+
+The content of each log file is listed in chronological order. To minimize performance issues, a maximum 2000 lines of each log file are shown.
+
+### Requests Profiles
+
+The **Requests Profiles** page contains the token required for profiling. For more details, see [Request Profiling](../../administration/monitoring/performance/request_profiling.md).
+
+### Audit Log **[PREMIUM ONLY]**
+
+The **Audit Log** page lists changes made within the GitLab server. With this information you can control, analyze, and track every change.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 97d2dfc0f7e..c6ee168bad0 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -533,22 +533,20 @@ This job failed because the necessary resources were not successfully created.
To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#kuberneteslog).
-NOTE: **NOTE:**
-As of GitLab 12.1 we require [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
-tokens for all project level clusters unless you unselect the
-[GitLab-managed cluster](#gitlab-managed-clusters) option. If you
-want to manage namespaces and service accounts yourself and don't
-want to provide a `cluster-admin` token to GitLab you must unselect this
-option or you will get the above error.
-
-Common reasons for failure include:
+Reasons for failure include:
-- The token you gave GitLab did not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+- The token you gave GitLab does not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
privileges required by GitLab.
- Missing `KUBECONFIG` or `KUBE_TOKEN` variables. To be passed to your job, they must have a matching
[`environment:name`](../../../ci/environments.md#defining-environments). If your job has no
`environment:name` set, it will not be passed the Kubernetes credentials.
+NOTE: **NOTE:**
+Project-level clusters upgraded from GitLab 12.0 or older may be configured
+in a way that causes this error. Ensure you deselect the
+[GitLab-managed cluster](#gitlab-managed-clusters) option if you want to manage
+namespaces and service accounts yourself.
+
## Monitoring your Kubernetes cluster **[ULTIMATE]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
diff --git a/doc/user/project/clusters/serverless/img/function-endpoint.png b/doc/user/project/clusters/serverless/img/function-endpoint.png
new file mode 100644
index 00000000000..f3c7ae7a00d
--- /dev/null
+++ b/doc/user/project/clusters/serverless/img/function-endpoint.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 3be5bfeaddc..91f0e24b44e 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -325,3 +325,275 @@ loading or is not available at this time._ It will appear upon the first access
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
+must manually obtain and install TLS certificates.
+
+The simplest way to accomplish this is to
+use [Certbot to manually obtain Let's Encrypt certificates](https://knative.dev/docs/serving/using-a-tls-cert/#using-certbot-to-manually-obtain-let-s-encrypt-certificates). Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated websites to enable HTTPS.
+
+NOTE: **Note:**
+The instructions below relate to installing and running Certbot on a Linux server and may not work on other operating systems.
+
+1. Install Certbot by running the
+ [`certbot-auto` wrapper script](https://certbot.eff.org/docs/install.html#certbot-auto).
+ On the command line of your server, run the following commands:
+
+ ```sh
+ wget https://dl.eff.org/certbot-auto
+ sudo mv certbot-auto /usr/local/bin/certbot-auto
+ sudo chown root /usr/local/bin/certbot-auto
+ chmod 0755 /usr/local/bin/certbot-auto
+ /usr/local/bin/certbot-auto --help
+ ```
+
+ To check the integrity of the `certbot-auto` script, run:
+
+ ```sh
+ wget -N https://dl.eff.org/certbot-auto.asc
+ gpg2 --keyserver ipv4.pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
+ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto
+ ```
+
+ The output of the last command should look something like:
+
+ ```sh
+ gpg: Signature made Mon 10 Jun 2019 06:24:40 PM EDT
+ gpg: using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2
+ gpg: key 4D17C995CD9775F2 marked as ultimately trusted
+ gpg: checking the trustdb
+ gpg: marginals needed: 3 completes needed: 1 trust model: pgp
+ gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
+ gpg: next trustdb check due at 2027-11-22
+ gpg: Good signature from "Let's Encrypt Client Team <letsencrypt-client@eff.org>" [ultimate]
+ ```
+
+1. Run the following command to use Certbot to request a certificate
+ using DNS challenge during authorization:
+
+
+ ```sh
+ ./certbot-auto certonly --manual --preferred-challenges dns -d '*.<namespace>.example.com'
+ ```
+
+ Where `<namespace>` is the namespace created by GitLab for your serverless project (composed of `<projectname+id>`) and
+ `example.com` is the domain being used for your project. If you are unsure what the namespace of your project is, navigate
+ to the **Operations > Serverless** page of your project and inspect
+ the endpoint provided for your function/app.
+
+ ![function_endpoint](img/function-endpoint.png)
+
+ In the above image, the namespace for the project is `node-function-11909507` and the domain is `knative.info`, thus
+ certificate request line would look like this:
+
+ ```sh
+ ./certbot-auto certonly --manual --preferred-challenges dns -d '*.node-function-11909507.knative.info'
+ ```
+
+ The Certbot tool walks you through the steps of validating that you own each domain that you specify by creating TXT records in those domains.
+ After this process is complete, the output should look something like this:
+
+ ```sh
+ IMPORTANT NOTES:
+ - Congratulations! Your certificate and chain have been saved at:
+ /etc/letsencrypt/live/namespace.example.com/fullchain.pem
+ Your key file has been saved at:
+ /etc/letsencrypt/live/namespace.example/privkey.pem
+ Your cert will expire on 2019-09-19. To obtain a new or tweaked
+ version of this certificate in the future, simply run certbot-auto
+ again. To non-interactively renew *all* of your certificates, run
+ "certbot-auto renew"
+ -----BEGIN PRIVATE KEY-----
+ - Your account credentials have been saved in your Certbot
+ configuration directory at /etc/letsencrypt. You should make a
+ secure backup of this folder now. This configuration directory will
+ also contain certificates and private keys obtained by Certbot so
+ making regular backups of this folder is ideal.
+ ```
+
+1. Create certificate and private key files. Using the contents of the files
+ returned by Certbot, we'll create two files in order to create the
+ Kubernetes secret:
+
+ Run the following command to see the contents of `fullchain.pem`:
+
+ ```sh
+ sudo cat /etc/letsencrypt/live/node-function-11909507.knative.info/fullchain.pem
+ ```
+
+ Output should look like this:
+
+ ```sh
+ -----BEGIN CERTIFICATE-----
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b4ag==
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ K2fcb195768c39e9a94cec2c2e30Qg==
+ -----END CERTIFICATE-----
+ ```
+
+ Create a file with the name `cert.pem` with the contents of the entire output.
+
+ Once `cert.pem` is created, run the following command to see the contents of `privkey.pem`:
+
+ ```sh
+ sudo cat /etc/letsencrypt/live/namespace.example/privkey.pem
+ ```
+
+ Output should look like this:
+
+ ```sh
+ -----BEGIN PRIVATE KEY-----
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ -----BEGIN CERTIFICATE-----
+ fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 4f294d1eaca42b8692017b4262==
+ -----END PRIVATE KEY-----
+ ```
+
+ Create a new file with the name `cert.pk` with the contents of the entire output.
+
+1. Create a Kubernetes secret to hold your TLS certificate, `cert.pem`, and
+ the private key `cert.pk`:
+
+ NOTE: **Note:**
+ Running `kubectl` commands on your cluster requires setting up access to the cluster first.
+ For clusters created on GKE, see
+ [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl).
+ For other platforms, [install `kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
+
+ ```sh
+ kubectl create --namespace istio-system secret tls istio-ingressgateway-certs \
+ --key cert.pk \
+ --cert cert.pem
+ ```
+
+ Where `cert.pem` and `cert.pk` are your certificate and private key files. Note that the `istio-ingressgateway-certs` secret name is required.
+
+1. Configure Knative to use the new secret that you created for HTTPS
+ connections. Run the
+ following command to open the Knative shared `gateway` in edit mode:
+
+ ```sh
+ kubectl edit gateway knative-ingress-gateway --namespace knative-serving
+ ```
+
+ Update the gateway to include the following tls: section and configuration:
+
+ ```sh
+ tls:
+ mode: SIMPLE
+ privateKey: /etc/istio/ingressgateway-certs/tls.key
+ serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
+ ```
+
+ Example:
+
+ ```sh
+ apiVersion: networking.istio.io/v1alpha3
+ kind: Gateway
+ metadata:
+ # ... skipped ...
+ spec:
+ selector:
+ istio: ingressgateway
+ servers:
+ - hosts:
+ - "*"
+ port:
+ name: http
+ number: 80
+ protocol: HTTP
+ - hosts:
+ - "*"
+ port:
+ name: https
+ number: 443
+ protocol: HTTPS
+ tls:
+ mode: SIMPLE
+ privateKey: /etc/istio/ingressgateway-certs/tls.key
+ serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
+ ```
+
+ After your changes are running on your Knative cluster, you can begin using the HTTPS protocol for secure access your deployed Knative services.
+ In the event a mistake is made during this process and you need to update the cert, you will need to edit the gateway `knative-ingress-gateway`
+ to switch back to `PASSTHROUGH` mode. Once corrections are made, edit the file again so the gateway will use the new certificates. \ No newline at end of file
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index b7c77730afb..4e31f74f18a 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -27,7 +27,7 @@ module API
end
get '/' do
authorize!(:read_board, user_project)
- present paginate(board_parent.boards), with: Entities::Board
+ present paginate(board_parent.boards.with_associations), with: Entities::Board
end
desc 'Find a project board' do
diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb
index 86d9b24802f..68497a08fb8 100644
--- a/lib/api/boards_responses.rb
+++ b/lib/api/boards_responses.rb
@@ -11,7 +11,7 @@ module API
end
def board_lists
- board.lists.destroyable
+ board.destroyable_lists
end
def create_list
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index ead01dc53f7..d783591c238 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1101,7 +1101,7 @@ module API
expose :project, using: Entities::BasicProjectDetails
expose :lists, using: Entities::List do |board|
- board.lists.destroyable
+ board.destroyable_lists
end
end
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index 9a20ee8c8b9..feb2254963e 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -37,7 +37,7 @@ module API
use :pagination
end
get '/' do
- present paginate(board_parent.boards), with: Entities::Board
+ present paginate(board_parent.boards.with_associations), with: Entities::Board
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 9ab5fa8d0bd..41418aa216c 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -158,6 +158,7 @@ module API
at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
+ optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set'
use :optional_attributes
end
post do
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 0b12e862ded..e2cbf91f281 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -434,7 +434,8 @@ module Gitlab
end
begin
- update_column_in_batches(table, column, default, &block)
+ default_after_type_cast = connection.type_cast(default, column_for(table, column))
+ update_column_in_batches(table, column, default_after_type_cast, &block)
change_column_null(table, column, false) unless allow_null
# We want to rescue _all_ exceptions here, even those that don't inherit
diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb
index e1002af40f6..9a41f04a4db 100644
--- a/lib/gitlab/git/raw_diff_change.rb
+++ b/lib/gitlab/git/raw_diff_change.rb
@@ -11,8 +11,8 @@ module Gitlab
if raw_change.is_a?(Gitaly::GetRawChangesResponse::RawChange)
@blob_id = raw_change.blob_id
@blob_size = raw_change.size
- @old_path = raw_change.old_path.presence
- @new_path = raw_change.new_path.presence
+ @old_path = raw_change.old_path_bytes.presence
+ @new_path = raw_change.new_path_bytes.presence
@operation = raw_change.operation&.downcase || :unknown
else
parse(raw_change)
diff --git a/lib/gitlab/graphql/copy_field_description.rb b/lib/gitlab/graphql/copy_field_description.rb
new file mode 100644
index 00000000000..edd73083ff2
--- /dev/null
+++ b/lib/gitlab/graphql/copy_field_description.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module CopyFieldDescription
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Returns the `description` for property of field `field_name` on type.
+ # This can be used to ensure, for example, that mutation argument descriptions
+ # are always identical to the corresponding query field descriptions.
+ #
+ # E.g.:
+ # argument :name, GraphQL::STRING_TYPE, description: copy_field_description(Types::UserType, :name)
+ def copy_field_description(type, field_name)
+ type.fields[field_name.to_s.camelize(:lower)].description
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/errors.rb b/lib/gitlab/graphql/errors.rb
index fe74549e322..40b90310e8b 100644
--- a/lib/gitlab/graphql/errors.rb
+++ b/lib/gitlab/graphql/errors.rb
@@ -6,6 +6,7 @@ module Gitlab
BaseError = Class.new(GraphQL::ExecutionError)
ArgumentError = Class.new(BaseError)
ResourceNotAvailable = Class.new(BaseError)
+ MutationError = Class.new(BaseError)
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/base_service.rb b/lib/gitlab/metrics/dashboard/base_service.rb
index 90895eb237a..0628e82e592 100644
--- a/lib/gitlab/metrics/dashboard/base_service.rb
+++ b/lib/gitlab/metrics/dashboard/base_service.rb
@@ -10,6 +10,8 @@ module Gitlab
NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
def get_dashboard
+ return error('Insufficient permissions.', :unauthorized) unless allowed?
+
success(dashboard: process_dashboard)
rescue NOT_FOUND_ERROR
error("#{dashboard_path} could not be found.", :not_found)
@@ -30,6 +32,12 @@ module Gitlab
private
+ # Determines whether users should be able to view
+ # dashboards at all.
+ def allowed?
+ Ability.allowed?(current_user, :read_environment, project)
+ end
+
# Returns a new dashboard Hash, supplemented with DB info
def process_dashboard
Gitlab::Metrics::Dashboard::Processor
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9ea368816f9..b8ce2c20563 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1991,9 +1991,6 @@ msgstr ""
msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose your merge method, options, checks, and set up a default merge request description template."
-msgstr ""
-
msgid "CiStatusLabel|canceled"
msgstr ""
@@ -6652,6 +6649,9 @@ msgstr ""
msgid "No"
msgstr ""
+msgid "No %{header} for this request."
+msgstr ""
+
msgid "No %{providerTitle} repositories available to import"
msgstr ""
@@ -7137,6 +7137,18 @@ msgstr ""
msgid "Performance optimization"
msgstr ""
+msgid "PerformanceBar|Gitaly calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
+msgstr ""
+
+msgid "PerformanceBar|profile"
+msgstr ""
+
+msgid "PerformanceBar|trace"
+msgstr ""
+
msgid "Permissions"
msgstr ""
@@ -8058,6 +8070,9 @@ msgstr ""
msgid "ProjectSettings|Badges"
msgstr ""
+msgid "ProjectSettings|Choose your merge method, merge options, and merge checks."
+msgstr ""
+
msgid "ProjectSettings|Customize your project badges."
msgstr ""
diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb
index 76f261e7d3f..cf202d88acc 100644
--- a/spec/controllers/admin/clusters/applications_controller_spec.rb
+++ b/spec/controllers/admin/clusters/applications_controller_spec.rb
@@ -13,16 +13,6 @@ describe Admin::Clusters::ApplicationsController do
it { expect { subject }.to be_allowed_for(:admin) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
-
- context 'when instance clusters are disabled' do
- before do
- stub_feature_flags(instance_clusters: false)
- end
-
- it 'returns 404' do
- is_expected.to have_http_status(:not_found)
- end
- end
end
let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index 7709f525119..e5501535875 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -17,66 +17,48 @@ describe Admin::ClustersController do
get :index, params: params
end
- context 'when feature flag is not enabled' do
- before do
- stub_feature_flags(instance_clusters: false)
- end
+ describe 'functionality' do
+ context 'when instance has one or more clusters' do
+ let!(:enabled_cluster) do
+ create(:cluster, :provided_by_gcp, :instance)
+ end
- it 'responds with not found' do
- get_index
+ let!(:disabled_cluster) do
+ create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
+ end
- expect(response).to have_gitlab_http_status(404)
- end
- end
+ it 'lists available clusters' do
+ get_index
- context 'when feature flag is enabled' do
- before do
- stub_feature_flags(instance_clusters: true)
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
+ end
- describe 'functionality' do
- context 'when instance has one or more clusters' do
- let!(:enabled_cluster) do
- create(:cluster, :provided_by_gcp, :instance)
- end
+ context 'when page is specified' do
+ let(:last_page) { Clusters::Cluster.instance_type.page.total_pages }
- let!(:disabled_cluster) do
- create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
+ before do
+ allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
+ create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance)
end
- it 'lists available clusters' do
- get_index
+ it 'redirects to the page' do
+ get_index(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:index)
- expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
- end
-
- context 'when page is specified' do
- let(:last_page) { Clusters::Cluster.instance_type.page.total_pages }
-
- before do
- allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
- create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance)
- end
-
- it 'redirects to the page' do
- get_index(page: last_page)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:clusters).current_page).to eq(last_page)
- end
+ expect(assigns(:clusters).current_page).to eq(last_page)
end
end
+ end
- context 'when instance does not have a cluster' do
- it 'returns an empty state page' do
- get_index
+ context 'when instance does not have a cluster' do
+ it 'returns an empty state page' do
+ get_index
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:index, partial: :empty_state)
- expect(assigns(:clusters)).to eq([])
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index, partial: :empty_state)
+ expect(assigns(:clusters)).to eq([])
end
end
end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 2f64c7f3460..09677b42887 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -20,70 +20,52 @@ describe Groups::ClustersController do
get :index, params: params.reverse_merge(group_id: group)
end
- context 'when feature flag is not enabled' do
- before do
- stub_feature_flags(group_clusters: false)
- end
+ describe 'functionality' do
+ context 'when group has one or more clusters' do
+ let(:group) { create(:group) }
- it 'renders 404' do
- go
+ let!(:enabled_cluster) do
+ create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
+ end
- expect(response).to have_gitlab_http_status(404)
- end
- end
+ let!(:disabled_cluster) do
+ create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
+ end
- context 'when feature flag is enabled' do
- before do
- stub_feature_flags(group_clusters: true)
- end
+ it 'lists available clusters' do
+ go
- describe 'functionality' do
- context 'when group has one or more clusters' do
- let(:group) { create(:group) }
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
+ end
- let!(:enabled_cluster) do
- create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
- end
+ context 'when page is specified' do
+ let(:last_page) { group.clusters.page.total_pages }
- let!(:disabled_cluster) do
- create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
+ before do
+ allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
+ create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
end
- it 'lists available clusters' do
- go
+ it 'redirects to the page' do
+ go(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:index)
- expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
- end
-
- context 'when page is specified' do
- let(:last_page) { group.clusters.page.total_pages }
-
- before do
- allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
- create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
- end
-
- it 'redirects to the page' do
- go(page: last_page)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:clusters).current_page).to eq(last_page)
- end
+ expect(assigns(:clusters).current_page).to eq(last_page)
end
end
+ end
- context 'when group does not have a cluster' do
- let(:group) { create(:group) }
+ context 'when group does not have a cluster' do
+ let(:group) { create(:group) }
- it 'returns an empty state page' do
- go
+ it 'returns an empty state page' do
+ go
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:index, partial: :empty_state)
- expect(assigns(:clusters)).to eq([])
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index, partial: :empty_state)
+ expect(assigns(:clusters)).to eq([])
end
end
end
diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb
new file mode 100644
index 00000000000..2879e06aee4
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::MergeRequests::ContentController do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ def do_request
+ get :widget, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid,
+ format: :json
+ }
+ end
+
+ describe 'GET widget' do
+ context 'user has access to the project' do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
+ project.add_maintainer(user)
+ end
+
+ it 'renders widget MR entity as json' do
+ do_request
+
+ expect(response).to match_response_schema('entities/merge_request_widget')
+ end
+
+ it 'checks whether the MR can be merged' do
+ controller.instance_variable_set(:@merge_request, merge_request)
+
+ expect(merge_request).to receive(:check_mergeability)
+
+ do_request
+ end
+
+ it 'closes an MR with moved source project' do
+ merge_request.update_column(:source_project_id, nil)
+
+ expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
+ end
+ end
+
+ context 'user does not have access to the project' do
+ it 'renders widget MR entity as json' do
+ do_request
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 4634d1d4bb3..5a5c0a1f6ac 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -17,6 +17,10 @@ describe SearchController do
set(:project) { create(:project, :public, :repository, :wiki_repo) }
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ end
+
subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
where(:partial, :scope) do
@@ -35,6 +39,19 @@ describe SearchController do
end
end
+ context 'global search' do
+ render_views
+
+ it 'omits pipeline status from load' do
+ project = create(:project, :public)
+ expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
+
+ get :show, params: { scope: 'projects', search: project.name }
+
+ expect(assigns[:search_objects].first).to eq project
+ end
+ end
+
it 'finds issue comments' do
project = create(:project, :public)
note = create(:note_on_issue, project: project)
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index d37e2bf511e..43753fa650c 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
awardable factory: :issue
after(:create) do |award, evaluator|
- award.awardable.project.add_guest(evaluator.user)
+ award.awardable.project&.add_guest(evaluator.user)
end
trait :upvote
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 08fa4a98feb..260eec7a9ed 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -362,14 +362,14 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
end
- it 'shows jump to next discussion button except on last discussion' do
+ it 'shows jump to next discussion button on all discussions' do
wait_for_requests
all_discussion_replies = page.all('.discussion-reply-holder')
expect(all_discussion_replies.count).to eq(2)
expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(1)
- expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(0)
+ expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(1)
end
it 'displays next discussion even if hidden' do
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 7018cb9a305..eac1dbc6474 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -99,7 +99,8 @@
"revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
- "status_path": { "type": "string" },
+ "merge_request_basic_path": { "type": "string" },
+ "merge_request_widget_path": { "type": "string" },
"new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index c3204b3aaa0..394666403ee 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -112,6 +112,44 @@ describe('DiscussionNotes', () => {
});
});
+ describe('events', () => {
+ describe('with groupped notes and replies expanded', () => {
+ const findNoteAtIndex = index => wrapper.find(`.note:nth-of-type(${index + 1}`);
+
+ beforeEach(() => {
+ createComponent({ shouldGroupReplies: true, isExpanded: true });
+ });
+
+ it('emits deleteNote when first note emits handleDeleteNote', () => {
+ findNoteAtIndex(0).vm.$emit('handleDeleteNote');
+ expect(wrapper.emitted().deleteNote).toBeTruthy();
+ });
+
+ it('emits startReplying when first note emits startReplying', () => {
+ findNoteAtIndex(0).vm.$emit('startReplying');
+ expect(wrapper.emitted().startReplying).toBeTruthy();
+ });
+
+ it('emits deleteNote when second note emits handleDeleteNote', () => {
+ findNoteAtIndex(1).vm.$emit('handleDeleteNote');
+ expect(wrapper.emitted().deleteNote).toBeTruthy();
+ });
+ });
+
+ describe('with ungroupped notes', () => {
+ let note;
+ beforeEach(() => {
+ createComponent();
+ note = wrapper.find('.note');
+ });
+
+ it('emits deleteNote when first note emits handleDeleteNote', () => {
+ note.vm.$emit('handleDeleteNote');
+ expect(wrapper.emitted().deleteNote).toBeTruthy();
+ });
+ });
+ });
+
describe('componentData', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/graphql/types/award_emojis/award_emoji_type_spec.rb b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
new file mode 100644
index 00000000000..5663a3d7195
--- /dev/null
+++ b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['AwardEmoji'] do
+ it { expect(described_class.graphql_name).to eq('AwardEmoji') }
+
+ it { is_expected.to require_graphql_authorizations(:read_emoji) }
+
+ it { expect(described_class).to have_graphql_fields(:description, :unicode_version, :emoji, :name, :unicode, :user) }
+end
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index ac7e0bb12a1..d3a76f33679 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,14 +1,19 @@
import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
+import { TEST_HOST } from 'spec/test_constants';
import MonitoringMock, { deploymentData } from '../mock_data';
describe('Area component', () => {
+ const mockSha = 'mockSha';
const mockWidgets = 'mockWidgets';
const mockSvgPathContent = 'mockSvgPathContent';
+ const projectPath = `${TEST_HOST}/path/to/project`;
+ const commitUrl = `${projectPath}/commit/${mockSha}`;
let mockGraphData;
let areaChart;
let spriteSpy;
@@ -26,6 +31,7 @@ describe('Area component', () => {
graphData: mockGraphData,
containerWidth: 0,
deploymentData: store.state.monitoringDashboard.deploymentData,
+ projectPath,
},
slots: {
default: mockWidgets,
@@ -88,11 +94,14 @@ describe('Area component', () => {
);
});
- it('renders commit sha in tooltip content', () => {
- const mockSha = 'mockSha';
+ it('renders clickable commit sha in tooltip content', () => {
areaChart.vm.tooltip.sha = mockSha;
+ areaChart.vm.tooltip.commitUrl = commitUrl;
- expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockSha)).toBe(true);
+ const commitLink = areaChart.find(GlLink);
+
+ expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
+ expect(commitLink.attributes('href')).toEqual(commitUrl);
});
});
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index 8f3c493dd4c..c3ed079e33b 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -32,6 +32,26 @@ describe('Getters Notes Store', () => {
};
});
+ describe('showJumpToNextDiscussion', () => {
+ it('should return true if there are 2 or more unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsByDate: ['123', '456'],
+ allResolvableDiscussions: [],
+ };
+
+ expect(getters.showJumpToNextDiscussion(state, localGetters)()).toBe(true);
+ });
+
+ it('should return false if there are 1 or less unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsByDate: ['123'],
+ allResolvableDiscussions: [],
+ };
+
+ expect(getters.showJumpToNextDiscussion(state, localGetters)()).toBe(false);
+ });
+ });
+
describe('discussions', () => {
it('should return all discussions in the store', () => {
expect(getters.discussions(state)).toEqual([individualNote]);
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 48f812f0db4..253413ae43e 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -218,7 +218,8 @@ export default {
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
email_patches_path: '/root/acets-app/merge_requests/22.patch',
plain_diff_path: '/root/acets-app/merge_requests/22.diff',
- status_path: '/root/acets-app/merge_requests/22.json',
+ merge_request_basic_path: '/root/acets-app/merge_requests/22.json?serializer=basic',
+ merge_request_widget_path: '/root/acets-app/merge_requests/22/widget.json',
merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
project_archived: false,
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index ac2fb16bd10..30e0504e4e1 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -473,7 +473,7 @@ describe('mrWidgetOptions', () => {
vm.mr.relatedLinks = {
assignToMe: null,
closing: `
- <a class="close-related-link" href="#'>
+ <a class="close-related-link" href="#">
Close
</a>
`,
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 3cf3d032bf4..7409572288c 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -583,6 +583,24 @@ describe Gitlab::Database::MigrationHelpers do
model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
end
end
+
+ it 'adds a column with an array default value for a jsonb type' do
+ create(:project)
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction).and_yield
+ expect(model).to receive(:update_column_in_batches).with(:projects, :foo, '[{"foo":"json"}]').and_call_original
+
+ model.add_column_with_default(:projects, :foo, :jsonb, default: [{ foo: "json" }])
+ end
+
+ it 'adds a column with an object default value for a jsonb type' do
+ create(:project)
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction).and_yield
+ expect(model).to receive(:update_column_in_batches).with(:projects, :foo, '{"foo":"json"}').and_call_original
+
+ model.add_column_with_default(:projects, :foo, :jsonb, default: { foo: "json" })
+ end
end
context 'inside a transaction' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 20842f55014..50138d272c4 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -67,7 +67,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
describe '#authorize!' do
- it 'does not raise an error' do
+ it 'raises an error' do
expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
diff --git a/spec/lib/gitlab/graphql/copy_field_description_spec.rb b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
new file mode 100644
index 00000000000..e7462c5b954
--- /dev/null
+++ b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::CopyFieldDescription do
+ subject { Class.new.include(described_class) }
+
+ describe '.copy_field_description' do
+ let(:type) do
+ Class.new(Types::BaseObject) do
+ graphql_name "TestType"
+
+ field :field_name, GraphQL::STRING_TYPE, null: true, description: 'Foo'
+ end
+ end
+
+ it 'returns the correct description' do
+ expect(subject.copy_field_description(type, :field_name)).to eq('Foo')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
index eecd257b38d..79a78df44ae 100644
--- a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
@@ -6,13 +6,19 @@ describe Gitlab::Metrics::Dashboard::DynamicDashboardService, :use_clean_rails_m
include MetricsDashboardHelpers
set(:project) { build(:project) }
+ set(:user) { create(:user) }
set(:environment) { create(:environment, project: project) }
+ before do
+ project.add_maintainer(user)
+ end
+
describe '#get_dashboard' do
- let(:service_params) { [project, nil, { environment: environment, dashboard_path: nil }] }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: nil }] }
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'valid embedded dashboard service response'
+ it_behaves_like 'raises error for users with insufficient permissions'
it 'caches the unprocessed dashboard for subsequent calls' do
expect(YAML).to receive(:safe_load).once.and_call_original
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index b9a5ee9c2b3..d8ed54c0248 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -6,12 +6,17 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
include MetricsDashboardHelpers
set(:project) { build(:project) }
+ set(:user) { create(:user) }
set(:environment) { create(:environment, project: project) }
let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
+ before do
+ project.add_maintainer(user)
+ end
+
describe '.find' do
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
- let(:service_call) { described_class.find(project, nil, environment, dashboard_path: dashboard_path) }
+ let(:service_call) { described_class.find(project, user, environment, dashboard_path: dashboard_path) }
it_behaves_like 'misconfigured dashboard service response', :not_found
@@ -41,13 +46,13 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
context 'when no dashboard is specified' do
- let(:service_call) { described_class.find(project, nil, environment) }
+ let(:service_call) { described_class.find(project, user, environment) }
it_behaves_like 'valid dashboard service response'
end
context 'when the dashboard is expected to be embedded' do
- let(:service_call) { described_class.find(project, nil, environment, dashboard_path: nil, embedded: true) }
+ let(:service_call) { described_class.find(project, user, environment, dashboard_path: nil, embedded: true) }
it_behaves_like 'valid embedded dashboard service response'
end
diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
index 57d82421b5d..468e8ec9ef2 100644
--- a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
@@ -5,8 +5,8 @@ require 'rails_helper'
describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
- set(:user) { build(:user) }
- set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
set(:environment) { create(:environment, project: project) }
before do
@@ -22,6 +22,8 @@ describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_m
it_behaves_like 'misconfigured dashboard service response', :not_found
end
+ it_behaves_like 'raises error for users with insufficient permissions'
+
context 'when the dashboard exists' do
let(:project) { project_with_dashboard(dashboard_path) }
diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
index 2af745bd4d7..13f22dd01c5 100644
--- a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
@@ -5,15 +5,21 @@ require 'spec_helper'
describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
- set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
set(:environment) { create(:environment, project: project) }
+ before do
+ project.add_maintainer(user)
+ end
+
describe 'get_dashboard' do
let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH }
- let(:service_params) { [project, nil, { environment: environment, dashboard_path: dashboard_path }] }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'valid dashboard service response'
+ it_behaves_like 'raises error for users with insufficient permissions'
it 'caches the unprocessed dashboard for subsequent calls' do
expect(YAML).to receive(:safe_load).once.and_call_original
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 96465a51db2..2378f400540 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -82,16 +82,6 @@ describe DeploymentPlatform do
end
end
end
-
- context 'feature flag disabled' do
- before do
- stub_feature_flags(group_clusters: false)
- end
-
- it 'returns nil' do
- is_expected.to be_nil
- end
- end
end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index d7accbef6bd..470ce65707d 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -866,35 +866,6 @@ describe Group do
end
end
- describe '#group_clusters_enabled?' do
- before do
- # Override global stub in spec/spec_helper.rb
- expect(Feature).to receive(:enabled?).and_call_original
- end
-
- subject { group.group_clusters_enabled? }
-
- it { is_expected.to be_truthy }
-
- context 'explicitly disabled for root ancestor' do
- before do
- feature = Feature.get(:group_clusters)
- feature.disable(group.root_ancestor)
- end
-
- it { is_expected.to be_falsey }
- end
-
- context 'explicitly disabled for root ancestor' do
- before do
- feature = Feature.get(:group_clusters)
- feature.enable(group.root_ancestor)
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
describe '#first_auto_devops_config' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index e100af7ddc7..95ae204a8a8 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -47,5 +47,13 @@ describe Postgresql::ReplicationSlot, :postgresql do
expect(described_class.lag_too_great?).to eq(false)
end
+
+ it 'returns false when there is a nil replication lag' do
+ expect(described_class)
+ .to receive(:pluck)
+ .and_return([0.megabytes, nil])
+
+ expect(described_class.lag_too_great?).to eq(false)
+ end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index cc0f5002a1e..1bc092fa41a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -497,7 +497,6 @@ describe Project do
it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
- it { is_expected.to delegate_method(:group_clusters_enabled?).to(:group).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) }
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c5ab7e57272..0bd17dbacd7 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -844,6 +844,19 @@ describe Repository do
end
end
+ describe '#get_raw_changes' do
+ context `with non-UTF8 bytes in paths` do
+ let(:old_rev) { 'd0888d297eadcd7a345427915c309413b1231e65' }
+ let(:new_rev) { '19950f03c765f7ac8723a73a0599764095f52fc0' }
+ let(:changes) { repository.raw_changes_between(old_rev, new_rev) }
+
+ it 'returns the changes' do
+ expect { changes }.not_to raise_error
+ expect(changes.first.new_path.bytes).to eq("hello\x80world".bytes)
+ end
+ end
+ end
+
describe '#create_ref' do
it 'redirects the call to write_ref' do
ref, ref_path = '1', '2'
diff --git a/spec/policies/award_emoji_policy_spec.rb b/spec/policies/award_emoji_policy_spec.rb
new file mode 100644
index 00000000000..2e3693c58d7
--- /dev/null
+++ b/spec/policies/award_emoji_policy_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojiPolicy do
+ let(:user) { create(:user) }
+ let(:award_emoji) { create(:award_emoji, awardable: awardable) }
+
+ subject { described_class.new(user, award_emoji) }
+
+ shared_examples 'when the user can read the awardable' do
+ context do
+ let(:project) { create(:project, :public) }
+
+ it { expect_allowed(:read_emoji) }
+ end
+ end
+
+ shared_examples 'when the user cannot read the awardable' do
+ context do
+ let(:project) { create(:project, :private) }
+
+ it { expect_disallowed(:read_emoji) }
+ end
+ end
+
+ context 'when the awardable is an issue' do
+ let(:awardable) { create(:issue, project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+
+ context 'when the awardable is a merge request' do
+ let(:awardable) { create(:merge_request, source_project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+
+ context 'when the awardable is a note' do
+ let(:awardable) { create(:note_on_merge_request, project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+
+ context 'when the awardable is a snippet' do
+ let(:awardable) { create(:project_snippet, :public, project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+end
diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb
index 9d755c6d29d..7b61819e079 100644
--- a/spec/policies/clusters/instance_policy_spec.rb
+++ b/spec/policies/clusters/instance_policy_spec.rb
@@ -16,21 +16,9 @@ describe Clusters::InstancePolicy do
context 'when admin' do
let(:user) { create(:admin) }
- context 'with instance_level_clusters enabled' do
- it { expect(policy).to be_allowed :read_cluster }
- it { expect(policy).to be_allowed :update_cluster }
- it { expect(policy).to be_allowed :admin_cluster }
- end
-
- context 'with instance_level_clusters disabled' do
- before do
- stub_feature_flags(instance_clusters: false)
- end
-
- it { expect(policy).to be_disallowed :read_cluster }
- it { expect(policy).to be_disallowed :update_cluster }
- it { expect(policy).to be_disallowed :admin_cluster }
- end
+ it { expect(policy).to be_allowed :read_cluster }
+ it { expect(policy).to be_allowed :update_cluster }
+ it { expect(policy).to be_allowed :admin_cluster }
end
end
end
diff --git a/spec/presenters/award_emoji_presenter_spec.rb b/spec/presenters/award_emoji_presenter_spec.rb
new file mode 100644
index 00000000000..e2ada2a3c93
--- /dev/null
+++ b/spec/presenters/award_emoji_presenter_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojiPresenter do
+ let(:emoji_name) { 'thumbsup' }
+ let(:award_emoji) { build(:award_emoji, name: emoji_name) }
+ let(:presenter) { described_class.new(award_emoji) }
+
+ describe '#description' do
+ it { expect(presenter.description).to eq Gitlab::Emoji.emojis[emoji_name]['description'] }
+ end
+
+ describe '#unicode' do
+ it { expect(presenter.unicode).to eq Gitlab::Emoji.emojis[emoji_name]['unicode'] }
+ end
+
+ describe '#unicode_version' do
+ it { expect(presenter.unicode_version).to eq Gitlab::Emoji.emoji_unicode_version(emoji_name) }
+ end
+
+ describe '#emoji' do
+ it { expect(presenter.emoji).to eq Gitlab::Emoji.emojis[emoji_name]['moji'] }
+ end
+
+ describe 'when presenting an award emoji with an invalid name' do
+ let(:emoji_name) { 'invalid-name' }
+
+ it 'returns nil for all properties' do
+ expect(presenter.description).to be_nil
+ expect(presenter.emoji).to be_nil
+ expect(presenter.unicode).to be_nil
+ expect(presenter.unicode_version).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
new file mode 100644
index 00000000000..3982125a38a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding an AwardEmoji' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:awardable) { create(:note) }
+ let(:project) { awardable.project }
+ let(:emoji_name) { 'thumbsup' }
+ let(:mutation) do
+ variables = {
+ awardable_id: GitlabSchema.id_from_object(awardable).to_s,
+ name: emoji_name
+ }
+
+ graphql_mutation(:add_award_emoji, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:add_award_emoji)
+ end
+
+ shared_examples 'a mutation that does not create an AwardEmoji' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { AwardEmoji.count }
+ end
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'when the given awardable is not an Awardable' do
+ let(:awardable) { create(:label) }
+
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
+ let(:awardable) { create(:system_note) }
+
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable an Awardable' do
+ it 'creates an emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the emoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['awardEmoji']['name']).to eq(emoji_name)
+ end
+
+ context 'when there were active record validation errors' do
+ before do
+ expect_next_instance_of(AwardEmoji) do |award|
+ expect(award).to receive(:valid?).at_least(:once).and_return(false)
+ expect(award).to receive_message_chain(
+ :errors,
+ :full_messages
+ ).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
new file mode 100644
index 00000000000..c78f0c7ca27
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Removing an AwardEmoji' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:awardable) { create(:note) }
+ let(:project) { awardable.project }
+ let(:emoji_name) { 'thumbsup' }
+ let(:input) { { awardable_id: GitlabSchema.id_from_object(awardable).to_s, name: emoji_name } }
+
+ let(:mutation) do
+ graphql_mutation(:remove_award_emoji, input)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:remove_award_emoji)
+ end
+
+ def create_award_emoji(user)
+ create(:award_emoji, name: emoji_name, awardable: awardable, user: user )
+ end
+
+ shared_examples 'a mutation that does not destroy an AwardEmoji' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { AwardEmoji.count }
+ end
+ end
+
+ shared_examples 'a mutation that does not authorize the user' do
+ it_behaves_like 'a mutation that does not destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+
+ context 'when the current_user does not own the award emoji' do
+ let!(:award_emoji) { create_award_emoji(create(:user)) }
+
+ it_behaves_like 'a mutation that does not authorize the user'
+ end
+
+ context 'when the current_user owns the award emoji' do
+ let!(:award_emoji) { create_award_emoji(current_user) }
+
+ context 'when the given awardable is not an Awardable' do
+ let(:awardable) { create(:label) }
+
+ it_behaves_like 'a mutation that does not destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable' do
+ it 'removes the emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns no errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to be_nil
+ end
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
new file mode 100644
index 00000000000..31145730f10
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Toggling an AwardEmoji' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:awardable) { create(:note) }
+ let(:project) { awardable.project }
+ let(:emoji_name) { 'thumbsup' }
+ let(:mutation) do
+ variables = {
+ awardable_id: GitlabSchema.id_from_object(awardable).to_s,
+ name: emoji_name
+ }
+
+ graphql_mutation(:toggle_award_emoji, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:toggle_award_emoji)
+ end
+
+ shared_examples 'a mutation that does not create or destroy an AwardEmoji' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { AwardEmoji.count }
+ end
+ end
+
+ def create_award_emoji(user)
+ create(:award_emoji, name: emoji_name, awardable: awardable, user: user )
+ end
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'when the given awardable is not an Awardable' do
+ let(:awardable) { create(:label) }
+
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
+ let(:awardable) { create(:system_note) }
+
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable' do
+ context 'when no emoji has been awarded by the current_user yet' do
+ # Create an award emoji for another user. This therefore tests that
+ # toggling is correctly scoped to the user's emoji only.
+ let!(:award_emoji) { create_award_emoji(create(:user)) }
+
+ it 'creates an emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the emoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['awardEmoji']['name']).to eq(emoji_name)
+ end
+
+ it 'returns toggledOn as true' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['toggledOn']).to eq(true)
+ end
+
+ context 'when there were active record validation errors' do
+ before do
+ expect_next_instance_of(AwardEmoji) do |award|
+ expect(award).to receive(:valid?).at_least(:once).and_return(false)
+ expect(award).to receive_message_chain(:errors, :full_messages).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+ end
+ end
+
+ context 'when an emoji has been awarded by the current_user' do
+ let!(:award_emoji) { create_award_emoji(current_user) }
+
+ it 'removes the emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns no errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to be_nil
+ end
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+
+ it 'returns toggledOn as false' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['toggledOn']).to eq(false)
+ end
+ end
+ end
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index bab1520b960..46925daf40a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -416,7 +416,6 @@ describe API::Users do
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
- expect(new_user).not_to eq(nil)
expect(new_user.admin).to eq(true)
expect(new_user.can_create_group).to eq(true)
end
@@ -435,7 +434,6 @@ describe API::Users do
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
- expect(new_user).not_to eq(nil)
expect(new_user.admin).to eq(false)
expect(new_user.can_create_group).to eq(false)
end
@@ -445,7 +443,6 @@ describe API::Users do
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
- expect(new_user).not_to eq(nil)
expect(new_user.admin).to eq(false)
end
@@ -460,7 +457,6 @@ describe API::Users do
user_id = json_response['id']
new_user = User.find(user_id)
- expect(new_user).not_to eq nil
expect(new_user.external).to be_falsy
end
@@ -470,7 +466,6 @@ describe API::Users do
user_id = json_response['id']
new_user = User.find(user_id)
- expect(new_user).not_to eq nil
expect(new_user.external).to be_truthy
end
@@ -482,7 +477,19 @@ describe API::Users do
user_id = json_response['id']
new_user = User.find(user_id)
- expect(new_user).not_to eq(nil)
+ expect(new_user.recently_sent_password_reset?).to eq(true)
+ end
+
+ it "creates user with random password" do
+ params = attributes_for(:user, force_random_password: true, reset_password: true)
+ post api('/users', admin), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+
+ expect(new_user.valid_password?(params[:password])).to eq(false)
expect(new_user.recently_sent_password_reset?).to eq(true)
end
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb
index 592962ebf7c..3abb5096a7a 100644
--- a/spec/support/api/boards_shared_examples.rb
+++ b/spec/support/api/boards_shared_examples.rb
@@ -14,6 +14,16 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false|
end
end
+ it 'avoids N+1 queries' do
+ pat = create(:personal_access_token, user: user)
+ control = ActiveRecord::QueryRecorder.new { get api(root_url, personal_access_token: pat) }
+
+ create(:milestone, "#{board_parent.class.name.underscore}": board_parent)
+ create(:board, "#{board_parent.class.name.underscore}": board_parent)
+
+ expect { get api(root_url, personal_access_token: pat) }.not_to exceed_query_limit(control)
+ end
+
describe "GET #{route_definition}" do
context "when unauthenticated" do
it "returns authentication error" do
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index bcf6669f37d..1a09d48f4cd 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -4,10 +4,7 @@ module GraphqlHelpers
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
def self.fieldnamerize(underscored_field_name)
- graphql_field_name = underscored_field_name.to_s.camelize
- graphql_field_name[0] = graphql_field_name[0].downcase
-
- graphql_field_name
+ underscored_field_name.to_s.camelize(:lower)
end
# Run a loader's named resolver
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index 6de00eea474..1511a2f6b49 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -50,4 +50,12 @@ module MetricsDashboardHelpers
it_behaves_like 'valid dashboard service response for schema'
end
+
+ shared_examples_for 'raises error for users with insufficient permissions' do
+ context 'when the user does not have sufficient access' do
+ let(:user) { build(:user) }
+
+ it_behaves_like 'misconfigured dashboard service response', :unauthorized
+ end
+ end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 77f22d9dd24..e63099d89b7 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -64,7 +64,8 @@ module TestEnv
'with-codeowners' => '219560e',
'submodule_inside_folder' => 'b491b92',
'png-lfs' => 'fe42f41',
- 'sha-starting-with-large-number' => '8426165'
+ 'sha-starting-with-large-number' => '8426165',
+ 'invalid-utf8-diff-paths' => '99e4853'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
new file mode 100644
index 00000000000..022d41c0bdd
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# Shared example for expecting top-level errors.
+# See https://graphql-ruby.org/mutations/mutation_errors#raising-errors
+#
+# { errors: [] }
+#
+# There must be a method or let called `mutation` defined that executes
+# the mutation.
+RSpec.shared_examples 'a mutation that returns top-level errors' do |errors:|
+ it do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ error_messages = graphql_errors.map { |e| e['message'] }
+
+ expect(error_messages).to eq(errors)
+ end
+end
+
+# Shared example for expecting schema-level errors.
+# See https://graphql-ruby.org/mutations/mutation_errors#errors-as-data
+#
+# { data: { mutationName: { errors: [] } } }
+#
+# There must be:
+# - a method or let called `mutation` defined that executes the mutation
+# - a `mutation_response` method defined that returns the data of the mutation response.
+RSpec.shared_examples 'a mutation that returns errors in the response' do |errors:|
+ it do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).to eq(errors)
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index 9dbd1d8e867..5359831f8f8 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -1,8 +1,17 @@
shared_examples 'issues move service' do |group|
+ shared_examples 'updating timestamps' do
+ it 'updates updated_at' do
+ expect {described_class.new(parent, user, params).execute(issue)}
+ .to change {issue.reload.updated_at}
+ end
+ end
+
context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
+ it_behaves_like 'updating timestamps'
+
it 'delegates the label changes to Issues::UpdateService' do
service = double(:service)
expect(Issues::UpdateService).to receive(:new).and_return(service)
@@ -24,6 +33,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
+ it_behaves_like 'updating timestamps'
+
it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
@@ -46,6 +57,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } }
+ it_behaves_like 'updating timestamps'
+
it 'keeps labels and milestone' do
described_class.new(parent, user, params).execute(issue)
issue.reload
@@ -59,6 +72,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
+ it_behaves_like 'updating timestamps'
+
it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
@@ -75,10 +90,13 @@ shared_examples 'issues move service' do |group|
end
context 'when moving to same list' do
- let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+ let(:assignee) { create(:user) }
+ let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:issue) do
+ create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee])
+ end
it 'returns false' do
expect(described_class.new(parent, user, params).execute(issue)).to eq false
@@ -90,18 +108,36 @@ shared_examples 'issues move service' do |group|
expect(issue.reload.labels).to contain_exactly(bug, development)
end
- it 'sorts issues' do
- [issue, issue1, issue2].each do |issue|
- issue.move_to_end && issue.save!
- end
+ it 'keeps issues assignees' do
+ described_class.new(parent, user, params).execute(issue)
+
+ expect(issue.reload.assignees).to contain_exactly(assignee)
+ end
- params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
+ it 'sorts issues' do
+ reorder_issues(params, issues: [issue, issue1, issue2])
described_class.new(parent, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
+ it 'does not update updated_at' do
+ reorder_issues(params, issues: [issue, issue1, issue2])
+
+ updated_at = issue.updated_at
+ updated_at1 = issue1.updated_at
+ updated_at2 = issue2.updated_at
+
+ Timecop.travel(1.minute.from_now) do
+ described_class.new(parent, user, params).execute(issue)
+ end
+
+ expect(issue.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
+ expect(issue1.reload.updated_at.change(usec: 0)).to eq updated_at1.change(usec: 0)
+ expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0)
+ end
+
if group
context 'when on a group board' do
it 'sends the board_group_id parameter' do
@@ -114,5 +150,13 @@ shared_examples 'issues move service' do |group|
end
end
end
+
+ def reorder_issues(params, issues: [])
+ issues.each do |issue|
+ issue.move_to_end && issue.save!
+ end
+
+ params.merge!(move_after_id: issues[1].id, move_before_id: issues[2].id)
+ end
end
end