Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-15 03:08:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-15 03:08:06 +0300
commitaebabf8f5a4b5b4ef68221aeb73729f91f11f98f (patch)
tree6cc4117df89eec3be7ea8cbb199515aef4622aca
parent67cd2904c9ebd7ba346cc92a37ac953747a3f0e5 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_metrics.js27
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue23
-rw-r--r--app/assets/javascripts/ide/stores/utils.js43
-rw-r--r--app/assets/javascripts/reports/accessibility_report/store/getters.js23
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue16
-rw-r--r--app/controllers/projects/alert_management_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb6
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml11
-rw-r--r--changelogs/unreleased/31810-markdown-images.yml5
-rw-r--r--changelogs/unreleased/fj-215861-add-rake-tasks-to-migrate-snippets.yml5
-rw-r--r--changelogs/unreleased/tr-remove-alert-mgmt-feature-flag.yml5
-rw-r--r--config/webpack.config.js16
-rw-r--r--doc/administration/monitoring/gitlab_self_monitoring_project/index.md2
-rw-r--r--doc/administration/reference_architectures/index.md24
-rw-r--r--doc/administration/repository_checks.md12
-rw-r--r--doc/api/metrics_dashboard_annotations.md3
-rw-r--r--doc/raketasks/README.md1
-rw-r--r--doc/raketasks/migrate_snippets.md96
-rw-r--r--doc/user/project/issues/index.md4
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md4
-rw-r--r--lib/gitlab/background_migration/backfill_snippet_repositories.rb20
-rw-r--r--lib/tasks/gitlab/snippets.rake91
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/controllers/projects/alert_management_controller_spec.rb28
-rw-r--r--spec/frontend/behaviors/markdown/render_metrics_spec.js36
-rw-r--r--spec/frontend/ide/stores/utils_spec.js71
-rw-r--r--spec/frontend/reports/accessibility_report/store/getters_spec.js18
-rw-r--r--spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js41
-rw-r--r--spec/serializers/merge_request_poll_widget_entity_spec.rb22
-rw-r--r--spec/tasks/gitlab/snippets_rake_spec.rb114
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb24
34 files changed, 669 insertions, 150 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_metrics.js b/app/assets/javascripts/behaviors/markdown/render_metrics.js
index 9260a89bd52..37cbce46b6f 100644
--- a/app/assets/javascripts/behaviors/markdown/render_metrics.js
+++ b/app/assets/javascripts/behaviors/markdown/render_metrics.js
@@ -1,15 +1,12 @@
import Vue from 'vue';
-import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
import { createStore } from '~/monitoring/stores/embed_group/';
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-foss/issues/64369.
export default function renderMetrics(elements) {
if (!elements.length) {
- return;
+ return Promise.resolve();
}
- const EmbedGroupComponent = Vue.extend(EmbedGroup);
-
const wrapperList = [];
elements.forEach(element => {
@@ -31,14 +28,20 @@ export default function renderMetrics(elements) {
element.parentNode.removeChild(element);
});
- wrapperList.forEach(wrapper => {
- // eslint-disable-next-line no-new
- new EmbedGroupComponent({
- el: wrapper,
- store: createStore(),
- propsData: {
- urls: wrapper.urls,
- },
+ return import(
+ /* webpackChunkName: 'gfm_metrics' */ '~/monitoring/components/embeds/embed_group.vue'
+ ).then(({ default: EmbedGroup }) => {
+ const EmbedGroupComponent = Vue.extend(EmbedGroup);
+
+ wrapperList.forEach(wrapper => {
+ // eslint-disable-next-line no-new
+ new EmbedGroupComponent({
+ el: wrapper,
+ store: createStore(),
+ propsData: {
+ urls: wrapper.urls,
+ },
+ });
});
});
}
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index a0305a8f52f..c72a8b2b0d0 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -13,6 +13,7 @@ import {
import Editor from '../lib/editor';
import FileTemplatesBar from './file_templates/bar.vue';
import { __ } from '~/locale';
+import { extractMarkdownImagesFromEntries } from '../stores/utils';
export default {
components: {
@@ -26,6 +27,12 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ content: '',
+ images: {},
+ };
+ },
computed: {
...mapState('rightPane', {
rightPaneIsOpen: 'isOpen',
@@ -36,6 +43,7 @@ export default {
'currentActivityView',
'renderWhitespaceInCode',
'editorTheme',
+ 'entries',
]),
...mapGetters([
'currentMergeRequest',
@@ -136,6 +144,18 @@ export default {
this.$nextTick(() => this.refreshEditorDimensions());
}
},
+ showContentViewer(val) {
+ if (!val) return;
+
+ if (this.fileType === 'markdown') {
+ const { content, images } = extractMarkdownImagesFromEntries(this.file, this.entries);
+ this.content = content;
+ this.images = images;
+ } else {
+ this.content = this.file.content || this.file.raw;
+ this.images = {};
+ }
+ },
},
beforeDestroy() {
this.editor.dispose();
@@ -310,7 +330,8 @@ export default {
></div>
<content-viewer
v-if="showContentViewer"
- :content="file.content || file.raw"
+ :content="content"
+ :images="images"
:path="file.rawPath || file.path"
:file-path="file.path"
:file-size="file.size"
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 4e5b01596d8..56671142bd4 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,4 +1,5 @@
import { commitActionTypes, FILE_VIEW_MODE_EDITOR } from '../constants';
+import { relativePathToAbsolute, isAbsolute, isRootRelative } from '~/lib/utils/url_utility';
export const dataStructure = () => ({
id: '',
@@ -274,3 +275,45 @@ export const pathsAreEqual = (a, b) => {
// if the contents of a file dont end with a newline, this function adds a newline
export const addFinalNewlineIfNeeded = content =>
content.charAt(content.length - 1) !== '\n' ? `${content}\n` : content;
+
+export function extractMarkdownImagesFromEntries(mdFile, entries) {
+ /**
+ * Regex to identify an image tag in markdown, like:
+ *
+ * ![img alt goes here](/img.png)
+ * ![img alt](../img 1/img.png "my image title")
+ * ![img alt](https://gitlab.com/assets/logo.svg "title here")
+ *
+ */
+ const reMdImage = /!\[([^\]]*)\]\((.*?)(?:(?="|\))"([^"]*)")?\)/gi;
+ const prefix = 'gl_md_img_';
+ const images = {};
+
+ let content = mdFile.content || mdFile.raw;
+ let i = 0;
+
+ content = content.replace(reMdImage, (_, alt, path, title) => {
+ const imagePath = (isRootRelative(path) ? path : relativePathToAbsolute(path, mdFile.path))
+ .substr(1)
+ .trim();
+
+ const imageContent = entries[imagePath]?.content || entries[imagePath]?.raw;
+
+ if (!isAbsolute(path) && imageContent) {
+ const ext = path.includes('.')
+ ? path
+ .split('.')
+ .pop()
+ .trim()
+ : 'jpeg';
+ const src = `data:image/${ext};base64,${imageContent}`;
+ i += 1;
+ const key = `{{${prefix}${i}}}`;
+ images[key] = { alt, src, title };
+ return key;
+ }
+ return title ? `![${alt}](${path}"${title}")` : `![${alt}](${path})`;
+ });
+
+ return { content, images };
+}
diff --git a/app/assets/javascripts/reports/accessibility_report/store/getters.js b/app/assets/javascripts/reports/accessibility_report/store/getters.js
index 83e97a48e3b..9aff427e644 100644
--- a/app/assets/javascripts/reports/accessibility_report/store/getters.js
+++ b/app/assets/javascripts/reports/accessibility_report/store/getters.js
@@ -37,23 +37,12 @@ export const summaryStatus = state => {
export const shouldRenderIssuesList = state =>
Object.values(state.report).some(x => Array.isArray(x) && x.length > 0);
-export const unresolvedIssues = state => [
- ...state.report.existing_errors,
- ...state.report.existing_warnings,
- ...state.report.existing_notes,
-];
-
-export const resolvedIssues = state => [
- ...state.report.resolved_errors,
- ...state.report.resolved_warnings,
- ...state.report.resolved_notes,
-];
-
-export const newIssues = state => [
- ...state.report.new_errors,
- ...state.report.new_warnings,
- ...state.report.new_notes,
-];
+// We could just map state, but we're going to iterate in the future
+// to add notes and warnings to these issue lists, so I'm going to
+// keep these as getters
+export const unresolvedIssues = state => state.report.existing_errors;
+export const resolvedIssues = state => state.report.resolved_errors;
+export const newIssues = state => state.report.new_errors;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
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 d0251c29fba..265ff81f39f 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
@@ -40,7 +40,6 @@ import TerraformPlan from './components/mr_widget_terraform_plan.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
import { setFaviconOverlay } from '../lib/utils/common_utils';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
el: '#js-vue-mr-widget',
@@ -80,7 +79,6 @@ export default {
TerraformPlan,
GroupedAccessibilityReportsApp,
},
- mixins: [glFeatureFlagsMixin()],
props: {
mrData: {
type: Object,
@@ -146,7 +144,7 @@ export default {
});
},
shouldShowAccessibilityReport() {
- return this.mr.accessibilityReportPath && this.glFeatures.accessibilityMergeRequestWidget;
+ return this.mr.accessibilityReportPath;
},
},
watch: {
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
index 3f8b8cea256..fe488ab6cfa 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
@@ -39,6 +39,11 @@ export default {
required: false,
default: '',
},
+ images: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
computed: {
viewer() {
@@ -67,6 +72,7 @@ export default {
:file-size="fileSize"
:project-path="projectPath"
:content="content"
+ :images="images"
:commit-sha="commitSha"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index bfd13a4b10f..1344c766e0e 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -5,6 +5,7 @@ import '~/behaviors/markdown/render_gfm';
import { GlSkeletonLoading } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
+import { forEach, escape } from 'lodash';
const { CancelToken } = axios;
let axiosSource;
@@ -32,6 +33,11 @@ export default {
type: String,
required: true,
},
+ images: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
data() {
return {
@@ -76,7 +82,15 @@ export default {
postOptions,
)
.then(({ data }) => {
- this.previewContent = data.body;
+ let previewContent = data.body;
+ forEach(this.images, ({ src, title = '', alt }, key) => {
+ previewContent = previewContent.replace(
+ key,
+ `<img src="${escape(src)}" title="${escape(title)}" alt="${escape(alt)}">`,
+ );
+ });
+
+ this.previewContent = previewContent;
this.isLoading = false;
this.$nextTick(() => {
diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb
index 086aca3f6a3..bcaaeb3c017 100644
--- a/app/controllers/projects/alert_management_controller.rb
+++ b/app/controllers/projects/alert_management_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class Projects::AlertManagementController < Projects::ApplicationController
- before_action :ensure_list_feature_enabled, only: :index
before_action :ensure_detail_feature_enabled, only: :details
before_action :authorize_read_alert_management_alert!
before_action do
@@ -18,10 +17,6 @@ class Projects::AlertManagementController < Projects::ApplicationController
private
- def ensure_list_feature_enabled
- render_404 unless Feature.enabled?(:alert_management_minimal, project)
- end
-
def ensure_detail_feature_enabled
render_404 unless Feature.enabled?(:alert_management_detail, project)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 87b69291559..5613b5b9589 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -32,7 +32,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:code_navigation, @project)
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
push_frontend_feature_flag(:merge_ref_head_comments, @project)
- push_frontend_feature_flag(:accessibility_merge_request_widget, @project)
push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true)
end
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index 08255db5cbf..aad607f358a 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -71,6 +71,12 @@ class MergeRequestPollWidgetEntity < Grape::Entity
end
end
+ expose :accessibility_report_path do |merge_request|
+ if merge_request.has_accessibility_reports?
+ accessibility_reports_project_merge_request_path(merge_request.project, merge_request, format: :json)
+ end
+ end
+
expose :terraform_reports_path do |merge_request|
if merge_request.has_terraform_reports?
terraform_reports_project_merge_request_path(merge_request.project, merge_request, format: :json)
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 2f33cb22502..f0e68423424 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -222,12 +222,11 @@
%span
= _('Metrics')
- - if Feature.enabled?(:alert_management_minimal, @project)
- - if project_nav_tab?(:alert_management)
- = nav_link(controller: :alert_management) do
- = link_to project_alert_management_index_path(@project), title: _('Alerts'), class: 'shortcuts-tracking qa-operations-tracking-link' do
- %span
- = _('Alerts')
+ - if project_nav_tab?(:alert_management)
+ = nav_link(controller: :alert_management) do
+ = link_to project_alert_management_index_path(@project), title: _('Alerts'), class: 'shortcuts-tracking qa-operations-tracking-link' do
+ %span
+ = _('Alerts')
- if project_nav_tab? :environments
= render_if_exists "layouts/nav/sidebar/tracing_link"
diff --git a/changelogs/unreleased/31810-markdown-images.yml b/changelogs/unreleased/31810-markdown-images.yml
new file mode 100644
index 00000000000..fdf35adb281
--- /dev/null
+++ b/changelogs/unreleased/31810-markdown-images.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Web IDE markdown to preview uncommitted images
+merge_request: 31540
+author:
+type: added
diff --git a/changelogs/unreleased/fj-215861-add-rake-tasks-to-migrate-snippets.yml b/changelogs/unreleased/fj-215861-add-rake-tasks-to-migrate-snippets.yml
new file mode 100644
index 00000000000..5b487c9caf7
--- /dev/null
+++ b/changelogs/unreleased/fj-215861-add-rake-tasks-to-migrate-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippet migration rake tasks
+merge_request: 30489
+author:
+type: other
diff --git a/changelogs/unreleased/tr-remove-alert-mgmt-feature-flag.yml b/changelogs/unreleased/tr-remove-alert-mgmt-feature-flag.yml
new file mode 100644
index 00000000000..ba0d4bcfc34
--- /dev/null
+++ b/changelogs/unreleased/tr-remove-alert-mgmt-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Alert Management functionality
+merge_request: 31171
+author:
+type: added
diff --git a/config/webpack.config.js b/config/webpack.config.js
index e2e1139d49e..342a4da5170 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -268,6 +268,22 @@ module.exports = {
minChunks: 2,
reuseExistingChunk: true,
},
+ echarts: {
+ priority: 14,
+ name: 'echarts',
+ chunks: 'all',
+ test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
+ minChunks: 2,
+ reuseExistingChunk: true,
+ },
+ security_reports: {
+ priority: 13,
+ name: 'security_reports',
+ chunks: 'initial',
+ test: /[\\/](vue_shared[\\/](security_reports|license_compliance)|security_dashboard)[\\/]/,
+ minChunks: 2,
+ reuseExistingChunk: true,
+ },
vendors: {
priority: 10,
chunks: 'async',
diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
index a919dd769f7..35c41dbd7ef 100644
--- a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
+++ b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md
@@ -46,7 +46,7 @@ The project will be automatically configured to connect to the
instance is present (should be the case if GitLab was installed via Omnibus
and you haven't disabled it).
-If that's not the case or if you have an external Prometheus instance or an HA setup,
+If that's not the case or if you have an external Prometheus instance or a customized setup,
you should
[configure it manually](../../../user/project/integrations/prometheus.md#manual-configuration-of-prometheus).
diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md
index d39a7bdecfa..26244368234 100644
--- a/doc/administration/reference_architectures/index.md
+++ b/doc/administration/reference_architectures/index.md
@@ -57,9 +57,9 @@ The following reference architectures are available:
- [Up to 25,000 users](25k_users.md)
- [Up to 50,000 users](50k_users.md)
-## Availability components
+## Availability Components
-GitLab comes with the following availability components for your use, listed from
+GitLab comes with the following components for your use, listed from
least to most complex:
1. [Automated backups](#automated-backups-core-only)
@@ -68,11 +68,11 @@ least to most complex:
1. [Automated database failover](#automated-database-failover-premium-only)
1. [Instance level replication with GitLab Geo](#instance-level-replication-with-gitlab-geo-premium-only)
-As you get started implementing HA, begin with a single server and then do
+As you implement these components, begin with a single server and then do
backups. Only after completing the first server should you proceed to the next.
-Also, not implementing HA for GitLab doesn't necessarily mean that you'll have
-more downtime. Depending on your needs and experience level, non-HA servers can
+Also, not implementing extra servers for GitLab doesn't necessarily mean that you'll have
+more downtime. Depending on your needs and experience level, single servers can
have more actual perceived uptime for your users.
### Automated backups **(CORE ONLY)**
@@ -83,7 +83,7 @@ have more actual perceived uptime for your users.
This solution is appropriate for many teams that have the default GitLab installation.
With automatic backups of the GitLab repositories, configuration, and the database,
-this can be an optimal solution if you don't have strict availability requirements.
+this can be an optimal solution if you don't have strict requirements.
[Automated backups](../../raketasks/backup_restore.md#configuring-cron-to-make-daily-backups)
is the least complex to setup. This provides a point-in-time recovery of a predetermined schedule.
@@ -100,7 +100,7 @@ shared file server and database systems on the back end. This way, if one of the
application servers fails, the workflow is not interrupted.
[HAProxy](https://www.haproxy.org/) is recommended as the load balancer.
-With this added availability component you have a number of advantages compared
+With this added component you have a number of advantages compared
to the default installation:
- Increase the number of users.
@@ -124,7 +124,7 @@ As long as at least one of each component is online and capable of handling the
> - Supported tiers: [GitLab Premium and Ultimate](https://about.gitlab.com/pricing/)
By adding automatic failover for database systems, you can enable higher uptime
-with an additional database nodes. This extends the default database with a
+with additional database nodes. This extends the default database with
cluster management and failover policies.
[PgBouncer](../../development/architecture.md#pgbouncer) in conjunction with
[Repmgr](../high_availability/database.md) is recommended.
@@ -157,12 +157,12 @@ column.
| Load balancer(s) ([6](#footnotes)) | Handles load balancing, typically when you have multiple GitLab application services nodes | [Load balancer configuration](../high_availability/load_balancer.md) ([6](#footnotes)) | No |
| Object storage service ([4](#footnotes)) | Recommended store for shared data objects | [Object Storage configuration](../object_storage.md) | No |
| NFS ([5](#footnotes)) ([7](#footnotes)) | Shared disk storage service. Can be used as an alternative Object Storage. Required for GitLab Pages | [NFS configuration](../high_availability/nfs.md) | No |
-| [Consul](../../development/architecture.md#consul) ([3](#footnotes)) | Service discovery and health checks/failover | [Consul HA configuration](../high_availability/consul.md) **(PREMIUM ONLY)** | Yes |
+| [Consul](../../development/architecture.md#consul) ([3](#footnotes)) | Service discovery and health checks/failover | [Consul configuration](../high_availability/consul.md) **(PREMIUM ONLY)** | Yes |
| [PostgreSQL](../../development/architecture.md#postgresql) | Database | [PostgreSQL configuration](https://docs.gitlab.com/omnibus/settings/database.html) | Yes |
| [PgBouncer](../../development/architecture.md#pgbouncer) | Database connection pooler | [PgBouncer configuration](../high_availability/pgbouncer.md#running-pgbouncer-as-part-of-a-non-ha-gitlab-installation) **(PREMIUM ONLY)** | Yes |
| Repmgr | PostgreSQL cluster management and failover | [PostgreSQL and Repmgr configuration](../high_availability/database.md) | Yes |
| [Redis](../../development/architecture.md#redis) ([3](#footnotes)) | Key/value store for fast data lookup and caching | [Redis configuration](../high_availability/redis.md) | Yes |
-| Redis Sentinel | High availability for Redis | [Redis Sentinel configuration](../high_availability/redis.md) | Yes |
+| Redis Sentinel | Redis | [Redis Sentinel configuration](../high_availability/redis.md) | Yes |
| [Gitaly](../../development/architecture.md#gitaly) ([2](#footnotes)) ([7](#footnotes)) ([10](#footnotes)) | Provides access to Git repositories | [Gitaly configuration](../gitaly/index.md#running-gitaly-on-its-own-server) | Yes |
| [Sidekiq](../../development/architecture.md#sidekiq) | Asynchronous/background jobs | [Sidekiq configuration](../high_availability/sidekiq.md) | Yes |
| [GitLab application services](../../development/architecture.md#unicorn)([1](#footnotes)) | Puma/Unicorn, Workhorse, GitLab Shell - serves front-end requests (UI, API, Git over HTTP/SSH) | [GitLab app scaling configuration](../high_availability/gitlab.md) | Yes |
@@ -177,7 +177,7 @@ column.
on workload.
1. Gitaly node requirements are dependent on customer data, specifically the number of
- projects and their sizes. We recommend two nodes as an absolute minimum for HA environments
+ projects and their sizes. We recommend two nodes as an absolute minimum,
and at least four nodes should be used when supporting 50,000 or more users.
We also recommend that each Gitaly node should store no more than 5TB of data
and have the number of [`gitaly-ruby` workers](../gitaly/index.md#gitaly-ruby)
@@ -194,7 +194,7 @@ column.
that you run the Redis Sentinel clusters separately for each Redis Cluster.
1. For data objects such as LFS, Uploads, Artifacts, etc. We recommend an [Object Storage service](../object_storage.md)
- over NFS where possible, due to better performance and availability.
+ over NFS where possible, due to better performance.
1. NFS can be used as an alternative for object storage but this isn't typically
recommended for performance reasons. Note however it is required for [GitLab
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index d14b829bdcc..6d9ab723d2f 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -7,8 +7,8 @@ integrity of all data committed to a repository. GitLab administrators
can trigger such a check for a project via the project page under the
admin panel. The checks run asynchronously so it may take a few minutes
before the check result is visible on the project admin page. If the
-checks failed you can see their output on the admin log page under
-'repocheck.log'.
+checks failed you can see their output on in the [`repocheck.log`
+file.](logs.md#repochecklog)
NOTE: **Note:**
It is OFF by default because it still causes too many false alarms.
@@ -31,12 +31,10 @@ panel.
## What to do if a check failed
If the repository check fails for some repository you should look up the error
-in `repocheck.log`:
+in the [`repocheck.log` file](logs.md#repochecklog) on disk:
-- in the [admin panel](logs.md#repochecklog)
-- or on disk, see:
- - `/var/log/gitlab/gitlab-rails` for Omnibus installations
- - `/home/git/gitlab/log` for installations from source
+- `/var/log/gitlab/gitlab-rails` for Omnibus installations
+- `/home/git/gitlab/log` for installations from source
If the periodic repository check causes false alarms, you can clear all repository check states by
navigating to **Admin Area > Settings > Repository**
diff --git a/doc/api/metrics_dashboard_annotations.md b/doc/api/metrics_dashboard_annotations.md
index a4d5985803f..09187a096ef 100644
--- a/doc/api/metrics_dashboard_annotations.md
+++ b/doc/api/metrics_dashboard_annotations.md
@@ -11,6 +11,9 @@ POST /environments/:id/metrics_dashboard/annotations/
POST /clusters/:id/metrics_dashboard/annotations/
```
+NOTE: **Note:**
+The value of `dashboard_path` will be treated as a CGI-escaped path, and automatically unescaped.
+
Parameters:
| Attribute | Type | Required | Description |
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 4b77d4952eb..4bba4f2bc5a 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -38,3 +38,4 @@ The following are available Rake tasks:
| [User management](user_management.md) | Perform user management tasks. |
| [Webhooks administration](web_hooks.md) | Maintain project Webhooks. |
| [X.509 signatures](x509_signatures.md) | Update X.509 commit signatures, useful if certificate store has changed. |
+| [Migrate Snippets to Git](migrate_snippets.md) | Migrate GitLab Snippets to Git repositories and show migration status |
diff --git a/doc/raketasks/migrate_snippets.md b/doc/raketasks/migrate_snippets.md
new file mode 100644
index 00000000000..fce91ab703e
--- /dev/null
+++ b/doc/raketasks/migrate_snippets.md
@@ -0,0 +1,96 @@
+# Migration to Versioned Snippets **(CORE ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215861) in GitLab 13.0.
+
+In GitLab 13.0, [GitLab Snippets are backed by Git repositories](../user/snippets.md#versioned-snippets).
+This means that the snippet content will be stored in the repository
+and users can update it directly through Git.
+
+Nevertheless, existing GitLab Snippets have to be migrated to this new functionality.
+For each snippet, a new repository is created and the snippet content is committed
+to the repository inside a file whose name is the file name used in the snippet
+as well.
+
+GitLab performs this migration through a [Background Migration](../development/background_migrations.md)
+automatically when the GitLab instance is upgrade to 13.0 or a higher version.
+However, if the migration fails for any of the snippets, they still need
+to be migrated individually.
+
+The following Rake tasks will help with that process.
+
+## Migrate specific snippets to Git
+
+In case you want to migrate a range of snippets, run the tasks as described below.
+
+For Omnibus installations, run:
+
+```shell
+sudo gitlab-rake gitlab:snippets:migrate SNIPPET_IDS=1,2,3,4
+```
+
+For installations from source code, run:
+
+```shell
+bundle exec rake gitlab:snippets:migrate SNIPPET_IDS=1,2,3,4
+```
+
+There is a default limit (100) to the number of ids supported in the migration
+process. You can modify this limit by using the env variable `LIMIT`.
+
+```shell
+sudo gitlab-rake gitlab:snippets:migrate SNIPPET_IDS=1,2,3,4 LIMIT=50
+```
+
+For installations from source code, run:
+
+```shell
+bundle exec rake gitlab:snippets:migrate SNIPPET_IDS=1,2,3,4 LIMIT=50
+```
+
+## Show whether the snippet background migration is running
+
+In case you want to check the status of the snippet background migration,
+whether it is running or not, you can use the following task.
+
+For Omnibus installations, run:
+
+```shell
+sudo gitlab-rake gitlab:snippets:migration_status
+```
+
+For installations from source code, run:
+
+```shell
+bundle exec rake gitlab:snippets:migration_status RAILS_ENV=production
+```
+
+## List non-migrated snippets
+
+With the following task, you can get the ids of all of the snippets
+that haven't been migrated yet or failed to migrate.
+
+For Omnibus installations, run:
+
+```shell
+sudo gitlab-rake gitlab:snippets:list_non_migrated
+```
+
+For installations from source code, run:
+
+```shell
+bundle exec rake gitlab:snippets:list_non_migrated RAILS_ENV=production
+```
+
+As the number of non-migrated snippets can be large, we limit
+by default the size of the number of ids returned to 100. You can
+modify this limit by using the env variable `LIMIT`.
+
+```shell
+sudo gitlab-rake gitlab:snippets:list_non_migrated LIMIT=200
+```
+
+For installations from source code, run:
+
+```shell
+bundle exec rake gitlab:snippets:list_non_migrated RAILS_ENV=production LIMIT=200
+```
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index bc297d5891d..22221531360 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -87,7 +87,7 @@ must be set.
While you can view and manage the full details of an issue on the [issue page](#issue-page),
you can also work with multiple issues at a time using the [Issues List](#issues-list),
-[Issue Boards](#issue-boards), Issue references, and [Epics](#epics-ultimate)**(ULTIMATE)**.
+[Issue Boards](#issue-boards), Issue references, and [Epics](#epics-premium)**(PREMIUM)**.
Key actions for Issues include:
@@ -137,7 +137,7 @@ With [Design Management](design_management.md), you can upload design
assets to issues and view them all together to easily share and
collaborate with your team.
-### Epics **(ULTIMATE)**
+### Epics **(PREMIUM)**
[Epics](../../group/epics/index.md) let you manage your portfolio of projects more
efficiently and with less effort by tracking groups of issues that share a theme, across
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 06524f785ab..65e21566d5d 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -16,7 +16,7 @@ You can find all the information for that issue on one screen.
- **2.** [To Do](#to-do)
- **3.** [Assignee](#assignee)
- **3.1.** [Multiple Assignees **(STARTER)**](#multiple-assignees-starter)
-- **4.** [Epic **(ULTIMATE)**](#epic-ultimate)
+- **4.** [Epic **(PREMIUM)**](#epic-premium)
- **5.** [Milestone](#milestone)
- **6.** [Time tracking](#time-tracking)
- **7.** [Due date](#due-date)
@@ -100,7 +100,7 @@ to track in large teams where there is shared ownership of an issue.
In [GitLab Starter](https://about.gitlab.com/pricing/), you can
[assign multiple people](multiple_assignees_for_issues.md) to an issue.
-### Epic **(ULTIMATE)**
+### Epic **(PREMIUM)**
You can assign issues to an [Epic](../../group/epics/index.md), which allows better
management of groups of related issues.
diff --git a/lib/gitlab/background_migration/backfill_snippet_repositories.rb b/lib/gitlab/background_migration/backfill_snippet_repositories.rb
index dc8866062db..21538000fec 100644
--- a/lib/gitlab/background_migration/backfill_snippet_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_snippet_repositories.rb
@@ -8,7 +8,21 @@ module Gitlab
MAX_RETRIES = 2
def perform(start_id, stop_id)
- Snippet.includes(:author, snippet_repository: :shard).where(id: start_id..stop_id).find_each do |snippet|
+ snippets = snippet_relation.where(id: start_id..stop_id)
+
+ migrate_snippets(snippets)
+ end
+
+ def perform_by_ids(snippet_ids)
+ snippets = snippet_relation.where(id: snippet_ids)
+
+ migrate_snippets(snippets)
+ end
+
+ private
+
+ def migrate_snippets(snippets)
+ snippets.find_each do |snippet|
# We need to expire the exists? value for the cached method in case it was cached
snippet.repository.expire_exists_cache
@@ -38,7 +52,9 @@ module Gitlab
end
end
- private
+ def snippet_relation
+ @snippet_relation ||= Snippet.includes(:author, snippet_repository: :shard)
+ end
def repository_present?(snippet)
snippet.snippet_repository && !snippet.empty_repo?
diff --git a/lib/tasks/gitlab/snippets.rake b/lib/tasks/gitlab/snippets.rake
new file mode 100644
index 00000000000..c391cecfdbc
--- /dev/null
+++ b/lib/tasks/gitlab/snippets.rake
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :snippets do
+ DEFAULT_LIMIT = 100
+
+ # @example
+ # bundle exec rake gitlab:snippets:migrate SNIPPET_IDS=1,2,3,4
+ # bundle exec rake gitlab:snippets:migrate SNIPPET_IDS=1,2,3,4 LIMIT=50
+ desc 'GitLab | Migrate specific snippets to git'
+ task :migrate, [:ids] => :environment do |_, args|
+ unless ENV['SNIPPET_IDS'].presence
+ raise "Please supply the list of ids through the SNIPPET_IDS env var"
+ end
+
+ raise "Invalid limit value" if limit.zero?
+
+ if migration_running?
+ raise "There are already snippet migrations running. Please wait until they are finished."
+ end
+
+ ids = parse_snippet_ids!
+
+ puts "Starting the migration..."
+ Gitlab::BackgroundMigration::BackfillSnippetRepositories.new.perform_by_ids(ids)
+
+ list_non_migrated = non_migrated_snippets.where(id: ids)
+
+ if list_non_migrated.exists?
+ puts "The following snippets couldn't be migrated:"
+ puts list_non_migrated.pluck(:id).join(',')
+ else
+ puts "All snippets were migrated successfully"
+ end
+ end
+
+ def parse_snippet_ids!
+ ids = ENV['SNIPPET_IDS'].delete(' ').split(',').map do |id|
+ id.to_i.tap do |value|
+ raise "Invalid id provided" if value.zero?
+ end
+ end
+
+ if ids.size > limit
+ raise "The number of ids provided is higher than #{limit}. You can update this limit by using the env var `LIMIT`"
+ end
+
+ ids
+ end
+
+ # @example
+ # bundle exec rake gitlab:snippets:migration_status
+ desc 'GitLab | Show whether there are snippet background migrations running'
+ task migration_status: :environment do
+ if migration_running?
+ puts "There are snippet migrations running"
+ else
+ puts "There are no snippet migrations running"
+ end
+ end
+
+ def migration_running?
+ Sidekiq::ScheduledSet.new.any? { |r| r.klass == 'BackgroundMigrationWorker' && r.args[0] == 'BackfillSnippetRepositories' }
+ end
+
+ # @example
+ # bundle exec rake gitlab:snippets:list_non_migrated
+ # bundle exec rake gitlab:snippets:list_non_migrated LIMIT=50
+ desc 'GitLab | Show non migrated snippets'
+ task list_non_migrated: :environment do
+ raise "Invalid limit value" if limit.zero?
+
+ non_migrated_count = non_migrated_snippets.count
+ if non_migrated_count.zero?
+ puts "All snippets have been successfully migrated"
+ else
+ puts "There are #{non_migrated_count} snippets that haven't been migrated. Showing a batch of ids of those snippets:\n"
+ puts non_migrated_snippets.limit(limit).pluck(:id).join(',')
+ end
+ end
+
+ def non_migrated_snippets
+ @non_migrated_snippets ||= Snippet.select(:id).where.not(id: SnippetRepository.select(:snippet_id))
+ end
+
+ # There are problems with the specs if we memoize this value
+ def limit
+ ENV['LIMIT'] ? ENV['LIMIT'].to_i : DEFAULT_LIMIT
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a47e2e7e0e8..753fec7f6e5 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -393,6 +393,12 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
+msgid "%{name} has %{percent} or less Shared Runner Pipeline minutes remaining. Once it runs out, no new jobs or pipelines in its projects will run."
+msgstr ""
+
+msgid "%{name} has run out of Shared Runner Pipeline minutes so no new jobs or pipelines in its projects will run."
+msgstr ""
+
msgid "%{name} is scheduled for %{action}"
msgstr ""
@@ -24007,6 +24013,12 @@ msgstr ""
msgid "We heard back from your U2F device. You have been authenticated."
msgstr ""
+msgid "We recommend that you buy more Pipeline minutes to avoid any interruption of service."
+msgstr ""
+
+msgid "We recommend that you buy more Pipeline minutes to resume normal service."
+msgstr ""
+
msgid "We sent you an email with reset password instructions"
msgstr ""
diff --git a/spec/controllers/projects/alert_management_controller_spec.rb b/spec/controllers/projects/alert_management_controller_spec.rb
index 31185aa948a..8fc2319ff88 100644
--- a/spec/controllers/projects/alert_management_controller_spec.rb
+++ b/spec/controllers/projects/alert_management_controller_spec.rb
@@ -14,32 +14,14 @@ describe Projects::AlertManagementController do
end
describe 'GET #index' do
- context 'when alert_management_minimal is enabled' do
- before do
- stub_feature_flags(alert_management_minimal: true)
- end
-
- it 'shows the page' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ it 'shows the page' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
- expect(response).to have_gitlab_http_status(:ok)
- end
-
- context 'when user is unauthorized' do
- let(:role) { :reporter }
-
- it 'shows 404' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
+ expect(response).to have_gitlab_http_status(:ok)
end
- context 'when alert_management_minimal is disabled' do
- before do
- stub_feature_flags(alert_management_minimal: false)
- end
+ context 'when user is unauthorized' do
+ let(:role) { :reporter }
it 'shows 404' do
get :index, params: { namespace_id: project.namespace, project_id: project }
diff --git a/spec/frontend/behaviors/markdown/render_metrics_spec.js b/spec/frontend/behaviors/markdown/render_metrics_spec.js
index 3f7beeb817b..ab81ed6b8f0 100644
--- a/spec/frontend/behaviors/markdown/render_metrics_spec.js
+++ b/spec/frontend/behaviors/markdown/render_metrics_spec.js
@@ -11,20 +11,20 @@ const getElements = () => Array.from(document.getElementsByClassName('js-render-
describe('Render metrics for Gitlab Flavoured Markdown', () => {
it('does nothing when no elements are found', () => {
- renderMetrics([]);
-
- expect(mockEmbedGroup).not.toHaveBeenCalled();
+ return renderMetrics([]).then(() => {
+ expect(mockEmbedGroup).not.toHaveBeenCalled();
+ });
});
it('renders a vue component when elements are found', () => {
document.body.innerHTML = `<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}"></div>`;
- renderMetrics(getElements());
-
- expect(mockEmbedGroup).toHaveBeenCalledTimes(1);
- expect(mockEmbedGroup).toHaveBeenCalledWith(
- expect.objectContaining({ propsData: { urls: [`${TEST_HOST}`] } }),
- );
+ return renderMetrics(getElements()).then(() => {
+ expect(mockEmbedGroup).toHaveBeenCalledTimes(1);
+ expect(mockEmbedGroup).toHaveBeenCalledWith(
+ expect.objectContaining({ propsData: { urls: [`${TEST_HOST}`] } }),
+ );
+ });
});
it('takes sibling metrics and groups them under a shared parent', () => {
@@ -36,14 +36,14 @@ describe('Render metrics for Gitlab Flavoured Markdown', () => {
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/3"></div>
`;
- renderMetrics(getElements());
-
- expect(mockEmbedGroup).toHaveBeenCalledTimes(2);
- expect(mockEmbedGroup).toHaveBeenCalledWith(
- expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/1`, `${TEST_HOST}/2`] } }),
- );
- expect(mockEmbedGroup).toHaveBeenCalledWith(
- expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/3`] } }),
- );
+ return renderMetrics(getElements()).then(() => {
+ expect(mockEmbedGroup).toHaveBeenCalledTimes(2);
+ expect(mockEmbedGroup).toHaveBeenCalledWith(
+ expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/1`, `${TEST_HOST}/2`] } }),
+ );
+ expect(mockEmbedGroup).toHaveBeenCalledWith(
+ expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/3`] } }),
+ );
+ });
});
});
diff --git a/spec/frontend/ide/stores/utils_spec.js b/spec/frontend/ide/stores/utils_spec.js
index 90f2644de62..b87f6c1f05a 100644
--- a/spec/frontend/ide/stores/utils_spec.js
+++ b/spec/frontend/ide/stores/utils_spec.js
@@ -685,4 +685,75 @@ describe('Multi-file store utils', () => {
});
});
});
+
+ describe('extractMarkdownImagesFromEntries', () => {
+ let mdFile;
+ let entries;
+
+ beforeEach(() => {
+ const img = { content: '/base64/encoded/image+' };
+ mdFile = { path: 'path/to/some/directory/myfile.md' };
+ entries = {
+ // invalid (or lack of) extensions are also supported as long as there's
+ // a real image inside and can go into an <img> tag's `src` and the browser
+ // can render it
+ img,
+ 'img.js': img,
+ 'img.png': img,
+ 'img.with.many.dots.png': img,
+ 'path/to/img.gif': img,
+ 'path/to/some/img.jpg': img,
+ 'path/to/some/img 1/img.png': img,
+ 'path/to/some/directory/img.png': img,
+ 'path/to/some/directory/img 1.png': img,
+ };
+ });
+
+ it.each`
+ markdownBefore | ext | imgAlt | imgTitle
+ ${'* ![img](/img)'} | ${'jpeg'} | ${'img'} | ${undefined}
+ ${'* ![img](/img.js)'} | ${'js'} | ${'img'} | ${undefined}
+ ${'* ![img](img.png)'} | ${'png'} | ${'img'} | ${undefined}
+ ${'* ![img](./img.png)'} | ${'png'} | ${'img'} | ${undefined}
+ ${'* ![with spaces](../img 1/img.png)'} | ${'png'} | ${'with spaces'} | ${undefined}
+ ${'* ![img](../../img.gif " title ")'} | ${'gif'} | ${'img'} | ${' title '}
+ ${'* ![img](../img.jpg)'} | ${'jpg'} | ${'img'} | ${undefined}
+ ${'* ![img](/img.png "title")'} | ${'png'} | ${'img'} | ${'title'}
+ ${'* ![img](/img.with.many.dots.png)'} | ${'png'} | ${'img'} | ${undefined}
+ ${'* ![img](img 1.png)'} | ${'png'} | ${'img'} | ${undefined}
+ ${'* ![img](img.png "title here")'} | ${'png'} | ${'img'} | ${'title here'}
+ `(
+ 'correctly transforms markdown with uncommitted images: $markdownBefore',
+ ({ markdownBefore, ext, imgAlt, imgTitle }) => {
+ mdFile.content = markdownBefore;
+
+ expect(utils.extractMarkdownImagesFromEntries(mdFile, entries)).toEqual({
+ content: '* {{gl_md_img_1}}',
+ images: {
+ '{{gl_md_img_1}}': {
+ src: `data:image/${ext};base64,/base64/encoded/image+`,
+ alt: imgAlt,
+ title: imgTitle,
+ },
+ },
+ });
+ },
+ );
+
+ it.each`
+ markdown
+ ${'* ![img](i.png)'}
+ ${'* ![img](img.png invalid title)'}
+ ${'* ![img](img.png "incorrect" "markdown")'}
+ ${'* ![img](https://gitlab.com/logo.png)'}
+ ${'* ![img](https://gitlab.com/some/deep/nested/path/logo.png)'}
+ `("doesn't touch invalid or non-existant images in markdown: $markdown", ({ markdown }) => {
+ mdFile.content = markdown;
+
+ expect(utils.extractMarkdownImagesFromEntries(mdFile, entries)).toEqual({
+ content: markdown,
+ images: {},
+ });
+ });
+ });
});
diff --git a/spec/frontend/reports/accessibility_report/store/getters_spec.js b/spec/frontend/reports/accessibility_report/store/getters_spec.js
index db8f48c067a..d74c71cfa09 100644
--- a/spec/frontend/reports/accessibility_report/store/getters_spec.js
+++ b/spec/frontend/reports/accessibility_report/store/getters_spec.js
@@ -115,39 +115,33 @@ describe('Accessibility reports store getters', () => {
});
describe('unresolvedIssues', () => {
- it('returns concatenated array of unresolved errors, warnings, and notes', () => {
+ it('returns the array unresolved errors', () => {
localState.report = {
existing_errors: [1],
- existing_warnings: [2],
- existing_notes: [3],
};
- const result = [1, 2, 3];
+ const result = [1];
expect(getters.unresolvedIssues(localState)).toEqual(result);
});
});
describe('resolvedIssues', () => {
- it('returns concatenated array of resolved errors, warnings, and notes', () => {
+ it('returns array of resolved errors', () => {
localState.report = {
resolved_errors: [1],
- resolved_warnings: [2],
- resolved_notes: [3],
};
- const result = [1, 2, 3];
+ const result = [1];
expect(getters.resolvedIssues(localState)).toEqual(result);
});
});
describe('newIssues', () => {
- it('returns concatenated array of new errors, warnings, and notes', () => {
+ it('returns array of new errors', () => {
localState.report = {
new_errors: [1],
- new_warnings: [2],
- new_notes: [3],
};
- const result = [1, 2, 3];
+ const result = [1];
expect(getters.newIssues(localState)).toEqual(result);
});
diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
index 587b9f07e53..8d3fcdd48d2 100644
--- a/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/content_viewer/viewers/markdown_viewer_spec.js
@@ -35,7 +35,7 @@ describe('MarkdownViewer', () => {
describe('success', () => {
beforeEach(() => {
mock.onPost(`${gon.relative_url_root}/testproject/preview_markdown`).replyOnce(200, {
- body: '<b>testing</b>',
+ body: '<b>testing</b> {{gl_md_img_1}}',
});
});
@@ -53,15 +53,48 @@ describe('MarkdownViewer', () => {
});
});
- it('receives the filePath as a parameter and passes it on to the server', () => {
- createComponent({ filePath: 'foo/test.md' });
+ it('receives the filePath and commitSha as a parameters and passes them on to the server', () => {
+ createComponent({ filePath: 'foo/test.md', commitSha: 'abcdef' });
expect(axios.post).toHaveBeenCalledWith(
`${gon.relative_url_root}/testproject/preview_markdown`,
- { path: 'foo/test.md', text: '* Test' },
+ { path: 'foo/test.md', text: '* Test', ref: 'abcdef' },
expect.any(Object),
);
});
+
+ it.each`
+ imgSrc | imgAlt
+ ${'data:image/jpeg;base64,AAAAAA+/'} | ${'my image title'}
+ ${'data:image/jpeg;base64,AAAAAA+/'} | ${'"somebody\'s image" &'}
+ ${'hack" onclick=alert(0)'} | ${'hack" onclick=alert(0)'}
+ ${'hack\\" onclick=alert(0)'} | ${'hack\\" onclick=alert(0)'}
+ ${"hack' onclick=alert(0)"} | ${"hack' onclick=alert(0)"}
+ ${"hack'><script>alert(0)</script>"} | ${"hack'><script>alert(0)</script>"}
+ `(
+ 'transforms template tags with base64 encoded images available locally',
+ ({ imgSrc, imgAlt }) => {
+ createComponent({
+ images: {
+ '{{gl_md_img_1}}': {
+ src: imgSrc,
+ alt: imgAlt,
+ title: imgAlt,
+ },
+ },
+ });
+
+ return waitForPromises().then(() => {
+ const img = wrapper.find('.md-previewer img').element;
+
+ // if the values are the same as the input, it means
+ // they were escaped correctly
+ expect(img).toHaveAttr('src', imgSrc);
+ expect(img).toHaveAttr('alt', imgAlt);
+ expect(img).toHaveAttr('title', imgAlt);
+ });
+ },
+ );
});
describe('error', () => {
diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb
index ecc0589bfdf..4b3bfc99c88 100644
--- a/spec/serializers/merge_request_poll_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb
@@ -93,6 +93,28 @@ describe MergeRequestPollWidgetEntity do
end
end
+ describe 'accessibility_report_path' do
+ context 'when merge request has accessibility reports' do
+ before do
+ allow(resource).to receive(:has_accessibility_reports?).and_return(true)
+ end
+
+ it 'set the path to poll data' do
+ expect(subject[:accessibility_report_path]).to be_present
+ end
+ end
+
+ context 'when merge request has no accessibility reports' do
+ before do
+ allow(resource).to receive(:has_accessibility_reports?).and_return(false)
+ end
+
+ it 'set the path to poll data' do
+ expect(subject[:accessibility_report_path]).to be_nil
+ end
+ end
+ end
+
describe 'exposed_artifacts_path' do
context 'when merge request has exposed artifacts' do
before do
diff --git a/spec/tasks/gitlab/snippets_rake_spec.rb b/spec/tasks/gitlab/snippets_rake_spec.rb
new file mode 100644
index 00000000000..c4bb8d7897c
--- /dev/null
+++ b/spec/tasks/gitlab/snippets_rake_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe 'gitlab:snippets namespace rake task' do
+ let_it_be(:user) { create(:user)}
+ let_it_be(:migrated) { create(:personal_snippet, :repository, author: user) }
+ let(:non_migrated) { create_list(:personal_snippet, 3, author: user) }
+ let(:non_migrated_ids) { non_migrated.pluck(:id) }
+
+ before :all do
+ Rake.application.rake_require 'tasks/gitlab/snippets'
+ end
+
+ describe 'migrate' do
+ subject { run_rake_task('gitlab:snippets:migrate') }
+
+ before do
+ stub_env('SNIPPET_IDS' => non_migrated_ids.join(','))
+ end
+
+ it 'can migrate specific snippets passing ids' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::BackfillSnippetRepositories) do |instance|
+ expect(instance).to receive(:perform_by_ids).with(non_migrated_ids).and_call_original
+ end
+
+ expect { subject }.to output(/All snippets were migrated successfully/).to_stdout
+ end
+
+ it 'returns the ids of those snippet that failed the migration' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::BackfillSnippetRepositories) do |instance|
+ expect(instance).to receive(:perform_by_ids).with(non_migrated_ids)
+ end
+
+ expect { subject }.to output(/The following snippets couldn't be migrated:\n#{non_migrated_ids.join(',')}/).to_stdout
+ end
+
+ it 'fails if the SNIPPET_IDS env var is not set' do
+ stub_env('SNIPPET_IDS' => nil)
+
+ expect { subject }.to raise_error(RuntimeError, 'Please supply the list of ids through the SNIPPET_IDS env var')
+ end
+
+ it 'fails if the number of ids provided is higher than the limit' do
+ stub_env('LIMIT' => 2)
+
+ expect { subject }.to raise_error(RuntimeError, /The number of ids provided is higher than/)
+ end
+
+ it 'fails if the env var LIMIT is invalid' do
+ stub_env('LIMIT' => 'foo')
+
+ expect { subject }.to raise_error(RuntimeError, 'Invalid limit value')
+ end
+
+ it 'fails if the ids are invalid' do
+ stub_env('SNIPPET_IDS' => '1,2,a')
+
+ expect { subject }.to raise_error(RuntimeError, 'Invalid id provided')
+ end
+
+ it 'fails if the snippet background migration is running' do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(180, 'BackfillSnippetRepositories', [non_migrated.first.id, non_migrated.last.id])
+ expect(Sidekiq::ScheduledSet.new).to be_one
+
+ expect { subject }.to raise_error(RuntimeError, /There are already snippet migrations running/)
+
+ Sidekiq::ScheduledSet.new.clear
+ end
+ end
+ end
+
+ describe 'migration_status' do
+ subject { run_rake_task('gitlab:snippets:migration_status') }
+
+ it 'returns a message when the background migration is not running' do
+ expect { subject }.to output("There are no snippet migrations running\n").to_stdout
+ end
+
+ it 'returns a message saying that the background migration is running' do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(180, 'BackfillSnippetRepositories', [non_migrated.first.id, non_migrated.last.id])
+ expect(Sidekiq::ScheduledSet.new).to be_one
+
+ expect { subject }.to output("There are snippet migrations running\n").to_stdout
+
+ Sidekiq::ScheduledSet.new.clear
+ end
+ end
+ end
+
+ describe 'list_non_migrated' do
+ subject { run_rake_task('gitlab:snippets:list_non_migrated') }
+
+ it 'returns a message if all snippets are migrated' do
+ expect { subject }.to output("All snippets have been successfully migrated\n").to_stdout
+ end
+
+ context 'when there are still non migrated snippets' do
+ let!(:non_migrated) { create_list(:personal_snippet, 3, author: user) }
+
+ it 'returns a message returning the non migrated snippets ids' do
+ expect { subject }.to output(/#{non_migrated_ids}/).to_stdout
+ end
+
+ it 'returns as many snippet ids as the limit set' do
+ stub_env('LIMIT' => 1)
+
+ expect { subject }.to output(/#{non_migrated_ids[0]}/).to_stdout
+ end
+ end
+ end
+end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 41bb5f55453..45866ad7d9f 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -163,28 +163,10 @@ describe 'layouts/nav/sidebar/_project' do
end
describe 'Alert Management' do
- context 'when alert_management_minimal is enabled' do
- before do
- stub_feature_flags(alert_management_minimal: true)
- end
-
- it 'shows the Alerts sidebar entry' do
- render
-
- expect(rendered).to have_css('a[title="Alerts"]')
- end
- end
-
- context 'when alert_management_minimal is disabled' do
- before do
- stub_feature_flags(alert_management_minimal: false)
- end
-
- it 'does not show the Alerts sidebar entry' do
- render
+ it 'shows the Alerts sidebar entry' do
+ render
- expect(rendered).to have_no_css('a[title="Alerts"]')
- end
+ expect(rendered).to have_css('a[title="Alerts"]')
end
end
end