Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-02 15:09:08 +00:00
parent b0107e8756
commit 840d5ecdbb
84 changed files with 1369 additions and 638 deletions

View File

@ -120,10 +120,10 @@ export default {
]),
...mapGetters('details', ['stacktrace']),
firstReleaseLink() {
return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseShortVersion}`;
return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseVersion}`;
},
lastReleaseLink() {
return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseShortVersion}`;
return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseVersion}`;
},
showStacktrace() {
return Boolean(this.stacktrace?.length);
@ -400,18 +400,18 @@ export default {
<icon name="external-link" class="ml-1 flex-shrink-0" />
</gl-link>
</li>
<li v-if="error.firstReleaseShortVersion">
<li v-if="error.firstReleaseVersion">
<strong class="bold">{{ __('First seen') }}:</strong>
<time-ago-tooltip :time="error.firstSeen" />
<gl-link :href="firstReleaseLink" target="_blank">
<span>{{ __('Release') }}: {{ error.firstReleaseShortVersion.substr(0, 10) }}</span>
<span>{{ __('Release') }}: {{ error.firstReleaseVersion }}</span>
</gl-link>
</li>
<li v-if="error.lastReleaseShortVersion">
<li v-if="error.lastReleaseVersion">
<strong class="bold">{{ __('Last seen') }}:</strong>
<time-ago-tooltip :time="error.lastSeen" />
<gl-link :href="lastReleaseLink" target="_blank">
<span>{{ __('Release') }}: {{ error.lastReleaseShortVersion.substr(0, 10) }}</span>
<span>{{ __('Release') }}: {{ error.lastReleaseVersion }}</span>
</gl-link>
</li>
<li>

View File

@ -18,8 +18,8 @@ query errorDetails($fullPath: ID!, $errorId: ID!) {
}
externalUrl
externalBaseUrl
firstReleaseShortVersion
lastReleaseShortVersion
firstReleaseVersion
lastReleaseVersion
gitlabCommit
gitlabCommitPath
gitlabIssuePath

View File

@ -1,22 +1,31 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { GlLink, GlSprintf, GlButton } from '@gitlab/ui';
import MrWidgetIcon from './mr_widget_icon.vue';
import PipelineTourState from './states/mr_widget_pipeline_tour.vue';
import Tracking from '~/tracking';
import { s__ } from '~/locale';
const trackingMixin = Tracking.mixin();
const TRACK_LABEL = 'no_pipeline_noticed';
export default {
name: 'MRWidgetSuggestPipeline',
iconName: 'status_notfound',
popoverTarget: 'suggest-popover',
popoverContainer: 'suggest-pipeline',
trackLabel: 'no_pipeline_noticed',
trackLabel: TRACK_LABEL,
linkTrackValue: 30,
linkTrackEvent: 'click_link',
showTrackValue: 10,
showTrackEvent: 'click_button',
helpContent: s__(
`mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd} by simply adding a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust.`,
),
helpURL: 'https://about.gitlab.com/blog/2019/07/12/guide-to-ci-cd-pipelines/',
components: {
GlLink,
GlSprintf,
GlButton,
MrWidgetIcon,
PipelineTourState,
},
mixins: [trackingMixin],
props: {
pipelinePath: {
type: String,
@ -31,45 +40,89 @@ export default {
required: true,
},
},
computed: {
tracking() {
return {
label: TRACK_LABEL,
property: this.humanAccess,
};
},
},
mounted() {
this.track();
},
};
</script>
<template>
<div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest gl-mb-3">
<mr-widget-icon :name="$options.iconName" />
<div :id="$options.popoverTarget">
<gl-sprintf
:message="
s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd}
<div class="mr-widget-body mr-pipeline-suggest gl-mb-3">
<div class="gl-display-flex gl-align-items-center">
<mr-widget-icon :name="$options.iconName" />
<div>
<gl-sprintf
:message="
s__(`mrWidget|%{prefixToLinkStart}No pipeline%{prefixToLinkEnd}
%{addPipelineLinkStart}Add the .gitlab-ci.yml file%{addPipelineLinkEnd}
to create one.`)
"
>
<template #prefixToLink="{content}">
"
>
<template #prefixToLink="{content}">
<strong>
{{ content }}
</strong>
</template>
<template #addPipelineLink="{content}">
<gl-link
:href="pipelinePath"
class="gl-ml-1"
data-testid="add-pipeline-link"
:data-track-property="humanAccess"
:data-track-value="$options.linkTrackValue"
:data-track-event="$options.linkTrackEvent"
:data-track-label="$options.trackLabel"
>
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</div>
</div>
<div class="row">
<div class="col-md-5 order-md-last col-12 gl-mt-5 mt-md-n3 svg-content svg-225">
<img data-testid="pipeline-image" :src="pipelineSvgPath" />
</div>
<div class="col-md-7 order-md-first col-12">
<div class="ml-6 gl-pt-5">
<strong>
{{ content }}
{{ s__('mrWidget|Are you adding technical debt or code vulnerabilities?') }}
</strong>
</template>
<template #addPipelineLink="{content}">
<gl-link
<p class="gl-mt-2">
<gl-sprintf :message="$options.helpContent">
<template #link="{ content }">
<gl-link
data-testid="help"
:href="$options.helpURL"
target="_blank"
class="font-size-inherit"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
<gl-button
data-testid="ok"
category="primary"
class="gl-mt-2"
variant="info"
:href="pipelinePath"
class="ml-2 js-add-pipeline-path"
:data-track-property="humanAccess"
:data-track-value="$options.linkTrackValue"
:data-track-event="$options.linkTrackEvent"
:data-track-value="$options.showTrackValue"
:data-track-event="$options.showTrackEvent"
:data-track-label="$options.trackLabel"
>
{{ content }}
</gl-link>
</template>
</gl-sprintf>
<pipeline-tour-state
:pipeline-path="pipelinePath"
:pipeline-svg-path="pipelineSvgPath"
:human-access="humanAccess"
:popover-target="$options.popoverTarget"
:popover-container="$options.popoverContainer"
:track-label="$options.trackLabel"
/>
{{ __('Show me how to add a pipeline') }}
</gl-button>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,143 +0,0 @@
<script>
import { GlPopover, GlDeprecatedButton, GlSprintf, GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import Cookies from 'js-cookie';
import { parseBoolean } from '~/lib/utils/common_utils';
import Tracking from '~/tracking';
import { s__ } from '~/locale';
const trackingMixin = Tracking.mixin();
const cookieKey = 'suggest_pipeline_dismissed';
export default {
name: 'MRWidgetPipelineTour',
dismissTrackValue: 20,
showTrackValue: 10,
trackEvent: 'click_button',
helpContent: s__(
`mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust.`,
),
helpURL: 'https://about.gitlab.com/blog/2019/07/12/guide-to-ci-cd-pipelines/',
components: {
GlPopover,
GlDeprecatedButton,
Icon,
GlSprintf,
GlLink,
},
mixins: [trackingMixin],
props: {
pipelinePath: {
type: String,
required: true,
},
pipelineSvgPath: {
type: String,
required: true,
},
humanAccess: {
type: String,
required: true,
},
popoverTarget: {
type: String,
required: true,
},
popoverContainer: {
type: String,
required: true,
},
trackLabel: {
type: String,
required: true,
},
},
data() {
return {
popoverDismissed: parseBoolean(Cookies.get(cookieKey)),
tracking: {
label: this.trackLabel,
property: this.humanAccess,
},
};
},
mounted() {
this.trackOnShow();
},
methods: {
trackOnShow() {
if (!this.popoverDismissed) {
this.track();
}
},
dismissPopover() {
this.popoverDismissed = true;
Cookies.set(cookieKey, this.popoverDismissed, { expires: 365 });
},
},
};
</script>
<template>
<gl-popover
v-if="!popoverDismissed"
show
:target="popoverTarget"
:container="popoverContainer"
placement="rightbottom"
>
<template #title>
<button
class="btn-blank float-right mt-1"
type="button"
:aria-label="__('Close')"
:data-track-property="humanAccess"
:data-track-value="$options.dismissTrackValue"
:data-track-event="$options.trackEvent"
:data-track-label="trackLabel"
@click="dismissPopover"
>
<icon name="close" aria-hidden="true" />
</button>
{{ s__('mrWidget|Are you adding technical debt or code vulnerabilities?') }}
</template>
<div class="svg-content svg-150 pt-1">
<img :src="pipelineSvgPath" />
</div>
<gl-sprintf :message="$options.helpContent">
<template #link="{ content }">
<gl-link :href="$options.helpURL" target="_blank" class="font-size-inherit">{{
content
}}</gl-link>
</template>
</gl-sprintf>
<gl-deprecated-button
ref="ok"
category="primary"
class="mt-2 mb-0"
variant="info"
block
:href="pipelinePath"
:data-track-property="humanAccess"
:data-track-value="$options.showTrackValue"
:data-track-event="$options.trackEvent"
:data-track-label="trackLabel"
>
{{ __('Show me how to add a pipeline') }}
</gl-deprecated-button>
<gl-deprecated-button
ref="no-thanks"
category="secondary"
class="mt-2 mb-0"
variant="info"
block
:data-track-property="humanAccess"
:data-track-value="$options.dismissTrackValue"
:data-track-event="$options.trackEvent"
:data-track-label="trackLabel"
@click="dismissPopover"
>
{{ __('No thanks') }}
</gl-deprecated-button>
</gl-popover>
</template>

View File

@ -88,6 +88,7 @@ export default {
type="search"
class="mb-3"
autofocus
data-qa-selector="project_search_field"
@input="onInput"
/>
<div class="d-flex flex-column">
@ -107,6 +108,7 @@ export default {
:project="project"
:matcher="searchQuery"
class="js-project-list-item"
data-qa-selector="project_list_item"
@click="projectClicked(project)"
/>
</div>

View File

@ -1,9 +1,10 @@
import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text';
import renderIdentifierText from './renderers/render_identifier_text';
import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
const listRenderers = [renderKramdownList];
const textRenderers = [renderKramdownText, renderIdentifierText];
const textRenderers = [renderKramdownText, renderIdentifierText, renderEmbeddedRubyText];
const executeRenderer = (renderers, node, context) => {
const availableRenderer = renderers.find(renderer => renderer.canRender(node, context));

View File

@ -0,0 +1,13 @@
import { buildUneditableTokens } from './build_uneditable_token';
const embeddedRubyRegex = /(^<%.+%>$)/;
const canRender = ({ literal }) => {
return embeddedRubyRegex.test(literal);
};
const render = (_, { origin }) => {
return buildUneditableTokens(origin());
};
export default { canRender, render };

View File

@ -33,7 +33,7 @@ const hasExitingPotential = literal => literal.includes(']: ');
const hasAdjacentExit = node => {
let currentNode = node;
while (currentNode.next && currentNode.literal !== null) {
while (currentNode && currentNode.literal !== null) {
if (hasExitingPotential(currentNode.literal)) {
return true;
}

View File

@ -20,7 +20,7 @@
width: 100%;
}
$image-widths: 80 130 150 250 306 394 430;
$image-widths: 80 130 150 225 250 306 394 430;
@each $width in $image-widths {
&.svg-#{$width} {
img,

View File

@ -397,6 +397,16 @@ $mr-widget-min-height: 69px;
}
}
}
&.mr-pipeline-suggest {
border-radius: $border-radius-default;
line-height: 20px;
border: 1px solid $border-color;
.circle-icon-container {
color: $gl-text-color-quaternary;
}
}
}
.mr-widget-help {
@ -596,26 +606,6 @@ $mr-widget-min-height: 69px;
}
}
.mr-pipeline-suggest {
flex-wrap: wrap;
border-radius: $border-radius-default;
padding: $gl-padding;
border: 1px solid $border-color;
min-height: $mr-widget-min-height;
@include media-breakpoint-up(md) {
align-items: center;
}
.circle-icon-container {
color: $gl-text-color-quaternary;
}
.popover {
z-index: 240;
}
}
.card-new-merge-request {
.card-header {
padding: 5px 10px;

View File

@ -6,7 +6,7 @@ class Admin::ServicesController < Admin::ApplicationController
before_action :service, only: [:edit, :update]
before_action :whitelist_query_limiting, only: [:index]
before_action only: :edit do
push_frontend_feature_flag(:integration_form_refactor)
push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
end
def index

View File

@ -9,7 +9,7 @@ module IntegrationsActions
before_action :not_found, unless: :integrations_enabled?
before_action :integration, only: [:edit, :update, :test]
before_action only: :edit do
push_frontend_feature_flag(:integration_form_refactor)
push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
module WikiActions
include DiffHelper
include SendsBlob
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
@ -11,8 +12,9 @@ module WikiActions
before_action :authorize_admin_wiki!, only: :destroy
before_action :wiki
before_action :page, only: [:show, :edit, :update, :history, :destroy]
before_action :page, only: [:show, :edit, :update, :history, :destroy, :diff]
before_action :load_sidebar, except: [:pages]
before_action :set_content_class
before_action only: [:show, :edit, :update] do
@valid_encoding = valid_encoding?
@ -25,6 +27,8 @@ module WikiActions
redirect_to wiki_path(wiki)
end
end
helper_method :view_file_button, :diff_file_html_data
end
def new
@ -136,6 +140,19 @@ module WikiActions
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def diff
return render_404 unless page
apply_diff_view_cookie!
@diffs = page.diffs(diff_options)
@diff_notes_disabled = true
render 'shared/wikis/diff'
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def destroy
WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page)
@ -207,7 +224,7 @@ module WikiActions
def page_params
keys = [:id]
keys << :version_id if params[:action] == 'show'
keys << :version_id if %w[show diff].include?(params[:action])
params.values_at(*keys)
end
@ -233,4 +250,25 @@ module WikiActions
wiki.repository.blob_at(commit.id, params[:id])
end
end
def set_content_class
@content_class = 'limit-container-width' unless fluid_layout # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
# Override CommitsHelper#view_file_button
def view_file_button(commit_sha, *args)
path = wiki_page_path(wiki, page, version_id: page.version.id)
helpers.link_to(path, class: 'btn') do
helpers.raw(_('View page @ ')) + helpers.content_tag(:span, Commit.truncate_sha(commit_sha), class: 'commit-sha')
end
end
# Override DiffHelper#diff_file_html_data
def diff_file_html_data(_project, _diff_file_path, diff_commit_id)
{
blob_diff_path: wiki_page_path(wiki, page, action: :diff, version_id: diff_commit_id),
view: diff_view
}
end
end

View File

@ -12,7 +12,7 @@ class Projects::ServicesController < Projects::ApplicationController
before_action :set_deprecation_notice_for_prometheus_service, only: [:edit, :update]
before_action :redirect_deprecated_prometheus_service, only: [:update]
before_action only: :edit do
push_frontend_feature_flag(:integration_form_refactor)
push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
end
respond_to :html

View File

@ -14,6 +14,8 @@ module Types
description: 'Number of lines deleted'
field :changes, GraphQL::INT_TYPE, null: false,
description: 'Number of lines changed'
field :file_count, GraphQL::INT_TYPE, null: false,
description: 'Number of files changed'
def changes
object[:additions] + object[:deletions]

View File

@ -76,8 +76,14 @@ module Types
description: 'Commit the error was last seen'
field :first_release_short_version, GraphQL::STRING_TYPE,
null: true,
description: 'Release version the error was first seen'
description: 'Release short version the error was first seen'
field :last_release_short_version, GraphQL::STRING_TYPE,
null: true,
description: 'Release short version the error was last seen'
field :first_release_version, GraphQL::STRING_TYPE,
null: true,
description: 'Release version the error was first seen'
field :last_release_version, GraphQL::STRING_TYPE,
null: true,
description: 'Release version the error was last seen'
field :gitlab_commit, GraphQL::STRING_TYPE,

View File

@ -153,11 +153,11 @@ module Types
end
def diff_stats_summary
nil_stats = { additions: 0, deletions: 0 }
nil_stats = { additions: 0, deletions: 0, file_count: 0 }
return nil_stats unless object.diff_stats.present?
object.diff_stats.each_with_object(nil_stats) do |status, hash|
hash.merge!(additions: status.additions, deletions: status.deletions) { |_, x, y| x + y }
hash.merge!(additions: status.additions, deletions: status.deletions, file_count: 1) { |_, x, y| x + y }
end
end
end

View File

@ -181,15 +181,11 @@ module CommitsHelper
end
def view_file_button(commit_sha, diff_new_path, project, replaced: false)
path = project_blob_path(project, tree_join(commit_sha, diff_new_path))
title = replaced ? _('View replaced file @ ') : _('View file @ ')
link_to(
project_blob_path(project,
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file'
) do
raw(title) + content_tag(:span, Commit.truncate_sha(commit_sha),
class: 'commit-sha')
link_to(path, class: 'btn') do
raw(title) + content_tag(:span, truncate_sha(commit_sha), class: 'commit-sha')
end
end

View File

@ -135,8 +135,7 @@ module DiffHelper
def diff_file_html_data(project, diff_file_path, diff_commit_id)
{
blob_diff_path: project_blob_diff_path(project,
tree_join(diff_commit_id, diff_file_path)),
blob_diff_path: project_blob_diff_path(project, tree_join(diff_commit_id, diff_file_path)),
view: diff_view
}
end

View File

@ -27,7 +27,7 @@ module NavHelper
end
elsif current_path?('jobs#show')
%w[page-gutter build-sidebar right-sidebar-expanded]
elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access', 'destroy')
elsif current_controller?('wikis') && current_action?('show', 'create', 'edit', 'update', 'history', 'git_access', 'destroy', 'diff')
%w[page-gutter wiki-sidebar right-sidebar-expanded]
else
[]

View File

@ -96,7 +96,7 @@ module ServicesHelper
end
def integration_form_refactor?
Feature.enabled?(:integration_form_refactor, @project)
Feature.enabled?(:integration_form_refactor, @project, default_enabled: true)
end
def integration_form_data(integration)

View File

@ -3,6 +3,30 @@
module WikiHelper
include API::Helpers::RelatedResourcesHelpers
def wiki_page_title(page, action = nil)
titles = [_('Wiki')]
if page.persisted?
titles << page.human_title
breadcrumb_title(page.human_title)
wiki_breadcrumb_dropdown_links(page.slug)
end
titles << action if action
page_title(*titles.reverse)
add_to_breadcrumbs(_('Wiki'), wiki_path(page.wiki))
end
def link_to_wiki_page(page, **options)
link_to page.human_title, wiki_page_path(page.wiki, page), **options
end
def wiki_sidebar_toggle_button
content_tag :button, class: 'btn btn-default sidebar-toggle js-sidebar-wiki-toggle', role: 'button', type: 'button' do
sprite_icon('chevron-double-lg-left')
end
end
# Produces a pure text breadcrumb for a given page.
#
# page_slug - The slug of a WikiPage object.

View File

@ -31,7 +31,7 @@ module Ci
merge_request_event: 10,
external_pull_request_event: 11,
parent_pipeline: 12,
ondemand_scan: 13
ondemand_dast_scan: 13
}
end

View File

@ -301,6 +301,10 @@ class WikiPage
version&.commit&.committed_date
end
def diffs(diff_options = {})
Gitlab::Diff::FileCollection::WikiPage.new(self, diff_options: diff_options)
end
private
def serialize_front_matter(hash)

View File

@ -27,6 +27,7 @@ module MergeRequests
success
end
end
log_info("Merge process finished on JID #{merge_jid} with state #{state}")
rescue MergeError => e
handle_merge_error(log_message: e.message, save_message_on_model: true)

View File

@ -37,7 +37,7 @@
an outdated change in
commit
%span.commit-sha= Commit.truncate_sha(discussion.commit_id)
%span.commit-sha= truncate_sha(discussion.commit_id)
- else
- unless discussion.active?
an old version of

View File

@ -26,16 +26,16 @@
%ul
- if dashboard_nav_link?(:groups)
%li.d-md-none
= link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups' do
= link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', data: { qa_selector: 'groups_link' } do
= _('Groups')
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity' do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', data: { qa_selector: 'activity_link' } do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', data: { qa_selector: 'milestones_link' } do
= _('Milestones')
- if dashboard_nav_link?(:snippets)

View File

@ -16,6 +16,8 @@
= diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'd-none d-sm-inline-block')
- elsif current_controller?(:compare)
= diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'd-none d-sm-inline-block')
- elsif current_controller?(:wikis)
= toggle_whitespace_link(url_for(params_with_whitespace), class: 'd-none d-sm-inline-block')
.btn-group
= inline_diff_btn
= parallel_diff_btn

View File

@ -2,8 +2,7 @@
- page_title s_("WikiClone|Git Access"), _("Wiki")
.wiki-page-header.top-area.has-sidebar-toggle.py-3.flex-column.flex-lg-row
%button.btn.btn-default.d-block.d-sm-block.d-md-none.float-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= sprite_icon('chevron-double-lg-left')
= wiki_sidebar_toggle_button
.git-access-header.w-100.d-flex.flex-column.justify-content-center
%span

View File

@ -0,0 +1,32 @@
- wiki_page_title @page, _('Changes')
- commit = @diffs.diffable
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
= wiki_sidebar_toggle_button
.nav-text
%h2.wiki-page-title
= link_to_wiki_page @page
%span.light
&middot;
= _('Changes')
.nav-controls.pb-md-3.pb-lg-0
= link_to wiki_page_path(@wiki, @page, action: :history), class: 'btn', role: 'button', data: { qa_selector: 'page_history_button' } do
= s_('Wiki|Page history')
.page-content-header
.header-main-content
%strong= markdown_field(commit, :title)
%span.d-none.d-sm-inline= _('authored')
#{time_ago_with_tooltip(commit.authored_date)}
%span= s_('ByAuthor|by')
= author_avatar(commit, size: 24, has_tooltip: false)
%strong
= commit_author_link(commit, avatar: true, size: 24)
- if commit.description.present?
%pre.commit-description<
= preserve(markdown_field(commit, :description))
= render 'projects/diffs/diffs', diffs: @diffs
= render 'shared/wikis/sidebar'

View File

@ -1,18 +1,14 @@
- @content_class = "limit-container-width" unless fluid_layout
- add_to_breadcrumbs _("Wiki"), wiki_page_path(@wiki, @page)
- breadcrumb_title @page.persisted? ? _("Edit") : _("New")
- page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki")
- wiki_page_title @page, @page.persisted? ? _('Edit') : _('New')
= wiki_page_errors(@error)
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= sprite_icon('chevron-double-lg-left')
= wiki_sidebar_toggle_button
.nav-text
%h2.wiki-page-title
- if @page.persisted?
= link_to @page.human_title, wiki_page_path(@wiki, @page)
= link_to_wiki_page @page
%span.light
&middot;
= s_("Wiki|Edit Page")

View File

@ -1,41 +1,38 @@
- page_title _("History"), @page.human_title, _("Wiki")
- wiki_page_title @page, _('History')
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= sprite_icon('chevron-double-lg-left')
= wiki_sidebar_toggle_button
.nav-text
%h2.wiki-page-title
= link_to @page.human_title, wiki_page_path(@wiki, @page)
= link_to_wiki_page @page
%span.light
&middot;
= _("History")
= _('History')
.table-holder
%table.table
%thead
%tr
%th= s_("Wiki|Page version")
%th= _("Author")
%th= _("Commit Message")
%th= _("Last updated")
%th= _("Format")
%tbody
- @page_versions.each_with_index do |version, index|
- commit = version
.prepend-top-default.gl-mb-3
.table-holder
%table.table.wiki-history
%thead
%tr
%td
= link_to wiki_page_path(@wiki, @page, version_id: index == 0 ? nil : commit.id) do
= truncate_sha(commit.id)
%td
= commit.author_name
%td
= commit.message
%td
#{time_ago_with_tooltip(version.authored_date)}
%td
%strong
= version.format
= paginate @page_versions, theme: 'gitlab'
%th= s_('Wiki|Page version')
%th= _('Author')
%th= _('Changes')
%th= _('Last updated')
%tbody
- @page_versions.each do |commit|
%tr
%td
= link_to wiki_page_path(@wiki, @page, version_id: commit.id) do
= truncate_sha(commit.id)
%td
= commit.author_name
%td
%span.str-truncated-60
= link_to wiki_page_path(@wiki, @page, action: :diff, version_id: commit.id), { title: commit.message } do
= commit.message
%td
= time_ago_with_tooltip(commit.authored_date)
= paginate @page_versions, theme: 'gitlab'
= render 'shared/wikis/sidebar'

View File

@ -1,19 +1,14 @@
- @content_class = "limit-container-width" unless fluid_layout
- breadcrumb_title @page.human_title
- wiki_breadcrumb_dropdown_links(@page.slug)
- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), wiki_path(@wiki)
- wiki_page_title @page
.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= sprite_icon('chevron-double-lg-left')
= wiki_sidebar_toggle_button
.nav-text.flex-fill
%h2.wiki-page-title{ data: { qa_selector: 'wiki_page_title' } }= @page.human_title
%span.wiki-last-edit-by
- if @page.last_version
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.last_version.authored_date)}
= time_ago_with_tooltip(@page.last_version.authored_date)
.nav-controls.pb-md-3.pb-lg-0
= render 'shared/wikis/main_links'

View File

@ -0,0 +1,5 @@
---
title: Allow diffing changes in wiki history
merge_request: 35330
author: gwhyte, Steve Mokris
type: added

View File

@ -0,0 +1,5 @@
---
title: Use full version instead of short version for Sentry Error Release links.
merge_request: 35623
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add a custom HTML renderer to the Static Site Editor for embedded ruby (ERB) syntax
merge_request: 35261
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix unique case where static site editor's custom renderer for identifier syntax didn't robustly handle inline code
merge_request: 35775
author: Derek Knox
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add MergeRequest.diffStatsSummary.fileCount to graphql API
merge_request: 35685
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Update integration form to use GitLab UI components
merge_request: 35582
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Propagate error on FF pre-receive failure
merge_request: 35633
author:
type: fixed

View File

@ -10,6 +10,7 @@ scope(controller: :wikis) do
scope(path: 'wikis/*id', as: :wiki, format: false) do
get :edit
get :history
get :diff
post :preview_markdown
get '/', action: :show
put '/', action: :update

View File

@ -3164,6 +3164,11 @@ type DiffStatsSummary {
Number of lines deleted
"""
deletions: Int!
"""
Number of files changed
"""
fileCount: Int!
}
type Discussion implements ResolvableInterface {
@ -5393,6 +5398,31 @@ type Group {
startDate: ISO8601Date!
): VulnerabilitiesCountByDayAndSeverityConnection
"""
Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups
"""
vulnerabilityScanners(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): VulnerabilityScannerConnection
"""
Web URL of the group
"""
@ -5520,6 +5550,31 @@ type InstanceSecurityDashboard {
"""
last: Int
): ProjectConnection!
"""
Vulnerability scanners reported on the vulnerabilties from projects selected in Instance Security Dashboard
"""
vulnerabilityScanners(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): VulnerabilityScannerConnection
}
"""
@ -9771,6 +9826,31 @@ type Project {
state: [VulnerabilityState!]
): VulnerabilityConnection
"""
Vulnerability scanners reported on the project vulnerabilties
"""
vulnerabilityScanners(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): VulnerabilityScannerConnection
"""
Counts for each severity of vulnerability of the project
"""
@ -11465,10 +11545,15 @@ type SentryDetailedError {
firstReleaseLastCommit: String
"""
Release version the error was first seen
Release short version the error was first seen
"""
firstReleaseShortVersion: String
"""
Release version the error was first seen
"""
firstReleaseVersion: String
"""
Timestamp when the error was first seen
"""
@ -11505,10 +11590,15 @@ type SentryDetailedError {
lastReleaseLastCommit: String
"""
Release version the error was last seen
Release short version the error was last seen
"""
lastReleaseShortVersion: String
"""
Release version the error was last seen
"""
lastReleaseVersion: String
"""
Timestamp when the error was last seen
"""
@ -14443,6 +14533,51 @@ type VulnerabilityScanner {
Name of the vulnerability scanner
"""
name: String
"""
Type of the vulnerability report
"""
reportType: VulnerabilityReportType
"""
Vendor of the vulnerability scanner
"""
vendor: String
}
"""
The connection type for VulnerabilityScanner.
"""
type VulnerabilityScannerConnection {
"""
A list of edges.
"""
edges: [VulnerabilityScannerEdge]
"""
A list of nodes.
"""
nodes: [VulnerabilityScanner]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type VulnerabilityScannerEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: VulnerabilityScanner
}
"""

View File

@ -8778,6 +8778,24 @@
"description": "Number of lines deleted",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fileCount",
"description": "Number of files changed",
"args": [
],
"type": {
"kind": "NON_NULL",
@ -14808,6 +14826,59 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerabilityScanners",
"description": "Vulnerability scanners reported on the project vulnerabilties of the group and its subgroups",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityScannerConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "webUrl",
"description": "Web URL of the group",
@ -15206,6 +15277,59 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerabilityScanners",
"description": "Vulnerability scanners reported on the vulnerabilties from projects selected in Instance Security Dashboard",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityScannerConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -28685,6 +28809,59 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerabilityScanners",
"description": "Vulnerability scanners reported on the project vulnerabilties",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityScannerConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerabilitySeveritiesCount",
"description": "Counts for each severity of vulnerability of the project",
@ -33654,6 +33831,20 @@
},
{
"name": "firstReleaseShortVersion",
"description": "Release short version the error was first seen",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "firstReleaseVersion",
"description": "Release version the error was first seen",
"args": [
@ -33786,6 +33977,20 @@
},
{
"name": "lastReleaseShortVersion",
"description": "Release short version the error was last seen",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lastReleaseVersion",
"description": "Release version the error was last seen",
"args": [
@ -42503,6 +42708,146 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "reportType",
"description": "Type of the vulnerability report",
"args": [
],
"type": {
"kind": "ENUM",
"name": "VulnerabilityReportType",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vendor",
"description": "Vendor of the vulnerability scanner",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityScannerConnection",
"description": "The connection type for VulnerabilityScanner.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "VulnerabilityScannerEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "VulnerabilityScanner",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityScannerEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityScanner",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,

View File

@ -545,6 +545,7 @@ Aggregated summary of changes
| `additions` | Int! | Number of lines added |
| `changes` | Int! | Number of lines changed |
| `deletions` | Int! | Number of lines deleted |
| `fileCount` | Int! | Number of files changed |
## Discussion
@ -1679,7 +1680,8 @@ A Sentry error.
| `externalBaseUrl` | String! | External Base URL of the Sentry Instance |
| `externalUrl` | String! | External URL of the error |
| `firstReleaseLastCommit` | String | Commit the error was first seen |
| `firstReleaseShortVersion` | String | Release version the error was first seen |
| `firstReleaseShortVersion` | String | Release short version the error was first seen |
| `firstReleaseVersion` | String | Release version the error was first seen |
| `firstSeen` | Time! | Timestamp when the error was first seen |
| `frequency` | SentryErrorFrequency! => Array | Last 24hr stats of the error |
| `gitlabCommit` | String | GitLab commit SHA attributed to the Error based on the release version |
@ -1687,7 +1689,8 @@ A Sentry error.
| `gitlabIssuePath` | String | URL of GitLab Issue |
| `id` | ID! | ID (global ID) of the error |
| `lastReleaseLastCommit` | String | Commit the error was last seen |
| `lastReleaseShortVersion` | String | Release version the error was last seen |
| `lastReleaseShortVersion` | String | Release short version the error was last seen |
| `lastReleaseVersion` | String | Release version the error was last seen |
| `lastSeen` | Time! | Timestamp when the error was last seen |
| `message` | String | Sentry metadata message of the error |
| `sentryId` | String! | ID (Sentry ID) of the error |
@ -2219,6 +2222,8 @@ Represents a vulnerability scanner.
| --- | ---- | ---------- |
| `externalId` | String | External ID of the vulnerability scanner |
| `name` | String | Name of the vulnerability scanner |
| `reportType` | VulnerabilityReportType | Type of the vulnerability report |
| `vendor` | String | Vendor of the vulnerability scanner |
## VulnerabilitySeveritiesCount

View File

@ -290,7 +290,7 @@ Example response:
### Set override flag for a member of a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 12.10.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 13.0.
By default, the access level of LDAP group members is set to the value specified
by LDAP through Group Sync. You can allow access level overrides by calling this endpoint.
@ -326,7 +326,7 @@ Example response:
### Remove override for a member of a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 12.10.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4875) in GitLab 13.0.
Sets the override flag to false and allows LDAP Group Sync to reset the access
level to the LDAP-prescribed value.

View File

@ -17,7 +17,9 @@ Working with our frontend assets requires Node (v10.13.0 or greater) and Yarn
For our currently-supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).
Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers. Login to BrowserStack with the credentials saved in GitLab's [shared 1Password account](https://about.gitlab.com/handbook/security/#1password-for-teams).
Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers.
Sign in to BrowserStack with the credentials saved in the **Engineering** vault of GitLab's
[shared 1Password account](https://about.gitlab.com/handbook/security/#1password-guide).
## Initiatives

View File

@ -88,7 +88,7 @@ Take into consideration that such action can make the feature available on
GitLab.com shortly after the change to the feature flag is merged.
Changing the default state or removing the feature flag has to be done before
the 22nd of the month, _at least_ 2 working days before, in order for the change
the 22nd of the month, _at least_ 3-4 working days before, in order for the change
to be included in the final self-managed release.
In addition to this, the feature behind feature flag should:

View File

@ -856,7 +856,8 @@ Some regressions only affect a specific browser version. We can install and test
[BrowserStack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
You can use it directly through the [live app](https://www.browserstack.com/live) or you can install the [chrome extension](https://chrome.google.com/webstore/detail/browserstack/nkihdmlheodkdfojglpcjjmioefjahjb) for easy access.
You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
Sign in to BrowserStack with the credentials saved in the **Engineering** vault of GitLab's
[shared 1Password account](https://about.gitlab.com/handbook/security/#1password-guide).
### Firefox

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -748,6 +748,11 @@ Note the following properties:
![heatmap panel type](img/heatmap_panel_type.png)
CAUTION: **Warning:**
When a query returns too many data points, the heatmap data bucket dimensions tend downwards to 0, making the chart's data invisible, as shown in the image below. To fix this problem, limit the amount of data returned by changing the time range filter on the metrics dashboard UI, or adding the **step** property to your dashboard's YAML file.
![heatmap chart_too_much_data](img/heatmap_chart_too_much_data_v_13_2.png)
### Templating variables for metrics dashboards
Templating variables can be used to make your metrics dashboard more versatile.

View File

@ -73,16 +73,16 @@ module Gitlab
# @return [Hash<String,Array<String>>]
def changes_by_category
all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
hash[category_for_file(file)] << file
categories_for_file(file).each { |category| hash[category] << file }
end
end
# Determines the category a file is in, e.g., `:frontend` or `:backend`
# @return[Symbol]
def category_for_file(file)
_, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }
# Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`.
# @return Array<Symbol>
def categories_for_file(file)
_, categories = CATEGORIES.find { |regexp, _| regexp.match?(file) }
category || :unknown
Array(categories || :unknown)
end
# Returns the GFM for a category label, making its best guess if it's not
@ -125,10 +125,13 @@ module Gitlab
jest\.config\.js |
package\.json |
yarn\.lock |
config/.+\.js |
\.gitlab/ci/frontend\.gitlab-ci\.yml
config/.+\.js
)\z}x => :frontend,
%r{(\A|/)(
\.gitlab/ci/frontend\.gitlab-ci\.yml
)\z}x => %i[frontend engineering_productivity],
%r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
%r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Gitlab
module Diff
module FileCollection
class WikiPage < Base
def initialize(page, diff_options:)
commit = page.wiki.commit(page.version.commit)
diff_options = diff_options.merge(
expanded: true,
paths: [page.path]
)
super(commit,
# TODO: Uncouple diffing from projects
# https://gitlab.com/gitlab-org/gitlab/-/issues/217752
project: page.wiki,
diff_options: diff_options,
diff_refs: commit.diff_refs)
end
end
end
end
end

View File

@ -22,6 +22,7 @@ module Gitlab
:id,
:last_release_last_commit,
:last_release_short_version,
:last_release_version,
:last_seen,
:message,
:project_id,

View File

@ -101,6 +101,10 @@ module Gitlab
wrapped_gitaly_errors do
gitaly_find_page(title: title, version: version, dir: dir)
end
rescue Gitlab::Git::CommandError
# Return nil for invalid versions.
# This can be removed with https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2323 in place.
nil
end
def file(name, version)

View File

@ -178,6 +178,10 @@ module Gitlab
timeout: GitalyClient.long_timeout
)
if response.pre_receive_error.present?
raise Gitlab::Git::PreReceiveError.new("GL-HOOK-ERR: pre-receive hook failed.")
end
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::CommitError, e

View File

@ -168,7 +168,8 @@ module Sentry
first_release_short_version: issue.dig('firstRelease', 'shortVersion'),
first_release_version: issue.dig('firstRelease', 'version'),
last_release_last_commit: issue.dig('lastRelease', 'lastCommit'),
last_release_short_version: issue.dig('lastRelease', 'shortVersion')
last_release_short_version: issue.dig('lastRelease', 'shortVersion'),
last_release_version: issue.dig('lastRelease', 'version')
})
end

View File

@ -10187,9 +10187,6 @@ msgstr ""
msgid "Forks"
msgstr ""
msgid "Format"
msgstr ""
msgid "Format: %{dateFormat}"
msgstr ""
@ -10280,6 +10277,9 @@ msgstr ""
msgid "Generate new export"
msgstr ""
msgid "GenericReports|Report"
msgstr ""
msgid "Geo"
msgstr ""
@ -11234,16 +11234,16 @@ msgstr ""
msgid "Group: %{name}"
msgstr ""
msgid "GroupActivityMetrics|New Members created"
msgid "GroupActivityMetrics|Issues opened"
msgstr ""
msgid "GroupActivyMetrics|Issues created"
msgid "GroupActivityMetrics|Members added"
msgstr ""
msgid "GroupActivyMetrics|Merge Requests created"
msgid "GroupActivityMetrics|Merge Requests opened"
msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgid "GroupActivityMetrics|Recent activity (last 90 days)"
msgstr ""
msgid "GroupImport|Failed to import group."
@ -15311,9 +15311,6 @@ msgstr ""
msgid "No test coverage"
msgstr ""
msgid "No thanks"
msgstr ""
msgid "No vulnerabilities present"
msgstr ""
@ -25306,6 +25303,9 @@ msgstr ""
msgid "View open merge request"
msgstr ""
msgid "View page @ "
msgstr ""
msgid "View performance dashboard."
msgstr ""
@ -27551,7 +27551,7 @@ msgstr ""
msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated."
msgstr ""
msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd}, simply add a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd} by simply adding a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
msgstr ""
msgid "mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"

View File

@ -448,6 +448,7 @@ module QA
autoload :ConfirmModal, 'qa/page/component/confirm_modal'
autoload :CustomMetric, 'qa/page/component/custom_metric'
autoload :DesignManagement, 'qa/page/component/design_management'
autoload :ProjectSelector, 'qa/page/component/project_selector'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module QA
module Page
module Component
module ProjectSelector
extend QA::Page::PageConcern
def self.included(base)
super
base.view 'app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue' do
element :project_search_field
element :project_list_item
end
end
def fill_project_search_input(project_name)
fill_element :project_search_field, project_name
end
def select_project
click_element :project_list_item
end
end
end
end
end

View File

@ -22,6 +22,9 @@ module QA
element :groups_dropdown, required: true
element :more_dropdown
element :snippets_link
element :groups_link
element :activity_link
element :milestones_link
end
view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do
@ -53,10 +56,10 @@ module QA
end
end
def go_to_snippets
def go_to_more_dropdown_option(option_name)
within_top_menu do
click_element :more_dropdown
click_element :snippets_link
click_element option_name
end
end
@ -148,3 +151,5 @@ module QA
end
end
end
QA::Page::Main::Menu.prepend_if_ee('QA::EE::Page::Main::Menu')

View File

@ -6,7 +6,9 @@ module QA
it 'User creates a personal snippet' do
Flow::Login.sign_in
Page::Main::Menu.perform(&:go_to_snippets)
Page::Main::Menu.perform do |menu|
menu.go_to_more_dropdown_option(:snippets_link)
end
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Snippet title'

View File

@ -8,29 +8,29 @@ module RuboCop
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization'
# We want to exclude our own basetypes and scalars
WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType
QueryType GraphQL::Schema BaseUnion].freeze
ALLOWED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType
QueryType GraphQL::Schema BaseUnion].freeze
def_node_search :authorize?, <<~PATTERN
(send nil? :authorize ...)
PATTERN
def on_class(node)
return if whitelisted?(class_constant(node))
return if whitelisted?(superclass_constant(node))
return if allowed?(class_constant(node))
return if allowed?(superclass_constant(node))
add_offense(node, location: :expression) unless authorize?(node)
end
private
def whitelisted?(class_node)
def allowed?(class_node)
class_const = class_node&.const_name
return false unless class_const
return true if class_const.end_with?('Enum')
WHITELISTED_TYPES.any? { |whitelisted| class_node.const_name.include?(whitelisted) }
ALLOWED_TYPES.any? { |allowed| class_node.const_name.include?(allowed) }
end
def class_constant(node)

View File

@ -8,9 +8,10 @@ RSpec.describe 'User views a wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:path) { 'image.png' }
let(:wiki) { project.wiki }
let(:wiki_page) do
create(:wiki_page,
wiki: project.wiki,
wiki: wiki,
title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})")
end
@ -70,11 +71,13 @@ RSpec.describe 'User views a wiki page' do
click_on('Page history')
page.within(:css, '.nav-text') do
within('.nav-text') do
expect(page).to have_content('History')
end
find('a[href*="?version_id"]')
within('.wiki-history') do
expect(page).to have_css('a[href*="?version_id"]', count: 4)
end
end
end
@ -92,8 +95,8 @@ RSpec.describe 'User views a wiki page' do
let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
it do
expect(page).to have_xpath("//img[@data-src='#{project.wiki.wiki_base_path}/#{path}']")
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
@ -103,7 +106,7 @@ RSpec.describe 'User views a wiki page' do
end
it 'shows the creation page if file does not exist' do
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/#{path}")
expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
@ -114,7 +117,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page has history' do
before do
wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)') # rubocop:disable Rails/SaveBang
end
it 'shows the page history' do
@ -134,13 +137,74 @@ RSpec.describe 'User views a wiki page' do
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
context 'show the diff' do
def expect_diff_links(commit)
diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
expect(page).to have_link('Hide whitespace changes', href: "#{diff_path}&w=1")
expect(page).to have_link('Inline', href: "#{diff_path}&view=inline")
expect(page).to have_link('Side-by-side', href: "#{diff_path}&view=parallel")
expect(page).to have_link("View page @ #{commit.short_id}", href: wiki_page_path(wiki, wiki_page, version_id: commit))
expect(page).to have_css('.diff-file[data-blob-diff-path="%s"]' % diff_path)
end
it 'links to the correct diffs' do
visit project_wiki_history_path(project, wiki_page)
commit1 = wiki.commit('HEAD^')
commit2 = wiki.commit
expect(page).to have_link('created page: home', href: wiki_page_path(wiki, wiki_page, version_id: commit1, action: :diff))
expect(page).to have_link('updated home', href: wiki_page_path(wiki, wiki_page, version_id: commit2, action: :diff))
end
it 'between the current and the previous version of a page' do
commit = wiki.commit
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
expect(page).to have_content('by John Doe')
expect(page).to have_content('updated home')
expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
expect(page).to have_content('some link')
expect_diff_links(commit)
end
it 'between two old versions of a page' do
wiki_page.update(message: 'latest home change', content: 'updated [another link](other-page)') # rubocop:disable Rails/SaveBang:
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
expect(page).to have_content('by John Doe')
expect(page).to have_content('updated home')
expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
expect(page).to have_content('some link')
expect(page).not_to have_content('latest home change')
expect(page).not_to have_content('another link')
expect_diff_links(commit)
end
it 'for the oldest version of a page' do
commit = wiki.commit('HEAD^')
visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
expect(page).to have_content('by John Doe')
expect(page).to have_content('created page: home')
expect(page).to have_content('Showing 1 changed file with 4 additions and 0 deletions')
expect(page).to have_content('Look at this')
expect_diff_links(commit)
end
end
end
context 'when a page has special characters in its title' do
let(:title) { '<foo> !@#$%^&*()[]{}=_+\'"\\|<>? <bar>' }
before do
wiki_page.update(title: title )
wiki_page.update(title: title ) # rubocop:disable Rails/SaveBang
end
it 'preserves the special characters' do
@ -155,7 +219,7 @@ RSpec.describe 'User views a wiki page' do
let(:title) { '<script>alert("title")<script>' }
before do
wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar')
wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar') # rubocop:disable Rails/SaveBang
end
it 'safely displays the page' do
@ -168,7 +232,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page has XSS in its message' do
before do
wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update')
wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update') # rubocop:disable Rails/SaveBang
end
it 'safely displays the message' do

View File

@ -42,7 +42,30 @@
"isBookmarked": false,
"isPublic": false,
"isSubscribed": true,
"lastRelease": null,
"lastRelease": {
"dateReleased": null,
"commitCount": 1,
"url": null,
"data": {},
"lastDeploy": {},
"deployCount": 3,
"dateCreated": "2020-06-29T08:10:45.909Z",
"lastEvent": "2020-06-30T09:47:19.651Z",
"version": "17642328ead24b51867165985996d04b29321448",
"firstEvent": "2020-06-29T09:13:36.696Z",
"lastCommit": {},
"shortVersion": "27de6b42eb4",
"authors": [],
"owner": null,
"newGroups": 208,
"ref": null,
"projects": [
{
"name": "Pump Station",
"slug": "pump-station"
}
]
},
"lastSeen": "2018-11-06T21:19:55Z",
"level": "error",
"logger": null,

View File

@ -5,23 +5,6 @@ import { TEST_HOST } from '../helpers/test_constants';
export const mockProjectDir = '/frontend-fixtures/environments-project';
export const mockApiEndpoint = `${TEST_HOST}/monitoring/mock`;
export const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
settingsPath: '/path/to/settings',
clustersPath: '/path/to/clusters',
tagsPath: '/path/to/tags',
defaultBranch: 'master',
emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
emptyLoadingSvgPath: '/path/to/loading.svg',
emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
customMetricsAvailable: false,
customMetricsPath: '',
validateQueryPath: '',
};
export const customDashboardBasePath = '.gitlab/dashboards';
const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({

View File

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { propsData } from '../mock_data';
import { dashboardProps } from '../fixture_data';
describe('monitoring/pages/dashboard_page', () => {
let wrapper;
@ -28,9 +28,18 @@ describe('monitoring/pages/dashboard_page', () => {
});
it('renders the dashboard page with dashboard component', () => {
buildWrapper({ dashboardProps: propsData });
buildWrapper({ dashboardProps });
const allProps = {
...dashboardProps,
// default props values
rearrangePanelsAvailable: false,
showHeader: true,
showPanels: true,
smallEmptyState: false,
};
expect(findDashboardComponent().props()).toMatchObject(propsData);
expect(findDashboardComponent()).toExist();
expect(allProps).toMatchObject(findDashboardComponent().props());
});
});

View File

@ -1,23 +1,25 @@
import { mount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
import stubChildren from 'helpers/stub_children';
import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import { popoverProps, iconName } from './pipeline_tour_mock_data';
describe('MRWidgetHeader', () => {
describe('MRWidgetSuggestPipeline', () => {
let wrapper;
const pipelinePath = '/foo/bar/add/pipeline/path';
const pipelineSvgPath = '/foo/bar/pipeline/svg/path';
const humanAccess = 'maintainer';
const iconName = 'status_notfound';
let trackingSpy;
const mockTrackingOnWrapper = () => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
};
beforeEach(() => {
document.body.dataset.page = 'projects:merge_requests:show';
trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
wrapper = mount(suggestPipelineComponent, {
propsData: { pipelinePath, pipelineSvgPath, humanAccess },
propsData: popoverProps,
stubs: {
...stubChildren(PipelineTourState),
GlSprintf,
},
});
@ -25,14 +27,17 @@ describe('MRWidgetHeader', () => {
afterEach(() => {
wrapper.destroy();
unmockTracking();
});
describe('template', () => {
const findOkBtn = () => wrapper.find('[data-testid="ok"]');
it('renders add pipeline file link', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes().href).toBe(pipelinePath);
expect(link.attributes().href).toBe(popoverProps.pipelinePath);
});
it('renders the expected text', () => {
@ -52,25 +57,60 @@ describe('MRWidgetHeader', () => {
);
});
describe('tracking', () => {
let spy;
it('renders the show me how button', () => {
const button = findOkBtn();
beforeEach(() => {
spy = mockTracking('_category_', wrapper.element, jest.spyOn);
expect(button.exists()).toBe(true);
expect(button.classes('btn-info')).toEqual(true);
expect(button.attributes('href')).toBe(popoverProps.pipelinePath);
});
it('renders the help link', () => {
const link = wrapper.find('[data-testid="help"]');
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(wrapper.vm.$options.helpURL);
});
it('renders the empty pipelines image', () => {
const image = wrapper.find('[data-testid="pipeline-image"]');
expect(image.exists()).toBe(true);
expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
});
describe('tracking', () => {
it('send event for basic view of the suggest pipeline widget', () => {
const expectedCategory = undefined;
const expectedAction = undefined;
expect(trackingSpy).toHaveBeenCalledWith(expectedCategory, expectedAction, {
label: wrapper.vm.$options.trackLabel,
property: popoverProps.humanAccess,
});
});
afterEach(() => {
unmockTracking();
it('send an event when add pipeline link is clicked', () => {
mockTrackingOnWrapper();
const link = wrapper.find('[data-testid="add-pipeline-link"]');
triggerEvent(link.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
label: wrapper.vm.$options.trackLabel,
property: popoverProps.humanAccess,
value: '30',
});
});
it('send an event when ok button is clicked', () => {
const link = wrapper.find(GlLink);
triggerEvent(link.element);
mockTrackingOnWrapper();
const okBtn = findOkBtn();
triggerEvent(okBtn.element);
expect(spy).toHaveBeenCalledWith('_category_', 'click_link', {
label: 'no_pipeline_noticed',
property: humanAccess,
value: '30',
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: wrapper.vm.$options.trackLabel,
property: popoverProps.humanAccess,
value: '10',
});
});
});

View File

@ -0,0 +1,7 @@
export const popoverProps = {
pipelinePath: '/foo/bar/add/pipeline/path',
pipelineSvgPath: 'assets/illustrations/something.svg',
humanAccess: 'maintainer',
};
export const iconName = 'status_notfound';

View File

@ -1,153 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import { GlPopover, GlLink, GlSprintf } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import { popoverProps, cookieKey } from './pipeline_tour_mock_data';
describe('MRWidgetPipelineTour', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
describe(`when ${cookieKey} cookie is set`, () => {
beforeEach(() => {
Cookies.set(cookieKey, true);
wrapper = shallowMount(pipelineTourState, {
propsData: popoverProps,
});
});
it('does not render the popover', () => {
const popover = wrapper.find(GlPopover);
expect(popover.exists()).toBe(false);
});
describe('tracking', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
});
it('does not call tracking', () => {
expect(trackingSpy).not.toHaveBeenCalled();
});
});
});
describe(`when ${cookieKey} cookie is not set`, () => {
const findOkBtn = () => wrapper.find({ ref: 'ok' });
const findDismissBtn = () => wrapper.find({ ref: 'no-thanks' });
beforeEach(() => {
Cookies.remove(cookieKey);
wrapper = shallowMount(pipelineTourState, {
propsData: popoverProps,
stubs: {
GlSprintf,
},
});
});
it('renders the popover', () => {
const popover = wrapper.find(GlPopover);
expect(popover.exists()).toBe(true);
});
it('renders the help link', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(wrapper.vm.$options.helpURL);
});
it('renders the show me how button', () => {
const button = findOkBtn();
expect(button.exists()).toBe(true);
expect(button.attributes().category).toBe('primary');
});
it('renders the dismiss button', () => {
const button = findDismissBtn();
expect(button.exists()).toBe(true);
expect(button.attributes().category).toBe('secondary');
});
it('renders the empty pipelines image', () => {
const image = wrapper.find('img');
expect(image.exists()).toBe(true);
expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
});
describe('tracking', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
});
it('send event for basic view of popover', () => {
document.body.dataset.page = 'projects:merge_requests:show';
wrapper.vm.trackOnShow();
expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
label: popoverProps.trackLabel,
property: popoverProps.humanAccess,
});
});
it('send an event when ok button is clicked', () => {
const okBtn = findOkBtn();
triggerEvent(okBtn.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: popoverProps.trackLabel,
property: popoverProps.humanAccess,
value: '10',
});
});
it('send an event when dismiss button is clicked', () => {
const dismissBtn = findDismissBtn();
triggerEvent(dismissBtn.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: popoverProps.trackLabel,
property: popoverProps.humanAccess,
value: '20',
});
});
});
describe('dismissPopover', () => {
it('updates popoverDismissed', () => {
const button = findDismissBtn();
const popover = wrapper.find(GlPopover);
button.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(Cookies.get(cookieKey)).toBe('true');
expect(popover.exists()).toBe(false);
});
});
});
});
});
});

View File

@ -1,10 +0,0 @@
export const popoverProps = {
pipelinePath: '/foo/bar/add/pipeline/path',
pipelineSvgPath: 'assets/illustrations/something.svg',
humanAccess: 'maintainer',
popoverTarget: 'suggest-popover',
popoverContainer: 'suggest-pipeline',
trackLabel: 'some_tracking_label',
};
export const cookieKey = 'suggest_pipeline_dismissed';

View File

@ -19,9 +19,7 @@ const buildMockTextNodeWithAdjacentInlineCode = isForward => {
type: 'code',
[direction]: {
literal: isForward ? literalClose : literalOpen,
[direction]: {
literal: null,
},
[direction]: null,
},
},
};
@ -47,6 +45,7 @@ export const kramdownTextNode = buildMockTextNode('{:toc}');
export const identifierTextNode = buildMockTextNode('[Some text]: https://link.com');
export const identifierInlineCodeTextEnteringNode = buildMockTextNodeWithAdjacentInlineCode(true);
export const identifierInlineCodeTextExitingNode = buildMockTextNodeWithAdjacentInlineCode(false);
export const embeddedRubyTextNode = buildMockTextNode('<%= partial("some/path") %>');
export const normalTextNode = buildMockTextNode('This is just normal text.');
const uneditableOpenToken = {

View File

@ -0,0 +1,28 @@
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text';
import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
import { embeddedRubyTextNode, normalTextNode } from '../../mock_data';
describe('Render Embedded Ruby Text renderer', () => {
describe('canRender', () => {
it('should return true when the argument `literal` has embedded ruby syntax', () => {
expect(renderer.canRender(embeddedRubyTextNode)).toBe(true);
});
it('should return false when the argument `literal` lacks embedded ruby syntax', () => {
expect(renderer.canRender(normalTextNode)).toBe(false);
});
});
describe('render', () => {
const origin = jest.fn();
it('should return uneditable tokens', () => {
const context = { origin };
expect(renderer.render(embeddedRubyTextNode, context)).toStrictEqual(
buildUneditableTokens(origin()),
);
});
});
});

View File

@ -3,7 +3,7 @@ import {
buildUneditableOpenTokens,
buildUneditableCloseTokens,
buildUneditableTokens,
} from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
import {
identifierTextNode,

View File

@ -31,6 +31,8 @@ RSpec.describe GitlabSchema.types['SentryDetailedError'] do
lastReleaseLastCommit
firstReleaseShortVersion
lastReleaseShortVersion
firstReleaseVersion
lastReleaseVersion
gitlabIssuePath
gitlabCommit
gitlabCommitPath

View File

@ -51,6 +51,20 @@ RSpec.describe CommitsHelper do
end
end
describe '#view_file_button' do
let(:project) { build(:project) }
let(:path) { 'path/to/file' }
let(:sha) { '1234567890' }
subject do
helper.view_file_button(sha, path, project)
end
it 'links to project files' do
expect(subject).to have_link('1234567', href: helper.project_blob_path(project, "#{sha}/#{path}"))
end
end
describe '#view_on_environment_button' do
let(:project) { create(:project) }
let(:environment) { create(:environment, external_url: 'http://example.com') }

View File

@ -303,6 +303,20 @@ RSpec.describe DiffHelper do
end
end
describe '#diff_file_html_data' do
let(:project) { build(:project) }
let(:path) { 'path/to/file' }
let(:sha) { '1234567890' }
subject do
helper.diff_file_html_data(project, path, sha)
end
it 'returns data for project files' do
expect(subject).to include(blob_diff_path: helper.project_blob_diff_path(project, "#{sha}/#{path}"))
end
end
describe '#diff_file_path_text' do
it 'returns full path by default' do
expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path)

View File

@ -3,6 +3,38 @@
require 'spec_helper'
RSpec.describe WikiHelper do
describe '#wiki_page_title' do
let_it_be(:page) { create(:wiki_page) }
it 'sets the title for the show action' do
expect(helper).to receive(:breadcrumb_title).with(page.human_title)
expect(helper).to receive(:wiki_breadcrumb_dropdown_links).with(page.slug)
expect(helper).to receive(:page_title).with(page.human_title, 'Wiki')
expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
helper.wiki_page_title(page)
end
it 'sets the title for a custom action' do
expect(helper).to receive(:breadcrumb_title).with(page.human_title)
expect(helper).to receive(:wiki_breadcrumb_dropdown_links).with(page.slug)
expect(helper).to receive(:page_title).with('Edit', page.human_title, 'Wiki')
expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
helper.wiki_page_title(page, 'Edit')
end
it 'sets the title for an unsaved page' do
expect(page).to receive(:persisted?).and_return(false)
expect(helper).not_to receive(:breadcrumb_title)
expect(helper).not_to receive(:wiki_breadcrumb_dropdown_links)
expect(helper).to receive(:page_title).with('Wiki')
expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
helper.wiki_page_title(page)
end
end
describe '#breadcrumb' do
context 'when the page is at the root level' do
it 'returns the capitalized page name' do

View File

@ -165,125 +165,127 @@ RSpec.describe Gitlab::Danger::Helper do
end
end
describe '#category_for_file' do
where(:path, :expected_category) do
'doc/foo' | :none
'CONTRIBUTING.md' | :none
'LICENSE' | :none
'MAINTENANCE.md' | :none
'PHILOSOPHY.md' | :none
'PROCESS.md' | :none
'README.md' | :none
describe '#categories_for_file' do
where(:path, :expected_categories) do
'doc/foo' | [:none]
'CONTRIBUTING.md' | [:none]
'LICENSE' | [:none]
'MAINTENANCE.md' | [:none]
'PHILOSOPHY.md' | [:none]
'PROCESS.md' | [:none]
'README.md' | [:none]
'ee/doc/foo' | :unknown
'ee/README' | :unknown
'ee/doc/foo' | [:unknown]
'ee/README' | [:unknown]
'app/assets/foo' | :frontend
'app/views/foo' | :frontend
'public/foo' | :frontend
'scripts/frontend/foo' | :frontend
'spec/javascripts/foo' | :frontend
'spec/frontend/bar' | :frontend
'vendor/assets/foo' | :frontend
'babel.config.js' | :frontend
'jest.config.js' | :frontend
'package.json' | :frontend
'yarn.lock' | :frontend
'config/foo.js' | :frontend
'config/deep/foo.js' | :frontend
'app/assets/foo' | [:frontend]
'app/views/foo' | [:frontend]
'public/foo' | [:frontend]
'scripts/frontend/foo' | [:frontend]
'spec/javascripts/foo' | [:frontend]
'spec/frontend/bar' | [:frontend]
'vendor/assets/foo' | [:frontend]
'babel.config.js' | [:frontend]
'jest.config.js' | [:frontend]
'package.json' | [:frontend]
'yarn.lock' | [:frontend]
'config/foo.js' | [:frontend]
'config/deep/foo.js' | [:frontend]
'ee/app/assets/foo' | :frontend
'ee/app/views/foo' | :frontend
'ee/spec/javascripts/foo' | :frontend
'ee/spec/frontend/bar' | :frontend
'ee/app/assets/foo' | [:frontend]
'ee/app/views/foo' | [:frontend]
'ee/spec/javascripts/foo' | [:frontend]
'ee/spec/frontend/bar' | [:frontend]
'app/models/foo' | :backend
'bin/foo' | :backend
'config/foo' | :backend
'lib/foo' | :backend
'rubocop/foo' | :backend
'spec/foo' | :backend
'spec/foo/bar' | :backend
'.gitlab/ci/frontend.gitlab-ci.yml' | %i[frontend engineering_productivity]
'ee/app/foo' | :backend
'ee/bin/foo' | :backend
'ee/spec/foo' | :backend
'ee/spec/foo/bar' | :backend
'app/models/foo' | [:backend]
'bin/foo' | [:backend]
'config/foo' | [:backend]
'lib/foo' | [:backend]
'rubocop/foo' | [:backend]
'spec/foo' | [:backend]
'spec/foo/bar' | [:backend]
'generator_templates/foo' | :backend
'vendor/languages.yml' | :backend
'vendor/licenses.csv' | :backend
'file_hooks/examples/' | :backend
'ee/app/foo' | [:backend]
'ee/bin/foo' | [:backend]
'ee/spec/foo' | [:backend]
'ee/spec/foo/bar' | [:backend]
'Gemfile' | :backend
'Gemfile.lock' | :backend
'Rakefile' | :backend
'FOO_VERSION' | :backend
'generator_templates/foo' | [:backend]
'vendor/languages.yml' | [:backend]
'vendor/licenses.csv' | [:backend]
'file_hooks/examples/' | [:backend]
'Dangerfile' | :engineering_productivity
'danger/commit_messages/Dangerfile' | :engineering_productivity
'ee/danger/commit_messages/Dangerfile' | :engineering_productivity
'danger/commit_messages/' | :engineering_productivity
'ee/danger/commit_messages/' | :engineering_productivity
'.gitlab-ci.yml' | :engineering_productivity
'.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity
'.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity
'scripts/foo' | :engineering_productivity
'lib/gitlab/danger/foo' | :engineering_productivity
'ee/lib/gitlab/danger/foo' | :engineering_productivity
'.overcommit.yml.example' | :engineering_productivity
'.editorconfig' | :engineering_productivity
'tooling/overcommit/foo' | :engineering_productivity
'.codeclimate.yml' | :engineering_productivity
'Gemfile' | [:backend]
'Gemfile.lock' | [:backend]
'Rakefile' | [:backend]
'FOO_VERSION' | [:backend]
'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
'Dangerfile' | [:engineering_productivity]
'danger/commit_messages/Dangerfile' | [:engineering_productivity]
'ee/danger/commit_messages/Dangerfile' | [:engineering_productivity]
'danger/commit_messages/' | [:engineering_productivity]
'ee/danger/commit_messages/' | [:engineering_productivity]
'.gitlab-ci.yml' | [:engineering_productivity]
'.gitlab/ci/cng.gitlab-ci.yml' | [:engineering_productivity]
'.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | [:engineering_productivity]
'scripts/foo' | [:engineering_productivity]
'lib/gitlab/danger/foo' | [:engineering_productivity]
'ee/lib/gitlab/danger/foo' | [:engineering_productivity]
'.overcommit.yml.example' | [:engineering_productivity]
'.editorconfig' | [:engineering_productivity]
'tooling/overcommit/foo' | [:engineering_productivity]
'.codeclimate.yml' | [:engineering_productivity]
'ee/FOO_VERSION' | :unknown
'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | [:backend]
'db/schema.rb' | :database
'db/structure.sql' | :database
'db/migrate/foo' | :database
'db/post_migrate/foo' | :database
'ee/db/migrate/foo' | :database
'ee/db/post_migrate/foo' | :database
'ee/db/geo/migrate/foo' | :database
'ee/db/geo/post_migrate/foo' | :database
'app/models/project_authorization.rb' | :database
'app/services/users/refresh_authorized_projects_service.rb' | :database
'lib/gitlab/background_migration.rb' | :database
'lib/gitlab/background_migration/foo' | :database
'ee/lib/gitlab/background_migration/foo' | :database
'lib/gitlab/database.rb' | :database
'lib/gitlab/database/foo' | :database
'ee/lib/gitlab/database/foo' | :database
'lib/gitlab/github_import.rb' | :database
'lib/gitlab/github_import/foo' | :database
'lib/gitlab/sql/foo' | :database
'rubocop/cop/migration/foo' | :database
'ee/FOO_VERSION' | [:unknown]
'db/fixtures/foo.rb' | :backend
'ee/db/fixtures/foo.rb' | :backend
'db/schema.rb' | [:database]
'db/structure.sql' | [:database]
'db/migrate/foo' | [:database]
'db/post_migrate/foo' | [:database]
'ee/db/migrate/foo' | [:database]
'ee/db/post_migrate/foo' | [:database]
'ee/db/geo/migrate/foo' | [:database]
'ee/db/geo/post_migrate/foo' | [:database]
'app/models/project_authorization.rb' | [:database]
'app/services/users/refresh_authorized_projects_service.rb' | [:database]
'lib/gitlab/background_migration.rb' | [:database]
'lib/gitlab/background_migration/foo' | [:database]
'ee/lib/gitlab/background_migration/foo' | [:database]
'lib/gitlab/database.rb' | [:database]
'lib/gitlab/database/foo' | [:database]
'ee/lib/gitlab/database/foo' | [:database]
'lib/gitlab/github_import.rb' | [:database]
'lib/gitlab/github_import/foo' | [:database]
'lib/gitlab/sql/foo' | [:database]
'rubocop/cop/migration/foo' | [:database]
'qa/foo' | :qa
'ee/qa/foo' | :qa
'db/fixtures/foo.rb' | [:backend]
'ee/db/fixtures/foo.rb' | [:backend]
'changelogs/foo' | :none
'ee/changelogs/foo' | :none
'locale/gitlab.pot' | :none
'qa/foo' | [:qa]
'ee/qa/foo' | [:qa]
'FOO' | :unknown
'foo' | :unknown
'changelogs/foo' | [:none]
'ee/changelogs/foo' | [:none]
'locale/gitlab.pot' | [:none]
'foo/bar.rb' | :backend
'foo/bar.js' | :frontend
'foo/bar.txt' | :none
'foo/bar.md' | :none
'FOO' | [:unknown]
'foo' | [:unknown]
'foo/bar.rb' | [:backend]
'foo/bar.js' | [:frontend]
'foo/bar.txt' | [:none]
'foo/bar.md' | [:none]
end
with_them do
subject { helper.category_for_file(path) }
subject { helper.categories_for_file(path) }
it { is_expected.to eq(expected_category) }
it { is_expected.to eq(expected_categories) }
end
end
@ -296,6 +298,7 @@ RSpec.describe Gitlab::Danger::Helper do
:frontend | '~frontend'
:none | ''
:qa | '~QA'
:engineering_productivity | '~"Engineering Productivity" for CI, Danger'
end
with_them do

View File

@ -191,6 +191,20 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
it { expect(subject).to be_nil }
end
context "when the pre-receive hook fails" do
let(:response) do
Gitaly::UserFFBranchResponse.new(
branch_update: nil,
pre_receive_error: "pre-receive hook error message\n"
)
end
it "raises the error" do
# the PreReceiveError class strips the GL-HOOK-ERR prefix from this error
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "pre-receive hook failed.")
end
end
end
shared_examples 'cherry pick and revert errors' do

View File

@ -220,7 +220,7 @@ ci_pipelines:
- parent_pipeline
- downstream_bridges
- job_artifacts
- vulnerabilities_occurrence_pipelines
- vulnerabilities_finding_pipelines
- vulnerability_findings
- pipeline_config
- security_scans

View File

@ -234,6 +234,7 @@ RSpec.describe Sentry::Client::Issue do
:first_release_short_version | [:firstRelease, :shortVersion]
:last_release_short_version | [:lastRelease, :shortVersion]
:first_release_version | [:firstRelease, :version]
:last_release_version | [:lastRelease, :version]
end
with_them do

View File

@ -864,6 +864,24 @@ RSpec.describe WikiPage do
end
end
describe '#diffs' do
subject { existing_page }
it 'returns a diff instance' do
diffs = subject.diffs(foo: 'bar')
expect(diffs).to be_a(Gitlab::Diff::FileCollection::WikiPage)
expect(diffs.diffable).to be_a(Commit)
expect(diffs.diffable.id).to eq(subject.version.id)
expect(diffs.project).to be(subject.wiki)
expect(diffs.diff_options).to include(
expanded: true,
paths: [subject.path],
foo: 'bar'
)
end
end
private
def get_slugs(page_or_dir)

View File

@ -55,7 +55,12 @@ RSpec.describe 'getting merge request information nested in a project' do
expect(merge_request_graphql_data).to include(
'diffStats' => all(a_hash_including('path' => String, 'additions' => be_natural, 'deletions' => be_natural)),
'diffStatsSummary' => a_hash_including('additions' => be_natural, 'deletions' => be_natural, 'changes' => be_natural)
'diffStatsSummary' => a_hash_including(
'fileCount' => merge_request.diff_stats.count,
'additions' => be_natural,
'deletions' => be_natural,
'changes' => be_natural
)
)
# diff_stats is consistent with summary

View File

@ -104,6 +104,35 @@ RSpec.shared_examples 'wiki controller actions' do
end
end
describe 'GET #diff' do
context 'when commit exists' do
it 'renders the diff' do
get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('shared/wikis/diff')
expect(assigns(:diffs)).to be_a(Gitlab::Diff::FileCollection::Base)
expect(assigns(:diff_notes_disabled)).to be(true)
end
end
context 'when commit does not exist' do
it 'returns a 404 error' do
get :diff, params: routing_params.merge(id: wiki_title, version_id: 'invalid')
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when page does not exist' do
it 'returns a 404 error' do
get :diff, params: routing_params.merge(id: 'invalid')
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET #show' do
render_views