diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-21 12:09:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-21 12:09:11 +0300 |
commit | 49abdb108a4d3c3f2ef9b27c7c4dcde43da1016a (patch) | |
tree | 31dee66af9f14c3bd320c349810d877d96fd66cf /app | |
parent | 760dc7721406a82bbea06f3512a4e270d0bb3f0a (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_empty_state.vue | 8 | ||||
-rw-r--r-- | app/assets/javascripts/repository/components/blob_viewers/geo_json/constants.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.stories.js | 89 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.vue | 120 | ||||
-rw-r--r-- | app/controllers/import/base_controller.rb | 2 | ||||
-rw-r--r-- | app/helpers/calendar_helper.rb | 2 | ||||
-rw-r--r-- | app/helpers/feed_token_helper.rb | 12 | ||||
-rw-r--r-- | app/helpers/rss_helper.rb | 2 | ||||
-rw-r--r-- | app/models/abuse/trust_score.rb | 10 | ||||
-rw-r--r-- | app/models/abuse/user_trust_score.rb | 39 | ||||
-rw-r--r-- | app/models/note.rb | 8 | ||||
-rw-r--r-- | app/models/user.rb | 27 | ||||
-rw-r--r-- | app/services/packages/npm/create_metadata_cache_service.rb | 2 |
13 files changed, 285 insertions, 38 deletions
diff --git a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_empty_state.vue b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_empty_state.vue index 39ac55bb9c5..ab249dc61d1 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_empty_state.vue +++ b/app/assets/javascripts/ci/pipeline_schedules/components/pipeline_schedules_empty_state.vue @@ -1,5 +1,5 @@ <script> -import scheduleSvg from '@gitlab/svgs/dist/illustrations/schedule-md.svg?raw'; +import SCHEDULE_MD_SVG_URL from '@gitlab/svgs/dist/illustrations/schedule-md.svg?url'; import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; @@ -20,15 +20,13 @@ export default { ], createNew: s__('PipelineSchedules|Create a new pipeline schedule'), }, + SCHEDULE_MD_SVG_URL, components: { GlEmptyState, GlLink, GlSprintf, }, computed: { - scheduleSvgPath() { - return `data:image/svg+xml;utf8,${encodeURIComponent(scheduleSvg)}`; - }, schedulesHelpPath() { return helpPagePath('ci/pipelines/schedules'); }, @@ -37,7 +35,7 @@ export default { </script> <template> <gl-empty-state - :svg-path="scheduleSvgPath" + :svg-path="$options.SCHEDULE_MD_SVG_URL" :primary-button-text="$options.i18n.createNew" primary-button-link="#" > diff --git a/app/assets/javascripts/repository/components/blob_viewers/geo_json/constants.js b/app/assets/javascripts/repository/components/blob_viewers/geo_json/constants.js index fd4d111b4b0..2c95e63720e 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/geo_json/constants.js +++ b/app/assets/javascripts/repository/components/blob_viewers/geo_json/constants.js @@ -7,7 +7,7 @@ export const RENDER_ERROR_MSG = __( 'The map can not be displayed because there was an error loading the GeoJSON file.', ); -export const OPEN_STREET_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; +export const OPEN_STREET_TILE_URL = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; export const ICON_CONFIG = { iconUrl, iconRetinaUrl, shadowUrl }; export const MAP_ATTRIBUTION = __('Map data from'); export const OPEN_STREET_COPYRIGHT_LINK = diff --git a/app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.stories.js b/app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.stories.js new file mode 100644 index 00000000000..0ba6a44d153 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.stories.js @@ -0,0 +1,89 @@ +import Markdown from '~/vue_shared/components/markdown/non_gfm_markdown.vue'; + +export default { + title: 'vue_shared/non_gfm_markdown', + component: Markdown, + parameters: { + docs: { + description: { + component: ` +This component is designed to render the markdown, which is **not** the GitLab Flavored Markdown. + +It renders the code snippets the same way GitLab Flavored Markdown code snippets are rendered +respecting the user's preferred color scheme and featuring a copy-code button. + +This component can be used to render client-side markdown that doesn't have GitLab-specific markdown elements such as issue links. +`, + }, + }, + }, +}; + +const Template = (args, { argTypes }) => ({ + components: { Markdown }, + props: Object.keys(argTypes), + template: '<markdown v-bind="$props" />', +}); + +const textWithCodeblock = ` +#### Here is the text with the code block. + +\`\`\`javascript +function sayHi(name) { + console.log('Hi ' + name || 'Mark'); +} +\`\`\` + +It *can* have **formatting** as well +`; + +export const OneCodeBlock = Template.bind({}); +OneCodeBlock.args = { markdown: textWithCodeblock }; + +const textWithMultipleCodeBlocks = ` +#### Here is the text with the code block. + +\`\`\`javascript +function sayHi(name) { + console.log('Hi ' + name || 'Mark'); +} +\`\`\` + +Note that the copy buttons are appearing independently + +\`\`\`yaml +stages: + - build + - test + - deploy +\`\`\` +`; + +export const MultipleCodeBlocks = Template.bind({}); +MultipleCodeBlocks.args = { markdown: textWithMultipleCodeBlocks }; + +const textUndefinedLanguage = ` +#### Here is the code block with no language provided. + +\`\`\` +function sayHi(name) { + console.log('Hi ' + name || 'Mark'); +} +\`\`\` +`; + +export const UndefinedLanguage = Template.bind({}); +UndefinedLanguage.args = { markdown: textUndefinedLanguage }; + +const textCodeOneLiner = ` +#### Here is the text with the one-liner code block. + +Note that copy button rendering is ok. + +\`\`\`javascript +const foo = 'bar'; +\`\`\` +`; + +export const CodeOneLiner = Template.bind({}); +CodeOneLiner.args = { markdown: textCodeOneLiner }; diff --git a/app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.vue b/app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.vue new file mode 100644 index 00000000000..814e59681d0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/non_gfm_markdown.vue @@ -0,0 +1,120 @@ +<script> +/* +This component is designed to render the markdown, which is **not** the GitLab Flavored Markdown. + +It renders the code snippets the same way GitLab Flavored Markdown code snippets are rendered +respecting the user's preferred color scheme and featuring a copy-code button. + +This component can be used to render client-side markdown that doesn't have GitLab-specific markdown elements such as issue links. +*/ +import { marked } from 'marked'; +import CodeBlockHighlighted from '~/vue_shared/components/code_block_highlighted.vue'; +import SafeHtml from '~/vue_shared/directives/safe_html'; +import { sanitize } from '~/lib/dompurify'; +import { markdownConfig } from '~/lib/utils/text_utility'; +import { __ } from '~/locale'; +import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; + +export default { + components: { + CodeBlockHighlighted, + ModalCopyButton, + }, + directives: { + SafeHtml, + }, + props: { + markdown: { + type: String, + required: true, + }, + }, + data() { + return { + hoverMap: {}, + }; + }, + computed: { + markdownBlocks() { + // we use lexer https://marked.js.org/using_pro#lexer + // to get an array of tokens that marked npm module uses. + // We will use these tokens to override rendering of some of them + // with our vue components + const tokens = marked.lexer(this.markdown); + + // since we only want to differentiate between code and non-code blocks + // we want non-code blocks merged together so that the markdown parser could render + // them according to the markdown rules. + // This way we introduce minimum extra wrapper mark-up + const flattenedTokens = []; + + for (const token of tokens) { + const lastFlattenedToken = flattenedTokens[flattenedTokens.length - 1]; + if (token.type === 'code') { + flattenedTokens.push(token); + } else if (lastFlattenedToken?.type === 'markdown') { + lastFlattenedToken.raw += token.raw; + } else { + flattenedTokens.push({ type: 'markdown', raw: token.raw }); + } + } + + return flattenedTokens; + }, + }, + methods: { + getSafeHtml(markdown) { + return sanitize(marked.parse(markdown), markdownConfig); + }, + setHoverOn(key) { + this.hoverMap = { ...this.hoverMap, [key]: true }; + }, + setHoverOff(key) { + this.hoverMap = { ...this.hoverMap, [key]: false }; + }, + isLastElement(index) { + return index === this.markdownBlocks.length - 1; + }, + }, + safeHtmlConfig: { + ADD_TAGS: ['use', 'gl-emoji', 'copy-code'], + }, + i18n: { + copyCodeTitle: __('Copy code'), + }, + fallbackLanguage: 'text', +}; +</script> +<template> + <div> + <template v-for="(block, index) in markdownBlocks"> + <div + v-if="block.type === 'code'" + :key="`code-${index}`" + :class="{ 'gl-relative': true, 'gl-mb-4': !isLastElement(index) }" + data-testid="code-block-wrapper" + @mouseenter="setHoverOn(`code-${index}`)" + @mouseleave="setHoverOff(`code-${index}`)" + > + <modal-copy-button + v-if="hoverMap[`code-${index}`]" + :title="$options.i18n.copyCodeTitle" + :text="block.text" + class="gl-absolute gl-top-3 gl-right-3 gl-z-index-1 gl-transition-duration-medium" + /> + <code-block-highlighted + class="gl-border gl-rounded-0! gl-p-4 gl-mb-0 gl-overflow-y-auto" + :language="block.lang || $options.fallbackLanguage" + :code="block.text" + /> + </div> + <div + v-else + :key="`text-${index}`" + v-safe-html:[$options.safeHtmlConfig]="getSafeHtml(block.raw)" + :class="{ 'non-gfm-markdown-block': true, 'gl-mb-4': !isLastElement(index) }" + data-testid="non-code-markdown" + ></div> + </template> + </div> +</template> diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index bcb6aed9e38..f3a0ce64839 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -82,7 +82,7 @@ class Import::BaseController < ApplicationController # rubocop: disable CodeReuse/ActiveRecord def find_already_added_projects(import_type) - current_user.created_projects.where(import_type: import_type).with_import_state + current_user.created_projects.inc_routes.where(import_type: import_type).with_import_state end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/helpers/calendar_helper.rb b/app/helpers/calendar_helper.rb index ad4116fc3da..d70a860d468 100644 --- a/app/helpers/calendar_helper.rb +++ b/app/helpers/calendar_helper.rb @@ -3,7 +3,7 @@ module CalendarHelper def calendar_url_options { format: :ics, - feed_token: current_user.try(:feed_token), + feed_token: generate_feed_token(:ics), due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name, sort: 'closest_future_date' } end diff --git a/app/helpers/feed_token_helper.rb b/app/helpers/feed_token_helper.rb new file mode 100644 index 00000000000..751a8df4782 --- /dev/null +++ b/app/helpers/feed_token_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module FeedTokenHelper + def generate_feed_token(type) + feed_token = current_user&.feed_token + return unless feed_token + + final_path = "#{current_request.path}.#{type}" + digest = OpenSSL::HMAC.hexdigest("SHA256", feed_token, final_path) + "#{User::FEED_TOKEN_PREFIX}#{digest}-#{current_user.id}" + end +end diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb index 67c7d244f11..90dd4e8fedb 100644 --- a/app/helpers/rss_helper.rb +++ b/app/helpers/rss_helper.rb @@ -2,6 +2,6 @@ module RssHelper def rss_url_options - { format: :atom, feed_token: current_user.try(:feed_token) } + { format: :atom, feed_token: generate_feed_token(:atom) } end end diff --git a/app/models/abuse/trust_score.rb b/app/models/abuse/trust_score.rb index b7ed504a0ba..599e5c82296 100644 --- a/app/models/abuse/trust_score.rb +++ b/app/models/abuse/trust_score.rb @@ -15,6 +15,9 @@ module Abuse validates :score, presence: true validates :source, presence: true + scope :order_created_at_asc, -> { order(created_at: :asc) } + scope :order_created_at_desc, -> { order(created_at: :desc) } + before_create :assign_correlation_id after_commit :remove_old_scores @@ -25,12 +28,13 @@ module Abuse end def remove_old_scores - count = user.trust_scores_for_source(source).count + user_scores = Abuse::UserTrustScore.new(user) + count = user_scores.trust_scores_for_source(source).count return unless count > MAX_EVENTS TrustScore.delete( - user.trust_scores_for_source(source) - .order(created_at: :asc) + user_scores.trust_scores_for_source(source) + .order_created_at_asc .limit(count - MAX_EVENTS) ) end diff --git a/app/models/abuse/user_trust_score.rb b/app/models/abuse/user_trust_score.rb new file mode 100644 index 00000000000..544f84bb316 --- /dev/null +++ b/app/models/abuse/user_trust_score.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Abuse + class UserTrustScore + def initialize(user) + @user = user + end + + def spammer? + spam_score > Abuse::TrustScore::SPAMCHECK_HAM_THRESHOLD + end + + def spam_score + user_scores.spamcheck.average(:score) || 0.0 + end + + def telesign_score + user_scores.telesign.order_created_at_desc.first&.score || 0.0 + end + + def arkose_global_score + user_scores.arkose_global_score.order_created_at_desc.first&.score || 0.0 + end + + def arkose_custom_score + user_scores.arkose_custom_score.order_created_at_desc.first&.score || 0.0 + end + + def trust_scores_for_source(source) + user_scores.where(source: source) + end + + private + + def user_scores + Abuse::TrustScore.where(user_id: @user.id) + end + end +end diff --git a/app/models/note.rb b/app/models/note.rb index 2ac40357625..5274575b528 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -792,6 +792,14 @@ class Note < ApplicationRecord true end + # Use attributes.keys instead of attribute_names to filter out the fields that are skipped during export: + # + # - note_html + # - cached_markdown_version + def attribute_names_for_serialization + attributes.keys + end + private def trigger_note_subscription? diff --git a/app/models/user.rb b/app/models/user.rb index 96cdbb192bc..bc4fe36bff4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2279,30 +2279,6 @@ class User < ApplicationRecord namespace_commit_emails.find_by(namespace: project.root_namespace) end - def spammer? - spam_score > Abuse::TrustScore::SPAMCHECK_HAM_THRESHOLD - end - - def spam_score - abuse_trust_scores.spamcheck.average(:score) || 0.0 - end - - def telesign_score - abuse_trust_scores.telesign.order(created_at: :desc).first&.score || 0.0 - end - - def arkose_global_score - abuse_trust_scores.arkose_global_score.order(created_at: :desc).first&.score || 0.0 - end - - def arkose_custom_score - abuse_trust_scores.arkose_custom_score.order(created_at: :desc).first&.score || 0.0 - end - - def trust_scores_for_source(source) - abuse_trust_scores.where(source: source) - end - def abuse_metadata { account_age: account_age_in_days, @@ -2355,7 +2331,8 @@ class User < ApplicationRecord private def block_or_ban - if spammer? && account_age_in_days < 7 + user_scores = Abuse::UserTrustScore.new(self) + if user_scores.spammer? && account_age_in_days < 7 ban_and_report else block diff --git a/app/services/packages/npm/create_metadata_cache_service.rb b/app/services/packages/npm/create_metadata_cache_service.rb index 75cff5c5453..f470b9f1202 100644 --- a/app/services/packages/npm/create_metadata_cache_service.rb +++ b/app/services/packages/npm/create_metadata_cache_service.rb @@ -30,7 +30,7 @@ module Packages attr_reader :package_name, :project def metadata_content - metadata.payload.to_json + ::API::Entities::NpmPackage.represent(metadata.payload).to_json end strong_memoize_attr :metadata_content |