summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md20
-rw-r--r--app/assets/javascripts/content_editor/constants/index.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/sourcemap.js4
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js10
-rw-r--r--app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js49
-rw-r--r--app/assets/javascripts/jobs/components/filtered_search/constants.js13
-rw-r--r--app/assets/javascripts/jobs/components/filtered_search/jobs_filtered_search.vue22
-rw-r--r--app/assets/javascripts/jobs/components/filtered_search/utils.js23
-rw-r--r--app/assets/javascripts/jobs/components/table/jobs_table_app.vue13
-rw-r--r--app/views/devise/sessions/new.html.haml7
-rw-r--r--app/views/groups/settings/packages_and_registries/show.html.haml4
-rw-r--r--app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml4
-rw-r--r--app/views/projects/settings/packages_and_registries/show.html.haml4
-rw-r--r--config/feature_flags/development/cache_issue_sums.yml8
-rw-r--r--db/post_migrate/20220815061621_rename_web_hooks_service_id_to_integration_id.rb13
-rw-r--r--db/post_migrate/20220822090656_drop_build_coverage_regex_from_project.rb13
-rw-r--r--db/schema_migrations/202208150616211
-rw-r--r--db/schema_migrations/202208220906561
-rw-r--r--db/structure.sql49
-rw-r--r--doc/integration/saml.md6
-rw-r--r--glfm_specification/example_snapshots/examples_index.yml12
-rw-r--r--glfm_specification/example_snapshots/html.yml30
-rw-r--r--glfm_specification/example_snapshots/markdown.yml12
-rw-r--r--glfm_specification/example_snapshots/prosemirror_json.yml108
-rw-r--r--glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt51
-rw-r--r--glfm_specification/output/spec.txt51
-rw-r--r--lib/api/ci/runners.rb8
-rw-r--r--lib/gitlab/cache/import/caching.rb21
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json2
-rw-r--r--spec/features/groups/settings/packages_and_registries_spec.rb2
-rw-r--r--spec/features/users/signup_spec.rb5
-rw-r--r--spec/frontend/content_editor/remark_markdown_processing_spec.js47
-rw-r--r--spec/frontend/content_editor/render_html_and_json_for_all_examples.js4
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js21
-rw-r--r--spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js34
-rw-r--r--spec/frontend/jobs/components/filtered_search/utils_spec.js18
-rw-r--r--spec/frontend/jobs/components/table/job_table_app_spec.js14
-rw-r--r--spec/lib/gitlab/cache/import/caching_spec.rb45
-rw-r--r--spec/requests/api/ci/runners_spec.rb32
-rw-r--r--yarn.lock8
41 files changed, 764 insertions, 37 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9fe3c40d71..d5c1c058e9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 15.3.1 (2022-08-22)
+
+### Security (1 change)
+
+- [Validate if values to be saved in Redis can be converted to string](gitlab-org/security/gitlab@e8a4aeff901363923a5ddff3f7c6b654abf2b125) ([merge request](gitlab-org/security/gitlab!2723))
+
## 15.3.0 (2022-08-19)
### Added (147 changes)
@@ -607,6 +613,13 @@ entry.
- [Remove FF import_release_authors_from_github](gitlab-org/gitlab@c4d6871e4438a1626d688856903778623138f671) ([merge request](gitlab-org/gitlab!92686))
- [Remove unused feature](gitlab-org/gitlab@0ef95d341e4a15150d6ccb3d104ebbe064aa062a) ([merge request](gitlab-org/gitlab!92753))
+## 15.2.3 (2022-08-22)
+
+### Security (2 changes)
+
+- [Validate if values to be saved in Redis can be converted to string](gitlab-org/security/gitlab@427c7818b229fd45b10cb5de9ea6cc7c451dd4da) ([merge request](gitlab-org/security/gitlab!2724))
+- [Fix CSS selector used in specs](gitlab-org/security/gitlab@47bb40d097e2b05ecdbeebf6bdbe6eb9b6db1c7b) ([merge request](gitlab-org/security/gitlab!2727))
+
## 15.2.2 (2022-08-01)
### Fixed (6 changes)
@@ -1323,6 +1336,13 @@ entry.
- [Update GitLab Runner Helm Chart to 0.42.0](gitlab-org/gitlab@cc89200f498fe216864914c79b5b0d1d578edab3) ([merge request](gitlab-org/gitlab!90605))
- [Address database documentation Vale warningss](gitlab-org/gitlab@e5f9a089766bace046d3bbd760a2979865a4bbc0) by @cgives ([merge request](gitlab-org/gitlab!90093))
+## 15.1.5 (2022-08-22)
+
+### Security (2 changes)
+
+- [Validate if values to be saved in Redis can be converted to string](gitlab-org/security/gitlab@0bc82f875f78b6a2703b294b1a5a8d941000f9f2) ([merge request](gitlab-org/security/gitlab!2725))
+- [Fix CSS selector used in specs](gitlab-org/security/gitlab@a900e65b192a8415baa6bcd4050566543107ab1f) ([merge request](gitlab-org/security/gitlab!2728))
+
## 15.1.4 (2022-07-28)
### Security (18 changes)
diff --git a/app/assets/javascripts/content_editor/constants/index.js b/app/assets/javascripts/content_editor/constants/index.js
index a39a243ec6b..c7fe64a5fb6 100644
--- a/app/assets/javascripts/content_editor/constants/index.js
+++ b/app/assets/javascripts/content_editor/constants/index.js
@@ -58,3 +58,9 @@ export const EXTENSION_PRIORITY_LOWER = 75;
*/
export const EXTENSION_PRIORITY_DEFAULT = 100;
export const EXTENSION_PRIORITY_HIGHEST = 200;
+
+/**
+ * See lib/gitlab/file_type_detection.rb
+ */
+export const SAFE_VIDEO_EXT = ['mp4', 'm4v', 'mov', 'webm', 'ogv'];
+export const SAFE_AUDIO_EXT = ['mp3', 'oga', 'ogg', 'spx', 'wav'];
diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js
index f9de71f601b..ef02bc872e2 100644
--- a/app/assets/javascripts/content_editor/extensions/sourcemap.js
+++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js
@@ -1,4 +1,5 @@
import { Extension } from '@tiptap/core';
+import Audio from './audio';
import Blockquote from './blockquote';
import Bold from './bold';
import BulletList from './bullet_list';
@@ -25,12 +26,14 @@ import Table from './table';
import TableCell from './table_cell';
import TableHeader from './table_header';
import TableRow from './table_row';
+import Video from './video';
export default Extension.create({
addGlobalAttributes() {
return [
{
types: [
+ Audio.name,
Bold.name,
Blockquote.name,
BulletList.name,
@@ -56,6 +59,7 @@ export default Extension.create({
TableCell.name,
TableHeader.name,
TableRow.name,
+ Video.name,
...HTMLNodes.map((htmlNode) => htmlNode.name),
],
attributes: {
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index 472a0a4815b..12de6c41f77 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -108,7 +108,10 @@ const defaultSerializerConfig = {
},
nodes: {
- [Audio.name]: renderPlayable,
+ [Audio.name]: preserveUnchanged({
+ render: renderPlayable,
+ inline: true,
+ }),
[Blockquote.name]: preserveUnchanged((state, node) => {
if (node.attrs.multiline) {
state.write('>>>');
@@ -220,7 +223,10 @@ const defaultSerializerConfig = {
else renderBulletList(state, node);
}),
[Text.name]: defaultMarkdownSerializer.nodes.text,
- [Video.name]: renderPlayable,
+ [Video.name]: preserveUnchanged({
+ render: renderPlayable,
+ inline: true,
+ }),
[WordBreak.name]: (state) => state.write('<wbr>'),
...HTMLNodes.reduce((serializers, htmlNode) => {
return {
diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
index 8a15633708f..e5b78fa3d9b 100644
--- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
@@ -1,7 +1,10 @@
import { render } from '~/lib/gfm';
import { isValidAttribute } from '~/lib/dompurify';
+import { SAFE_AUDIO_EXT, SAFE_VIDEO_EXT } from '../constants';
import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter';
+const ALL_AUDIO_VIDEO_EXT = [...SAFE_AUDIO_EXT, ...SAFE_VIDEO_EXT];
+
const wrappableTags = ['img', 'br', 'code', 'i', 'em', 'b', 'strong', 'a', 'strike', 's', 'del'];
const isTaskItem = (hastNode) => {
@@ -17,6 +20,26 @@ const getTableCellAttrs = (hastNode) => ({
rowspan: parseInt(hastNode.properties.rowSpan, 10) || 1,
});
+const getMediaAttrs = (hastNode) => ({
+ src: hastNode.properties.src,
+ canonicalSrc: hastNode.properties.identifier ?? hastNode.properties.src,
+ isReference: hastNode.properties.isReference === 'true',
+ title: hastNode.properties.title,
+ alt: hastNode.properties.alt,
+});
+
+const isMediaTag = (hastNode) => hastNode.tagName === 'img' && Boolean(hastNode.properties);
+
+const extractMediaFileExtension = (url) => {
+ try {
+ const parsedUrl = new URL(url, window.location.origin);
+
+ return /\.(\w+)$/.exec(parsedUrl.pathname)?.[1] ?? null;
+ } catch {
+ return null;
+ }
+};
+
const factorySpecs = {
blockquote: { type: 'block', selector: 'blockquote' },
paragraph: { type: 'block', selector: 'p' },
@@ -121,16 +144,26 @@ const factorySpecs = {
selector: 'pre',
wrapInParagraph: true,
},
+ audio: {
+ type: 'inline',
+ selector: (hastNode) =>
+ isMediaTag(hastNode) &&
+ SAFE_AUDIO_EXT.includes(extractMediaFileExtension(hastNode.properties.src)),
+ getAttrs: getMediaAttrs,
+ },
image: {
type: 'inline',
- selector: 'img',
- getAttrs: (hastNode) => ({
- src: hastNode.properties.src,
- canonicalSrc: hastNode.properties.identifier ?? hastNode.properties.src,
- isReference: hastNode.properties.isReference === 'true',
- title: hastNode.properties.title,
- alt: hastNode.properties.alt,
- }),
+ selector: (hastNode) =>
+ isMediaTag(hastNode) &&
+ !ALL_AUDIO_VIDEO_EXT.includes(extractMediaFileExtension(hastNode.properties.src)),
+ getAttrs: getMediaAttrs,
+ },
+ video: {
+ type: 'inline',
+ selector: (hastNode) =>
+ isMediaTag(hastNode) &&
+ SAFE_VIDEO_EXT.includes(extractMediaFileExtension(hastNode.properties.src)),
+ getAttrs: getMediaAttrs,
},
hardBreak: {
type: 'inline',
diff --git a/app/assets/javascripts/jobs/components/filtered_search/constants.js b/app/assets/javascripts/jobs/components/filtered_search/constants.js
new file mode 100644
index 00000000000..0daba892375
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/filtered_search/constants.js
@@ -0,0 +1,13 @@
+export const jobStatusValues = [
+ 'CANCELED',
+ 'CREATED',
+ 'FAILED',
+ 'MANUAL',
+ 'SUCCESS',
+ 'PENDING',
+ 'PREPARING',
+ 'RUNNING',
+ 'SCHEDULED',
+ 'SKIPPED',
+ 'WAITING_FOR_RESOURCE',
+];
diff --git a/app/assets/javascripts/jobs/components/filtered_search/jobs_filtered_search.vue b/app/assets/javascripts/jobs/components/filtered_search/jobs_filtered_search.vue
index fe7b7428c6e..e498a735898 100644
--- a/app/assets/javascripts/jobs/components/filtered_search/jobs_filtered_search.vue
+++ b/app/assets/javascripts/jobs/components/filtered_search/jobs_filtered_search.vue
@@ -11,6 +11,13 @@ export default {
components: {
GlFilteredSearch,
},
+ props: {
+ queryString: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
computed: {
tokens() {
return [
@@ -24,6 +31,20 @@ export default {
},
];
},
+ filteredSearchValue() {
+ if (this.queryString?.statuses) {
+ return [
+ {
+ type: 'status',
+ value: {
+ data: this.queryString?.statuses,
+ operator: '=',
+ },
+ },
+ ];
+ }
+ return [];
+ },
},
methods: {
onSubmit(filters) {
@@ -37,6 +58,7 @@ export default {
<gl-filtered-search
:placeholder="s__('Jobs|Filter jobs')"
:available-tokens="tokens"
+ :value="filteredSearchValue"
@submit="onSubmit"
/>
</template>
diff --git a/app/assets/javascripts/jobs/components/filtered_search/utils.js b/app/assets/javascripts/jobs/components/filtered_search/utils.js
new file mode 100644
index 00000000000..eef5b738863
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/filtered_search/utils.js
@@ -0,0 +1,23 @@
+import { jobStatusValues } from './constants';
+
+// validates query string used for filtered search
+// on jobs table to ensure GraphQL query is called correctly
+export const validateQueryString = (queryStringObj) => {
+ // currently only one token is supported `statuses`
+ // this code will need to be expanded as more tokens
+ // are introduced
+
+ const filters = Object.keys(queryStringObj);
+
+ if (filters.includes('statuses')) {
+ const found = jobStatusValues.find((status) => status === queryStringObj.statuses);
+
+ if (found) {
+ return queryStringObj;
+ }
+
+ return null;
+ }
+
+ return null;
+};
diff --git a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
index c2f460cb647..b8ba781ab5f 100644
--- a/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
+++ b/app/assets/javascripts/jobs/components/table/jobs_table_app.vue
@@ -2,7 +2,9 @@
import { GlAlert, GlSkeletonLoader, GlIntersectionObserver, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import createFlash from '~/flash';
+import { setUrlParams, updateHistory, queryToObject } from '~/lib/utils/url_utility';
import JobsFilteredSearch from '../filtered_search/jobs_filtered_search.vue';
+import { validateQueryString } from '../filtered_search/utils';
import GetJobs from './graphql/queries/get_jobs.query.graphql';
import JobsTable from './jobs_table.vue';
import JobsTableEmptyState from './jobs_table_empty_state.vue';
@@ -37,6 +39,7 @@ export default {
variables() {
return {
fullPath: this.fullPath,
+ ...this.validatedQueryString,
};
},
update(data) {
@@ -95,6 +98,11 @@ export default {
jobsCount() {
return this.jobs.count;
},
+ validatedQueryString() {
+ const queryStringObject = queryToObject(window.location.search);
+
+ return validateQueryString(queryStringObject);
+ },
},
watch: {
// this watcher ensures that the count on the all tab
@@ -133,6 +141,10 @@ export default {
}
if (filter.type === 'status') {
+ updateHistory({
+ url: setUrlParams({ statuses: filter.value.data }, window.location.href, true),
+ });
+
this.$apollo.queries.jobs.refetch({ statuses: filter.value.data });
}
});
@@ -175,6 +187,7 @@ export default {
<jobs-filtered-search
v-if="showFilteredSearch"
:class="$options.filterSearchBoxStyles"
+ :query-string="validatedQueryString"
@filterJobsBySearch="filterJobsBySearch"
/>
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index f4db9ea5637..e0e0b82b596 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -17,10 +17,15 @@
%div
= _('No authentication methods configured.')
+ - if Feature.enabled?(:restyle_login_page, @project)
+ %p.gl-px-5
+ = html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe,
+ link_end: '</a>'.html_safe }
+
- if allow_signup?
%p{ class: "gl-mt-3 #{'gl-text-center' if Feature.enabled?(:restyle_login_page, @project)}" }
= _("Don't have an account yet?")
- = link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { qa_selector: 'register_link' }, class: "#{'gl-font-weight-bold' if Feature.enabled?(:restyle_login_page, @project)} "
+ = link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { qa_selector: 'register_link' }
- if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
.clearfix
= render 'devise/shared/omniauth_box'
diff --git a/app/views/groups/settings/packages_and_registries/show.html.haml b/app/views/groups/settings/packages_and_registries/show.html.haml
index 888419e463a..e414a59c55d 100644
--- a/app/views/groups/settings/packages_and_registries/show.html.haml
+++ b/app/views/groups/settings/packages_and_registries/show.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title _('Packages & Registries')
-- page_title _('Packages & Registries')
+- breadcrumb_title _('Package & registry settings')
+- page_title _('Package & registry settings')
- @content_class = 'limit-container-width' unless fluid_layout
%section#js-packages-and-registries-settings{ data: { group_path: @group.full_path,
diff --git a/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml b/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml
index 795544b75a2..9994ea0c912 100644
--- a/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml
+++ b/app/views/projects/settings/packages_and_registries/cleanup_tags.html.haml
@@ -1,6 +1,6 @@
-- add_to_breadcrumbs _('Packages & Registries'), project_settings_packages_and_registries_path(@project)
+- add_to_breadcrumbs _('Package & registry settings'), project_settings_packages_and_registries_path(@project)
- breadcrumb_title s_('ContainerRegistry|Clean up image tags')
-- page_title s_('ContainerRegistry|Clean up image tags'), _('Packages & Registries')
+- page_title s_('ContainerRegistry|Clean up image tags'), _('Package & registry settings')
- @content_class = 'limit-container-width' unless fluid_layout
#js-registry-settings-cleanup-image-tags{ data: cleanup_settings_data }
diff --git a/app/views/projects/settings/packages_and_registries/show.html.haml b/app/views/projects/settings/packages_and_registries/show.html.haml
index d579981ebc0..4a8c48be7ad 100644
--- a/app/views/projects/settings/packages_and_registries/show.html.haml
+++ b/app/views/projects/settings/packages_and_registries/show.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title _('Packages & Registries')
-- page_title _('Packages & Registries')
+- breadcrumb_title _('Package & registry settings')
+- page_title _('Package & registry settings')
- @content_class = 'limit-container-width' unless fluid_layout
#js-registry-settings{ data: settings_data }
diff --git a/config/feature_flags/development/cache_issue_sums.yml b/config/feature_flags/development/cache_issue_sums.yml
new file mode 100644
index 00000000000..9d045eca2c9
--- /dev/null
+++ b/config/feature_flags/development/cache_issue_sums.yml
@@ -0,0 +1,8 @@
+---
+name: cache_issue_sums
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95048
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365940
+milestone: '15.3'
+type: development
+group: group::product planning
+default_enabled: false
diff --git a/db/post_migrate/20220815061621_rename_web_hooks_service_id_to_integration_id.rb b/db/post_migrate/20220815061621_rename_web_hooks_service_id_to_integration_id.rb
new file mode 100644
index 00000000000..6bcee7f51df
--- /dev/null
+++ b/db/post_migrate/20220815061621_rename_web_hooks_service_id_to_integration_id.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RenameWebHooksServiceIdToIntegrationId < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :web_hooks, :service_id, :integration_id
+ end
+
+ def down
+ undo_rename_column_concurrently :web_hooks, :service_id, :integration_id
+ end
+end
diff --git a/db/post_migrate/20220822090656_drop_build_coverage_regex_from_project.rb b/db/post_migrate/20220822090656_drop_build_coverage_regex_from_project.rb
new file mode 100644
index 00000000000..70c6b660318
--- /dev/null
+++ b/db/post_migrate/20220822090656_drop_build_coverage_regex_from_project.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class DropBuildCoverageRegexFromProject < Gitlab::Database::Migration[2.0]
+ enable_lock_retries!
+
+ def up
+ remove_column :projects, :build_coverage_regex
+ end
+
+ def down
+ add_column :projects, :build_coverage_regex, :string # rubocop: disable Migration/AddColumnsToWideTables
+ end
+end
diff --git a/db/schema_migrations/20220815061621 b/db/schema_migrations/20220815061621
new file mode 100644
index 00000000000..66fc6a68014
--- /dev/null
+++ b/db/schema_migrations/20220815061621
@@ -0,0 +1 @@
+60a7782e9eaed833362e314fe3ae35f881ee051d9b529c59638833ce92d2db2d \ No newline at end of file
diff --git a/db/schema_migrations/20220822090656 b/db/schema_migrations/20220822090656
new file mode 100644
index 00000000000..bc1ec6d44b9
--- /dev/null
+++ b/db/schema_migrations/20220822090656
@@ -0,0 +1 @@
+696550615046e26d4012d8b5a5fb741d85c23d4d0d08a4a781da0123c0543de1 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index ca1f9c1c061..b291dfe7591 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22,6 +22,40 @@ RETURN NULL;
END
$$;
+CREATE FUNCTION function_for_trigger_a645cee67576() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ NEW."service_id" := NEW."integration_id";
+ RETURN NEW;
+END
+$$;
+
+CREATE FUNCTION function_for_trigger_a87bcfdf0f0b() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ IF NEW."service_id" IS NULL AND NEW."integration_id" IS NOT NULL THEN
+ NEW."service_id" = NEW."integration_id";
+ END IF;
+
+ IF NEW."integration_id" IS NULL AND NEW."service_id" IS NOT NULL THEN
+ NEW."integration_id" = NEW."service_id";
+ END IF;
+
+ RETURN NEW;
+END
+$$;
+
+CREATE FUNCTION function_for_trigger_aca5c963d732() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ NEW."integration_id" := NEW."service_id";
+ RETURN NEW;
+END
+$$;
+
CREATE FUNCTION gitlab_schema_prevent_write() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -19981,7 +20015,6 @@ CREATE TABLE projects (
mirror_user_id integer,
shared_runners_enabled boolean DEFAULT true NOT NULL,
runners_token character varying,
- build_coverage_regex character varying,
build_allow_git_fetch boolean DEFAULT true NOT NULL,
build_timeout integer DEFAULT 3600 NOT NULL,
mirror_trigger_builds boolean DEFAULT false NOT NULL,
@@ -22751,7 +22784,8 @@ CREATE TABLE web_hooks (
backoff_count smallint DEFAULT 0 NOT NULL,
disabled_until timestamp with time zone,
encrypted_url_variables bytea,
- encrypted_url_variables_iv bytea
+ encrypted_url_variables_iv bytea,
+ integration_id integer
);
CREATE SEQUENCE web_hooks_id_seq
@@ -30495,6 +30529,8 @@ CREATE INDEX index_web_hook_logs_part_on_web_hook_id ON ONLY web_hook_logs USING
CREATE INDEX index_web_hooks_on_group_id ON web_hooks USING btree (group_id) WHERE ((type)::text = 'GroupHook'::text);
+CREATE INDEX index_web_hooks_on_integration_id ON web_hooks USING btree (integration_id);
+
CREATE INDEX index_web_hooks_on_project_id ON web_hooks USING btree (project_id);
CREATE INDEX index_web_hooks_on_project_id_recent_failures ON web_hooks USING btree (project_id, recent_failures);
@@ -31959,6 +31995,12 @@ CREATE TRIGGER nullify_merge_request_metrics_build_data_on_update BEFORE UPDATE
CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
+CREATE TRIGGER trigger_a645cee67576 BEFORE UPDATE OF integration_id ON web_hooks FOR EACH ROW EXECUTE FUNCTION function_for_trigger_a645cee67576();
+
+CREATE TRIGGER trigger_a87bcfdf0f0b BEFORE INSERT ON web_hooks FOR EACH ROW EXECUTE FUNCTION function_for_trigger_a87bcfdf0f0b();
+
+CREATE TRIGGER trigger_aca5c963d732 BEFORE UPDATE OF service_id ON web_hooks FOR EACH ROW EXECUTE FUNCTION function_for_trigger_aca5c963d732();
+
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON integrations FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
@@ -32794,6 +32836,9 @@ ALTER TABLE ONLY project_group_links
ALTER TABLE ONLY project_topics
ADD CONSTRAINT fk_db13576296 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY web_hooks
+ ADD CONSTRAINT fk_db1ea5699b FOREIGN KEY (integration_id) REFERENCES integrations(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY security_scans
ADD CONSTRAINT fk_dbc89265b9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index f484f4cbc02..a3f6aec18d2 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -802,8 +802,8 @@ If you have any questions on configuring the SAML app, please contact your provi
### Okta setup notes
-1. In the Okta administrator section, make sure to select Classic UI view in the top left corner. From there, choose to **Add an App**.
-1. When the app screen comes up you see another button to **Create an App** and
+1. In the Okta administrator section choose **Applications**.
+1. When the app screen comes up you see another button to **Create App Integration** and
choose SAML 2.0 on the next screen.
1. Optionally, you can add a logo
(you can choose it from <https://about.gitlab.com/press/>). You must
@@ -940,7 +940,7 @@ Make sure this information is provided.
Another issue that can result in this error is when the correct information is being sent by
the IdP, but the attributes don't match the names in the OmniAuth `info` hash. In this case,
-you must set `attribute_statements` in the SAML configuration to
+you must set `attribute_statements` in the SAML configuration to
[map the attribute names in your SAML Response to the corresponding OmniAuth `info` hash names](#attribute_statements).
### Key validation error, Digest mismatch or Fingerprint mismatch
diff --git a/glfm_specification/example_snapshots/examples_index.yml b/glfm_specification/example_snapshots/examples_index.yml
index 4c112338352..bd295baaad0 100644
--- a/glfm_specification/example_snapshots/examples_index.yml
+++ b/glfm_specification/example_snapshots/examples_index.yml
@@ -2042,3 +2042,15 @@
07_03_00__gitlab_specific_markdown__front_matter__005:
spec_txt_example_position: 683
source_specification: gitlab
+07_04_00__gitlab_specific_markdown__audio__001:
+ spec_txt_example_position: 684
+ source_specification: gitlab
+07_04_00__gitlab_specific_markdown__audio__002:
+ spec_txt_example_position: 685
+ source_specification: gitlab
+07_05_00__gitlab_specific_markdown__video__001:
+ spec_txt_example_position: 686
+ source_specification: gitlab
+07_05_00__gitlab_specific_markdown__video__002:
+ spec_txt_example_position: 687
+ source_specification: gitlab
diff --git a/glfm_specification/example_snapshots/html.yml b/glfm_specification/example_snapshots/html.yml
index 9aeb54ef4de..196cd4f748d 100644
--- a/glfm_specification/example_snapshots/html.yml
+++ b/glfm_specification/example_snapshots/html.yml
@@ -7820,3 +7820,33 @@
wysiwyg: |-
<hr>
<h2>title: YAML front matter</h2>
+07_04_00__gitlab_specific_markdown__audio__001:
+ canonical: |
+ <p><audio src="audio.oga" title="audio title"></audio></p>
+ static: |-
+ <p data-sourcepos="1:1-1:33" dir="auto"><span class="media-container audio-container"><audio src="audio.oga" controls="true" data-setup="{}" data-title="audio title"></audio><a href="audio.oga" target="_blank" rel="noopener noreferrer" title="Download 'audio title'">audio title</a></span></p>
+ wysiwyg: |-
+ <p><span class="media-container audio-container"><audio src="audio.oga" controls="true" data-setup="{}" data-title="audio"></audio><a href="audio.oga">audio</a></span></p>
+07_04_00__gitlab_specific_markdown__audio__002:
+ canonical: |
+ <p><audio src="audio.oga" title="audio title"></audio></p>
+ static: |-
+ <p data-sourcepos="3:1-3:15" dir="auto"><span class="media-container audio-container"><audio src="audio.oga" controls="true" data-setup="{}" data-title="audio title"></audio><a href="audio.oga" target="_blank" rel="noopener noreferrer" title="Download 'audio title'">audio title</a></span></p>
+ wysiwyg: |-
+ <pre>[audio]: audio.oga "audio title"</pre>
+ <p><span class="media-container audio-container"><audio src="audio.oga" controls="true" data-setup="{}" data-title="audio"></audio><a href="audio.oga">audio</a></span></p>
+07_05_00__gitlab_specific_markdown__video__001:
+ canonical: |
+ <p><video src="video.m4v" title="video title"></video></p>
+ static: |-
+ <p data-sourcepos="1:1-1:33" dir="auto"><span class="media-container video-container"><video src="video.m4v" controls="true" data-setup="{}" data-title="video title" width="400" preload="metadata"></video><a href="video.m4v" target="_blank" rel="noopener noreferrer" title="Download 'video title'">video title</a></span></p>
+ wysiwyg: |-
+ <p><span class="media-container video-container"><video src="video.m4v" controls="true" data-setup="{}" data-title="video"></video><a href="video.m4v">video</a></span></p>
+07_05_00__gitlab_specific_markdown__video__002:
+ canonical: |
+ <p><video src="video.mov" title="video title"></video></p>
+ static: |-
+ <p data-sourcepos="3:1-3:15" dir="auto"><span class="media-container video-container"><video src="video.mov" controls="true" data-setup="{}" data-title="video title" width="400" preload="metadata"></video><a href="video.mov" target="_blank" rel="noopener noreferrer" title="Download 'video title'">video title</a></span></p>
+ wysiwyg: |-
+ <pre>[video]: video.mov "video title"</pre>
+ <p><span class="media-container video-container"><video src="video.mov" controls="true" data-setup="{}" data-title="video"></video><a href="video.mov">video</a></span></p>
diff --git a/glfm_specification/example_snapshots/markdown.yml b/glfm_specification/example_snapshots/markdown.yml
index 0d2387434d1..c2b2caacb5b 100644
--- a/glfm_specification/example_snapshots/markdown.yml
+++ b/glfm_specification/example_snapshots/markdown.yml
@@ -2227,3 +2227,15 @@
---
title: YAML front matter
---
+07_04_00__gitlab_specific_markdown__audio__001: |
+ ![audio](audio.oga "audio title")
+07_04_00__gitlab_specific_markdown__audio__002: |
+ [audio]: audio.oga "audio title"
+
+ ![audio][audio]
+07_05_00__gitlab_specific_markdown__video__001: |
+ ![video](video.m4v "video title")
+07_05_00__gitlab_specific_markdown__video__002: |
+ [video]: video.mov "video title"
+
+ ![video][video]
diff --git a/glfm_specification/example_snapshots/prosemirror_json.yml b/glfm_specification/example_snapshots/prosemirror_json.yml
index 35f8314b1bc..02330019a8d 100644
--- a/glfm_specification/example_snapshots/prosemirror_json.yml
+++ b/glfm_specification/example_snapshots/prosemirror_json.yml
@@ -20782,3 +20782,111 @@
}
]
}
+07_04_00__gitlab_specific_markdown__audio__001: |-
+ {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "audio",
+ "attrs": {
+ "uploading": false,
+ "src": "audio.oga",
+ "canonicalSrc": "audio.oga",
+ "alt": "audio"
+ }
+ }
+ ]
+ }
+ ]
+ }
+07_04_00__gitlab_specific_markdown__audio__002: |-
+ {
+ "type": "doc",
+ "content": [
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "audio",
+ "url": "audio.oga",
+ "title": "audio title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[audio]: audio.oga \"audio title\""
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "audio",
+ "attrs": {
+ "uploading": false,
+ "src": "audio.oga",
+ "canonicalSrc": "audio",
+ "alt": "audio"
+ }
+ }
+ ]
+ }
+ ]
+ }
+07_05_00__gitlab_specific_markdown__video__001: |-
+ {
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "video",
+ "attrs": {
+ "uploading": false,
+ "src": "video.m4v",
+ "canonicalSrc": "video.m4v",
+ "alt": "video"
+ }
+ }
+ ]
+ }
+ ]
+ }
+07_05_00__gitlab_specific_markdown__video__002: |-
+ {
+ "type": "doc",
+ "content": [
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "video",
+ "url": "video.mov",
+ "title": "video title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[video]: video.mov \"video title\""
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "video",
+ "attrs": {
+ "uploading": false,
+ "src": "video.mov",
+ "canonicalSrc": "video",
+ "alt": "video"
+ }
+ }
+ ]
+ }
+ ]
+ }
diff --git a/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt b/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt
index 3fa59a9f7b1..fd540a905bd 100644
--- a/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt
+++ b/glfm_specification/input/gitlab_flavored_markdown/glfm_canonical_examples.txt
@@ -199,3 +199,54 @@ title: YAML front matter
<hr>
<h2>title: YAML front matter</h2>
````````````````````````````````
+
+## Audio
+
+See
+[audio](https://docs.gitlab.com/ee/user/markdown.html#audio) in the GitLab Flavored Markdown documentation.
+
+GLFM renders image elements as an audio player as long as the resource’s file extension is
+one of the following supported audio extensions `.mp3`, `.oga`, `.ogg`, `.spx`, and `.wav`.
+Audio ignore the alternative text part of an image declaration.
+
+```````````````````````````````` example gitlab audio
+![audio](audio.oga "audio title")
+.
+<p><audio src="audio.oga" title="audio title"></audio></p>
+````````````````````````````````
+
+Reference definitions work audio as well:
+
+```````````````````````````````` example gitlab audio
+[audio]: audio.oga "audio title"
+
+![audio][audio]
+.
+<p><audio src="audio.oga" title="audio title"></audio></p>
+````````````````````````````````
+
+## Video
+
+See
+[videos](https://docs.gitlab.com/ee/user/markdown.html#videos) in the GitLab Flavored Markdown documentation.
+
+GLFM renders image elements as a video player as long as the resource’s file extension is
+one of the following supported video extensions `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
+Videos ignore the alternative text part of an image declaration.
+
+
+```````````````````````````````` example gitlab video
+![video](video.m4v "video title")
+.
+<p><video src="video.m4v" title="video title"></video></p>
+````````````````````````````````
+
+Reference definitions work video as well:
+
+```````````````````````````````` example gitlab video
+[video]: video.mov "video title"
+
+![video][video]
+.
+<p><video src="video.mov" title="video title"></video></p>
+````````````````````````````````
diff --git a/glfm_specification/output/spec.txt b/glfm_specification/output/spec.txt
index 49018b02220..2939e0d2fbd 100644
--- a/glfm_specification/output/spec.txt
+++ b/glfm_specification/output/spec.txt
@@ -9802,6 +9802,57 @@ title: YAML front matter
<h2>title: YAML front matter</h2>
````````````````````````````````
+## Audio
+
+See
+[audio](https://docs.gitlab.com/ee/user/markdown.html#audio) in the GitLab Flavored Markdown documentation.
+
+GLFM renders image elements as an audio player as long as the resource’s file extension is
+one of the following supported audio extensions `.mp3`, `.oga`, `.ogg`, `.spx`, and `.wav`.
+Audio ignore the alternative text part of an image declaration.
+
+```````````````````````````````` example gitlab audio
+![audio](audio.oga "audio title")
+.
+<p><audio src="audio.oga" title="audio title"></audio></p>
+````````````````````````````````
+
+Reference definitions work audio as well:
+
+```````````````````````````````` example gitlab audio
+[audio]: audio.oga "audio title"
+
+![audio][audio]
+.
+<p><audio src="audio.oga" title="audio title"></audio></p>
+````````````````````````````````
+
+## Video
+
+See
+[videos](https://docs.gitlab.com/ee/user/markdown.html#videos) in the GitLab Flavored Markdown documentation.
+
+GLFM renders image elements as a video player as long as the resource’s file extension is
+one of the following supported video extensions `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
+Videos ignore the alternative text part of an image declaration.
+
+
+```````````````````````````````` example gitlab video
+![video](video.m4v "video title")
+.
+<p><video src="video.m4v" title="video title"></video></p>
+````````````````````````````````
+
+Reference definitions work video as well:
+
+```````````````````````````````` example gitlab video
+[video]: video.mov "video title"
+
+![video][video]
+.
+<p><video src="video.mov" title="video title"></video></p>
+````````````````````````````````
+
<!-- END TESTS -->
# Appendix: A parsing strategy
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
index ec9b09a3419..0048999a8d2 100644
--- a/lib/api/ci/runners.rb
+++ b/lib/api/ci/runners.rb
@@ -129,6 +129,14 @@ module API
authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
+ jobs = jobs.preload( # rubocop: disable CodeReuse/ActiveRecord
+ [
+ :user,
+ { pipeline: { project: [:route, { namespace: :route }] } },
+ { project: [:route, { namespace: :route }] }
+ ]
+ )
+ jobs.each(&:commit) # batch loads all commits
present paginate(jobs), with: Entities::Ci::JobBasicWithProject
end
diff --git a/lib/gitlab/cache/import/caching.rb b/lib/gitlab/cache/import/caching.rb
index 4dbce0b05e1..4e7a7f326a5 100644
--- a/lib/gitlab/cache/import/caching.rb
+++ b/lib/gitlab/cache/import/caching.rb
@@ -65,6 +65,8 @@ module Gitlab
# value - The value to set.
# timeout - The time after which the cache key should expire.
def self.write(raw_key, value, timeout: TIMEOUT)
+ validate_redis_value!(value)
+
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
@@ -99,6 +101,8 @@ module Gitlab
# timeout - The time after which the cache key should expire.
# @return - the incremented value
def self.increment_by(raw_key, value, timeout: TIMEOUT)
+ validate_redis_value!(value)
+
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
@@ -113,6 +117,8 @@ module Gitlab
# value - The value to add to the set.
# timeout - The new timeout of the key.
def self.set_add(raw_key, value, timeout: TIMEOUT)
+ validate_redis_value!(value)
+
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
@@ -128,6 +134,8 @@ module Gitlab
# raw_key - The key of the set to check.
# value - The value to check for.
def self.set_includes?(raw_key, value)
+ validate_redis_value!(value)
+
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
@@ -157,6 +165,8 @@ module Gitlab
mapping.each do |raw_key, value|
key = cache_key_for("#{key_prefix}#{raw_key}")
+ validate_redis_value!(value)
+
multi.set(key, value, ex: timeout)
end
end
@@ -186,6 +196,8 @@ module Gitlab
#
# Returns true when the key was overwritten, false otherwise.
def self.write_if_greater(raw_key, value, timeout: TIMEOUT)
+ validate_redis_value!(value)
+
key = cache_key_for(raw_key)
val = Redis::Cache.with do |redis|
redis
@@ -202,6 +214,8 @@ module Gitlab
# value - The field value to add to the hash.
# timeout - The new timeout of the key.
def self.hash_add(raw_key, field, value, timeout: TIMEOUT)
+ validate_redis_value!(value)
+
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
@@ -226,6 +240,13 @@ module Gitlab
def self.cache_key_for(raw_key)
"#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}"
end
+
+ def self.validate_redis_value!(value)
+ value_as_string = value.to_s
+ return if value_as_string.is_a?(String)
+
+ raise "Value '#{value_as_string}' of type '#{value_as_string.class}' for '#{value.inspect}' is not a String"
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 86bc5403d10..4b825afa2ec 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -27688,6 +27688,9 @@ msgstr ""
msgid "PQL|Thank you for reaching out! Our sales team will get back to you soon."
msgstr ""
+msgid "Package & registry settings"
+msgstr ""
+
msgid "Package Registry"
msgstr ""
@@ -36596,6 +36599,9 @@ msgstr ""
msgid "SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Policy%{link_end}"
msgstr ""
+msgid "SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}."
+msgstr ""
+
msgid "SignUp|First name is too long (maximum is %{max_length} characters)."
msgstr ""
diff --git a/package.json b/package.json
index ed78775ed3c..9c724e8a37f 100644
--- a/package.json
+++ b/package.json
@@ -189,7 +189,7 @@
"vuex": "^3.6.2",
"web-vitals": "^0.2.4",
"webpack": "^4.46.0",
- "webpack-bundle-analyzer": "^4.5.0",
+ "webpack-bundle-analyzer": "^4.6.1",
"webpack-cli": "^4.10.0",
"webpack-stats-plugin": "^0.3.1",
"worker-loader": "^2.0.0",
diff --git a/spec/features/groups/settings/packages_and_registries_spec.rb b/spec/features/groups/settings/packages_and_registries_spec.rb
index 98dc534f54e..14272bd204a 100644
--- a/spec/features/groups/settings/packages_and_registries_spec.rb
+++ b/spec/features/groups/settings/packages_and_registries_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe 'Group Packages & Registries settings' do
it 'has a page title set' do
visit_settings_page
- expect(page).to have_title _('Packages & Registries')
+ expect(page).to have_title _('Package & registry settings')
end
it 'sidebar menu is open' do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index f2381e41de8..fa6a917a059 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -297,9 +297,8 @@ RSpec.describe 'Signup' do
enforce_terms
end
- it 'renders text that the user confirms terms by clicking register' do
+ it 'renders text that the user confirms terms by signing in' do
visit new_user_registration_path
-
expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/)
fill_in_signup_form
@@ -391,7 +390,7 @@ RSpec.describe 'Signup' do
enforce_terms
end
- it 'renders text that the user confirms terms by clicking register' do
+ it 'renders text that the user confirms terms by signing in' do
visit new_user_registration_path
expect(page).to have_content(/By clicking Register, I agree that I have read and accepted the Terms of Use and Privacy Policy/)
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index 7ae0a7c13c1..09b9e7020af 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -1,3 +1,4 @@
+import Audio from '~/content_editor/extensions/audio';
import Bold from '~/content_editor/extensions/bold';
import Blockquote from '~/content_editor/extensions/blockquote';
import BulletList from '~/content_editor/extensions/bullet_list';
@@ -25,13 +26,16 @@ import TableRow from '~/content_editor/extensions/table_row';
import TableCell from '~/content_editor/extensions/table_cell';
import TaskList from '~/content_editor/extensions/task_list';
import TaskItem from '~/content_editor/extensions/task_item';
+import Video from '~/content_editor/extensions/video';
import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
+import { SAFE_VIDEO_EXT, SAFE_AUDIO_EXT } from '~/content_editor/constants';
import { createTestEditor, createDocBuilder } from './test_utils';
const tiptapEditor = createTestEditor({
extensions: [
+ Audio,
Blockquote,
Bold,
BulletList,
@@ -57,6 +61,7 @@ const tiptapEditor = createTestEditor({
TableCell,
TaskList,
TaskItem,
+ Video,
...HTMLNodes,
],
});
@@ -65,6 +70,7 @@ const {
builders: {
doc,
paragraph,
+ audio,
bold,
blockquote,
bulletList,
@@ -91,10 +97,12 @@ const {
tableCell,
taskItem,
taskList,
+ video,
},
} = createDocBuilder({
tiptapEditor,
names: {
+ audio: { nodeType: Audio.name },
blockquote: { nodeType: Blockquote.name },
bold: { markType: Bold.name },
bulletList: { nodeType: BulletList.name },
@@ -120,6 +128,7 @@ const {
tableRow: { nodeType: TableRow.name },
taskItem: { nodeType: TaskItem.name },
taskList: { nodeType: TaskList.name },
+ video: { nodeType: Video.name },
...HTMLNodes.reduce(
(builders, htmlNode) => ({
...builders,
@@ -1233,6 +1242,44 @@ title: 'layout'
),
),
},
+ ...SAFE_AUDIO_EXT.map((extension) => {
+ const src = `http://test.host/video.${extension}`;
+ const markdown = `![audio](${src})`;
+
+ return {
+ markdown,
+ expectedDoc: doc(
+ paragraph(
+ source(markdown),
+ audio({
+ ...source(markdown),
+ canonicalSrc: src,
+ src,
+ alt: 'audio',
+ }),
+ ),
+ ),
+ };
+ }),
+ ...SAFE_VIDEO_EXT.map((extension) => {
+ const src = `http://test.host/video.${extension}`;
+ const markdown = `![video](${src})`;
+
+ return {
+ markdown,
+ expectedDoc: doc(
+ paragraph(
+ source(markdown),
+ video({
+ ...source(markdown),
+ canonicalSrc: src,
+ src,
+ alt: 'video',
+ }),
+ ),
+ ),
+ };
+ }),
];
const runOnly = examples.find((example) => example.only === true);
diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
index 4a57c7b1942..fc88c0ea445 100644
--- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
+++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
@@ -1,6 +1,7 @@
import { DOMSerializer } from 'prosemirror-model';
// TODO: DRY up duplication with spec/frontend/content_editor/services/markdown_serializer_spec.js
// See https://gitlab.com/groups/gitlab-org/-/epics/7719#plan
+import Audio from '~/content_editor/extensions/audio';
import Blockquote from '~/content_editor/extensions/blockquote';
import Bold from '~/content_editor/extensions/bold';
import BulletList from '~/content_editor/extensions/bullet_list';
@@ -35,11 +36,13 @@ import TableHeader from '~/content_editor/extensions/table_header';
import TableRow from '~/content_editor/extensions/table_row';
import TaskItem from '~/content_editor/extensions/task_item';
import TaskList from '~/content_editor/extensions/task_list';
+import Video from '~/content_editor/extensions/video';
import createMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestEditor } from 'jest/content_editor/test_utils';
const tiptapEditor = createTestEditor({
extensions: [
+ Audio,
Blockquote,
Bold,
BulletList,
@@ -74,6 +77,7 @@ const tiptapEditor = createTestEditor({
TableRow,
TaskItem,
TaskList,
+ Video,
],
});
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 0e5281be9bf..56394c85e8b 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -1,3 +1,4 @@
+import Audio from '~/content_editor/extensions/audio';
import Blockquote from '~/content_editor/extensions/blockquote';
import Bold from '~/content_editor/extensions/bold';
import BulletList from '~/content_editor/extensions/bullet_list';
@@ -33,6 +34,7 @@ import TableHeader from '~/content_editor/extensions/table_header';
import TableRow from '~/content_editor/extensions/table_row';
import TaskItem from '~/content_editor/extensions/task_item';
import TaskList from '~/content_editor/extensions/task_list';
+import Video from '~/content_editor/extensions/video';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestEditor, createDocBuilder } from '../test_utils';
@@ -41,6 +43,7 @@ jest.mock('~/emoji');
const tiptapEditor = createTestEditor({
extensions: [
+ Audio,
Blockquote,
Bold,
BulletList,
@@ -73,6 +76,7 @@ const tiptapEditor = createTestEditor({
TableRow,
TaskItem,
TaskList,
+ Video,
...HTMLMarks,
...HTMLNodes,
],
@@ -80,6 +84,7 @@ const tiptapEditor = createTestEditor({
const {
builders: {
+ audio,
doc,
blockquote,
bold,
@@ -114,6 +119,7 @@ const {
tableRow,
taskItem,
taskList,
+ video,
},
} = createDocBuilder({
tiptapEditor,
@@ -1230,6 +1236,21 @@ paragraph
);
});
+ it('serializes audio and video elements', () => {
+ expect(
+ serialize(
+ paragraph(
+ audio({ alt: 'audio', canonicalSrc: 'audio.mp3' }),
+ ' and ',
+ video({ alt: 'video', canonicalSrc: 'video.mov' }),
+ ),
+ ),
+ ).toBe(
+ `
+![audio](audio.mp3) and ![video](video.mov)`.trimLeft(),
+ );
+ });
+
const defaultEditAction = (initialContent) => {
tiptapEditor.chain().setContent(initialContent.toJSON()).insertContent(' modified').run();
};
diff --git a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
index 322cfa3ba1f..98bdfc3fcbc 100644
--- a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
+++ b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
@@ -15,23 +15,27 @@ describe('Jobs filtered search', () => {
const findStatusToken = () => getSearchToken('status');
- const createComponent = () => {
- wrapper = shallowMount(JobsFilteredSearch);
+ const createComponent = (props) => {
+ wrapper = shallowMount(JobsFilteredSearch, {
+ propsData: {
+ ...props,
+ },
+ });
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
it('displays filtered search', () => {
+ createComponent();
+
expect(findFilteredSearch().exists()).toBe(true);
});
it('displays status token', () => {
+ createComponent();
+
expect(findStatusToken()).toMatchObject({
type: 'status',
icon: 'status',
@@ -42,8 +46,26 @@ describe('Jobs filtered search', () => {
});
it('emits filter token to parent component', () => {
+ createComponent();
+
findFilteredSearch().vm.$emit('submit', mockFailedSearchToken);
expect(wrapper.emitted('filterJobsBySearch')).toEqual([[mockFailedSearchToken]]);
});
+
+ it('filtered search value is empty array when no query string is passed', () => {
+ createComponent();
+
+ expect(findFilteredSearch().props('value')).toEqual([]);
+ });
+
+ it('filtered search returns correct data shape when passed query string', () => {
+ const value = 'SUCCESS';
+
+ createComponent({ queryString: { statuses: value } });
+
+ expect(findFilteredSearch().props('value')).toEqual([
+ { type: 'status', value: { data: value, operator: '=' } },
+ ]);
+ });
});
diff --git a/spec/frontend/jobs/components/filtered_search/utils_spec.js b/spec/frontend/jobs/components/filtered_search/utils_spec.js
new file mode 100644
index 00000000000..8e9cd357a19
--- /dev/null
+++ b/spec/frontend/jobs/components/filtered_search/utils_spec.js
@@ -0,0 +1,18 @@
+import { validateQueryString } from '~/jobs/components/filtered_search/utils';
+
+describe('Filtered search utils', () => {
+ describe('validateQueryString', () => {
+ it.each`
+ queryStringObject | expected
+ ${{ statuses: 'SUCCESS' }} | ${{ statuses: 'SUCCESS' }}
+ ${{ wrong: 'SUCCESS' }} | ${null}
+ ${{ statuses: 'wrong' }} | ${null}
+ ${{ wrong: 'wrong' }} | ${null}
+ `(
+ 'when provided $queryStringObject, the expected result is $expected',
+ ({ queryStringObject, expected }) => {
+ expect(validateQueryString(queryStringObject)).toEqual(expected);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/jobs/components/table/job_table_app_spec.js b/spec/frontend/jobs/components/table/job_table_app_spec.js
index 374768c3ee4..8c724a8030b 100644
--- a/spec/frontend/jobs/components/table/job_table_app_spec.js
+++ b/spec/frontend/jobs/components/table/job_table_app_spec.js
@@ -11,12 +11,14 @@ import VueApollo from 'vue-apollo';
import { s__ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
import createFlash from '~/flash';
import getJobsQuery from '~/jobs/components/table/graphql/queries/get_jobs.query.graphql';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import JobsTableApp from '~/jobs/components/table/jobs_table_app.vue';
import JobsTableTabs from '~/jobs/components/table/jobs_table_tabs.vue';
import JobsFilteredSearch from '~/jobs/components/filtered_search/jobs_filtered_search.vue';
+import * as urlUtils from '~/lib/utils/url_utility';
import {
mockJobsResponsePaginated,
mockJobsResponseEmpty,
@@ -230,5 +232,17 @@ describe('Job table app', () => {
expect(createFlash).toHaveBeenCalledWith(expectedWarning);
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
});
+
+ it('updates URL query string when filtering jobs by status', async () => {
+ createComponent();
+
+ jest.spyOn(urlUtils, 'updateHistory');
+
+ await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
+
+ expect(urlUtils.updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/?statuses=FAILED`,
+ });
+ });
});
});
diff --git a/spec/lib/gitlab/cache/import/caching_spec.rb b/spec/lib/gitlab/cache/import/caching_spec.rb
index 946a7c604a1..6f9879da281 100644
--- a/spec/lib/gitlab/cache/import/caching_spec.rb
+++ b/spec/lib/gitlab/cache/import/caching_spec.rb
@@ -3,6 +3,17 @@
require 'spec_helper'
RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
+ shared_examples 'validated redis value' do
+ let(:value) { double('value', to_s: Object.new) }
+
+ it 'raise error if value.to_s does not return a String' do
+ value_as_string = value.to_s
+ message = /Value '#{value_as_string}' of type '#{value_as_string.class}' for '#{value.inspect}' is not a String/
+
+ expect { subject }.to raise_error(message)
+ end
+ end
+
describe '.read' do
it 'reads a value from the cache' do
described_class.write('foo', 'bar')
@@ -56,6 +67,16 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
expect(described_class.write('foo', 10)).to eq(10)
expect(described_class.read('foo')).to eq('10')
end
+
+ it_behaves_like 'validated redis value' do
+ subject { described_class.write('foo', value) }
+ end
+ end
+
+ describe '.increment_by' do
+ it_behaves_like 'validated redis value' do
+ subject { described_class.increment_by('foo', value) }
+ end
end
describe '.increment' do
@@ -78,6 +99,10 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
expect(values).to eq(['10'])
end
+
+ it_behaves_like 'validated redis value' do
+ subject { described_class.set_add('foo', value) }
+ end
end
describe '.set_includes?' do
@@ -96,6 +121,10 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
expect(described_class.set_includes?('foo', 10)).to eq(true)
end
+
+ it_behaves_like 'validated redis value' do
+ subject { described_class.set_includes?('foo', value) }
+ end
end
describe '.values_from_set' do
@@ -120,6 +149,10 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
expect(values).to eq({ '1' => '1', '2' => '2' })
end
+
+ it_behaves_like 'validated redis value' do
+ subject { described_class.hash_add('foo', 1, value) }
+ end
end
describe '.values_from_hash' do
@@ -160,6 +193,12 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
expect(found).to eq(value.to_s)
end
end
+
+ it_behaves_like 'validated redis value' do
+ let(:mapping) { { 'foo' => value, 'bar' => value } }
+
+ subject { described_class.write_multiple(mapping) }
+ end
end
describe '.expire' do
@@ -175,4 +214,10 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
expect(found_ttl).to be <= timeout
end
end
+
+ describe '.write_if_greater' do
+ it_behaves_like 'validated redis value' do
+ subject { described_class.write_if_greater('foo', value) }
+ end
+ end
end
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 31b85a0b1d6..a24bf22cd45 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -889,6 +889,38 @@ RSpec.describe API::Ci::Runners do
end
end
+ it 'avoids N+1 DB queries' do
+ get api("/runners/#{shared_runner.id}/jobs", admin)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/runners/#{shared_runner.id}/jobs", admin)
+ end
+
+ create(:ci_build, :failed, runner: shared_runner, project: create(:project))
+
+ expect do
+ get api("/runners/#{shared_runner.id}/jobs", admin)
+ end.not_to exceed_query_limit(control.count)
+ end
+
+ it 'batches loading of commits' do
+ shared_runner = create(:ci_runner, :instance, description: 'Shared runner')
+
+ project_with_repo = create(:project, :repository)
+
+ pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'ddd0f15ae83993f5cb66a927a28673882e99100b')
+ create(:ci_build, :running, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
+
+ pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'c1c67abbaf91f624347bb3ae96eabe3a1b742478')
+ create(:ci_build, :failed, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
+
+ expect_next_instance_of(Repository) do |repo|
+ expect(repo).to receive(:commits_by).once.and_call_original
+ end
+
+ get api("/runners/#{shared_runner.id}/jobs", admin)
+ end
+
context "when runner doesn't exist" do
it 'returns 404' do
get api('/runners/0/jobs', admin)
diff --git a/yarn.lock b/yarn.lock
index 868ae38c64f..c5c1a769b0b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12140,10 +12140,10 @@ webidl-conversions@^6.1.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
-webpack-bundle-analyzer@^4.5.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5"
- integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==
+webpack-bundle-analyzer@^4.6.1:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.1.tgz#bee2ee05f4ba4ed430e4831a319126bb4ed9f5a6"
+ integrity sha512-oKz9Oz9j3rUciLNfpGFjOb49/jEpXNmWdVH8Ls//zNcnLlQdTGXQQMsBbb/gR7Zl8WNLxVCq+0Hqbx3zv6twBw==
dependencies:
acorn "^8.0.4"
acorn-walk "^8.0.0"