Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c4a9ca5ffc
commit
74d4d931ac
20
CHANGELOG.md
20
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)
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
export const jobStatusValues = [
|
||||
'CANCELED',
|
||||
'CREATED',
|
||||
'FAILED',
|
||||
'MANUAL',
|
||||
'SUCCESS',
|
||||
'PENDING',
|
||||
'PREPARING',
|
||||
'RUNNING',
|
||||
'SCHEDULED',
|
||||
'SKIPPED',
|
||||
'WAITING_FOR_RESOURCE',
|
||||
];
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
60a7782e9eaed833362e314fe3ae35f881ee051d9b529c59638833ce92d2db2d
|
|
@ -0,0 +1 @@
|
|||
696550615046e26d4012d8b5a5fb741d85c23d4d0d08a4a781da0123c0543de1
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
````````````````````````````````
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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: '=' } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue