Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-23 00:09:41 +00:00
parent c4a9ca5ffc
commit 74d4d931ac
41 changed files with 764 additions and 37 deletions

View File

@ -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)

View File

@ -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'];

View File

@ -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: {

View File

@ -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 {

View File

@ -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',

View File

@ -0,0 +1,13 @@
export const jobStatusValues = [
'CANCELED',
'CREATED',
'FAILED',
'MANUAL',
'SUCCESS',
'PENDING',
'PREPARING',
'RUNNING',
'SCHEDULED',
'SKIPPED',
'WAITING_FOR_RESOURCE',
];

View File

@ -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>

View File

@ -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;
};

View File

@ -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"
/>

View File

@ -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'

View File

@ -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,

View File

@ -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 }

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
60a7782e9eaed833362e314fe3ae35f881ee051d9b529c59638833ce92d2db2d

View File

@ -0,0 +1 @@
696550615046e26d4012d8b5a5fb741d85c23d4d0d08a4a781da0123c0543de1

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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]

View File

@ -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"
}
}
]
}
]
}

View File

@ -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 resources 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 resources 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>
````````````````````````````````

View File

@ -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 resources 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 resources 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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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/)

View File

@ -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);

View File

@ -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,
],
});

View File

@ -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();
};

View File

@ -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: '=' } },
]);
});
});

View File

@ -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);
},
);
});
});

View File

@ -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`,
});
});
});
});

View File

@ -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

View File

@ -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)

View File

@ -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"