Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-25 03:09:35 +00:00
parent 0a209fd10e
commit 3bd9ad5574
61 changed files with 976 additions and 1104 deletions

View file

@ -238,9 +238,6 @@ gem 'redis-rails', '~> 5.0.2'
# Discord integration
gem 'discordrb-webhooks', '~> 3.4', require: false
# HipChat integration
gem 'hipchat', '~> 1.5.0'
# Jira integration
gem 'jira-ruby', '~> 2.1.4'
gem 'atlassian-jwt', '~> 0.2.0'

View file

@ -593,9 +593,6 @@ GEM
railties (>= 5.0)
heapy (0.2.0)
thor
hipchat (1.5.2)
httparty
mimemagic
html-pipeline (2.13.2)
activesupport (>= 2)
nokogiri (>= 1.4)
@ -1456,7 +1453,6 @@ DEPENDENCIES
hashie
hashie-forbidden_attributes
health_check (~> 3.0)
hipchat (~> 1.5.0)
html-pipeline (~> 2.13.2)
html2text
httparty (~> 0.16.4)

View file

@ -0,0 +1,253 @@
<script>
import { GlForm, GlIcon, GlLink, GlButton, GlSprintf } from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import { setUrlFragment } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
const MARKDOWN_LINK_TEXT = {
markdown: '[Link Title](page-slug)',
rdoc: '{Link title}[link:page-slug]',
asciidoc: 'link:page-slug[Link title]',
org: '[[page-slug]]',
};
export default {
components: {
GlForm,
GlSprintf,
GlIcon,
GlLink,
GlButton,
MarkdownField,
},
inject: ['formatOptions', 'pageInfo'],
data() {
return {
title: this.pageInfo.title?.trim() || '',
format: this.pageInfo.format || 'markdown',
content: this.pageInfo.content?.trim() || '',
commitMessage: '',
};
},
computed: {
csrfToken() {
return csrf.token;
},
formAction() {
return this.pageInfo.persisted ? this.pageInfo.path : this.pageInfo.createPath;
},
helpPath() {
return setUrlFragment(
this.pageInfo.helpPath,
this.pageInfo.persisted ? 'moving-a-wiki-page' : 'creating-a-new-wiki-page',
);
},
commitMessageI18n() {
return this.pageInfo.persisted
? s__('WikiPage|Update %{pageTitle}')
: s__('WikiPage|Create %{pageTitle}');
},
linkExample() {
return MARKDOWN_LINK_TEXT[this.format];
},
submitButtonText() {
if (this.pageInfo.persisted) return __('Save changes');
return s__('WikiPage|Create page');
},
cancelFormPath() {
if (this.pageInfo.persisted) return this.pageInfo.path;
return this.pageInfo.wikiPath;
},
wikiSpecificMarkdownHelpPath() {
return setUrlFragment(this.pageInfo.markdownHelpPath, 'wiki-specific-markdown');
},
},
mounted() {
this.updateCommitMessage();
},
methods: {
handleFormSubmit() {
window.removeEventListener('beforeunload', this.onBeforeUnload);
},
handleContentChange() {
window.addEventListener('beforeunload', this.onBeforeUnload);
},
onBeforeUnload() {
return '';
},
updateCommitMessage() {
if (!this.title) return;
// Replace hyphens with spaces
const newTitle = this.title.replace(/-+/g, ' ');
const newCommitMessage = sprintf(this.commitMessageI18n, { pageTitle: newTitle }, false);
this.commitMessage = newCommitMessage;
},
},
};
</script>
<template>
<gl-form
:action="formAction"
method="post"
class="wiki-form common-note-form gl-mt-3 js-quick-submit"
@submit="handleFormSubmit"
>
<input :value="csrfToken" type="hidden" name="authenticity_token" />
<input v-if="pageInfo.persisted" type="hidden" name="_method" value="put" />
<input
:v-if="pageInfo.persisted"
type="hidden"
name="wiki[last_commit_sha]"
:value="pageInfo.lastCommitSha"
/>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_title">{{ s__('WikiPage|Title') }}</label>
</div>
<div class="col-sm-10">
<input
id="wiki_title"
v-model.trim="title"
name="wiki[title]"
type="text"
class="form-control"
data-qa-selector="wiki_title_textbox"
:required="true"
:autofocus="!pageInfo.persisted"
:placeholder="s__('WikiPage|Page title')"
@input="updateCommitMessage"
/>
<span class="gl-display-inline-block gl-max-w-full gl-mt-2 gl-text-gray-600">
<gl-icon class="gl-mr-n1" name="bulb" />
{{
pageInfo.persisted
? s__(
'WikiPage|Tip: You can move this page by adding the path to the beginning of the title.',
)
: s__(
'WikiPage|Tip: You can specify the full path for the new file. We will automatically create any missing directories.',
)
}}
<gl-link :href="helpPath" target="_blank" data-testid="wiki-title-help-link"
><gl-icon name="question-o" /> {{ __('More Information.') }}</gl-link
>
</span>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_format">{{
s__('WikiPage|Format')
}}</label>
</div>
<div class="col-sm-10">
<select id="wiki_format" v-model="format" class="form-control" name="wiki[format]">
<option v-for="(key, label) of formatOptions" :key="key" :value="key">
{{ label }}
</option>
</select>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_content">{{
s__('WikiPage|Content')
}}</label>
</div>
<div class="col-sm-10">
<markdown-field
:markdown-preview-path="pageInfo.markdownPreviewPath"
:can-attach-file="true"
:enable-autocomplete="true"
:textarea-value="content"
:markdown-docs-path="pageInfo.markdownHelpPath"
:uploads-path="pageInfo.uploadsPath"
class="bordered-box"
>
<template #textarea>
<textarea
id="wiki_content"
ref="textarea"
v-model.trim="content"
name="wiki[content]"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
data-qa-selector="wiki_content_textarea"
:autofocus="pageInfo.persisted"
:aria-label="s__('WikiPage|Content')"
:placeholder="s__('WikiPage|Write your content or drag files here…')"
@input="handleContentChange"
>
</textarea>
</template>
</markdown-field>
<div class="clearfix"></div>
<div class="error-alert"></div>
<div class="form-text gl-text-gray-600">
<gl-sprintf
:message="
s__(
'WikiPage|To link to a (new) page, simply type %{linkExample}. More examples are in the %{linkStart}documentation%{linkEnd}.',
)
"
>
<template #linkExample
><code>{{ linkExample }}</code></template
>
<template
#link="// eslint-disable-next-line vue/no-template-shadow
{ content }"
><gl-link
:href="wikiSpecificMarkdownHelpPath"
target="_blank"
data-testid="wiki-markdown-help-link"
>{{ content }}</gl-link
></template
>
</gl-sprintf>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_message">{{
s__('WikiPage|Commit message')
}}</label>
</div>
<div class="col-sm-10">
<input
id="wiki_message"
v-model.trim="commitMessage"
name="wiki[message]"
type="text"
class="form-control"
data-qa-selector="wiki_message_textbox"
:placeholder="s__('WikiPage|Commit message')"
/>
</div>
</div>
<div class="form-actions">
<gl-button
category="primary"
variant="confirm"
type="submit"
data-qa-selector="wiki_submit_button"
data-testid="wiki-submit-button"
:disabled="!content || !title"
>{{ submitButtonText }}</gl-button
>
<gl-button :href="cancelFormPath" class="float-right" data-testid="wiki-cancel-button">{{
__('Cancel')
}}</gl-button>
</div>
</gl-form>
</template>

View file

@ -1,12 +1,14 @@
import $ from 'jquery';
import Vue from 'vue';
import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import Translate from '~/vue_shared/translate';
import GLForm from '../../../gl_form';
import ZenMode from '../../../zen_mode';
import deleteWikiModal from './components/delete_wiki_modal.vue';
import wikiAlert from './components/wiki_alert.vue';
import wikiForm from './components/wiki_form.vue';
import Wikis from './wikis';
const createModalVueApp = () => {
@ -61,7 +63,28 @@ const createAlertVueApp = () => {
}
};
const createWikiFormApp = () => {
const el = document.getElementById('js-wiki-form');
if (el) {
const { pageInfo, formatOptions } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
provide: {
formatOptions: JSON.parse(formatOptions),
pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)),
},
render(createElement) {
return createElement(wikiForm);
},
});
}
};
export default () => {
createModalVueApp();
createAlertVueApp();
createWikiFormApp();
};

View file

@ -1,15 +1,7 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { s__, sprintf } from '~/locale';
import Tracking from '~/tracking';
import showToast from '~/vue_shared/plugins/global_toast';
const MARKDOWN_LINK_TEXT = {
markdown: '[Link Title](page-slug)',
rdoc: '{Link title}[link:page-slug]',
asciidoc: 'link:page-slug[Link title]',
org: '[[page-slug]]',
};
const TRACKING_EVENT_NAME = 'view_wiki_page';
const TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/wiki_page_context/jsonschema/1-0-1';
@ -23,78 +15,11 @@ export default class Wikis {
sidebarToggles[i].addEventListener('click', (e) => this.handleToggleSidebar(e));
}
this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page'));
this.editTitleInput = document.querySelector('form.wiki-form #wiki_title');
this.commitMessageInput = document.querySelector('form.wiki-form #wiki_message');
this.submitButton = document.querySelector('.js-wiki-btn-submit');
this.commitMessageI18n = this.isNewWikiPage
? s__('WikiPageCreate|Create %{pageTitle}')
: s__('WikiPageEdit|Update %{pageTitle}');
if (this.editTitleInput) {
// Initialize the commit message on load
if (this.editTitleInput.value) this.setWikiCommitMessage(this.editTitleInput.value);
// Set the commit message as the page title is changed
this.editTitleInput.addEventListener('keyup', (e) => this.handleWikiTitleChange(e));
}
window.addEventListener('resize', () => this.renderSidebar());
this.renderSidebar();
const changeFormatSelect = document.querySelector('#wiki_format');
const linkExample = document.querySelector('.js-markup-link-example');
if (changeFormatSelect) {
changeFormatSelect.addEventListener('change', (e) => {
linkExample.innerHTML = MARKDOWN_LINK_TEXT[e.target.value];
});
}
this.wikiTextarea = document.querySelector('form.wiki-form #wiki_content');
const wikiForm = document.querySelector('form.wiki-form');
if (this.wikiTextarea) {
this.wikiTextarea.addEventListener('input', () => this.handleWikiContentChange());
wikiForm.addEventListener('submit', () => {
window.onbeforeunload = null;
});
}
Wikis.trackPageView();
Wikis.showToasts();
this.updateSubmitButton();
}
handleWikiContentChange() {
this.updateSubmitButton();
window.onbeforeunload = () => '';
}
handleWikiTitleChange(e) {
this.updateSubmitButton();
this.setWikiCommitMessage(e.target.value);
}
updateSubmitButton() {
if (!this.wikiTextarea) return;
const isEnabled = Boolean(this.wikiTextarea.value.trim() && this.editTitleInput.value.trim());
if (isEnabled) this.submitButton.removeAttribute('disabled');
else this.submitButton.setAttribute('disabled', 'true');
}
setWikiCommitMessage(rawTitle) {
let title = rawTitle;
// Replace hyphens with spaces
if (title) title = title.replace(/-+/g, ' ');
const newCommitMessage = sprintf(this.commitMessageI18n, { pageTitle: title }, false);
this.commitMessageInput.value = newCommitMessage;
}
handleToggleSidebar(e) {

View file

@ -39,7 +39,7 @@ function formatTooltipText({ date, count }) {
if (count > 0) {
contribText = n__('%d contribution', '%d contributions', count);
}
return `${contribText}<br />${dateDayName} ${dateText}`;
return `${contribText}<br /><span class="gl-text-gray-300">${dateDayName} ${dateText}</span>`;
}
const initColorKey = () => d3.scaleLinear().range(['#acd5f2', '#254e77']).domain([0, 3]);

View file

@ -62,6 +62,11 @@ export default {
required: false,
default: true,
},
uploadsPath: {
type: String,
required: false,
default: '',
},
enableAutocomplete: {
type: Boolean,
required: false,
@ -229,6 +234,7 @@ export default {
ref="gl-form"
:class="{ 'gl-mt-3 gl-mb-3': addSpacingClasses }"
class="js-vue-markdown-field md-area position-relative gfm-form"
:data-uploads-path="uploadsPath"
>
<markdown-header
:preview-markdown="previewMarkdown"

View file

@ -191,7 +191,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/63107')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/29418')
end
def application_setting_params

View file

@ -43,7 +43,7 @@ class Import::GitlabProjectsController < Import::BaseController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42437')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20823')
end
def uploader_class

View file

@ -116,6 +116,6 @@ class Import::ManifestController < Import::BaseController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/48939')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/23147')
end
end

View file

@ -119,7 +119,7 @@ class Projects::ForksController < Projects::ApplicationController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20783')
end
def load_namespaces_with_associations

View file

@ -356,10 +356,10 @@ class Projects::IssuesController < Projects::ApplicationController
def disable_query_limiting
# Also see the following issues:
#
# 1. https://gitlab.com/gitlab-org/gitlab-foss/issues/42423
# 2. https://gitlab.com/gitlab-org/gitlab-foss/issues/42424
# 3. https://gitlab.com/gitlab-org/gitlab-foss/issues/42426
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42422')
# 1. https://gitlab.com/gitlab-org/gitlab/-/issues/20815
# 2. https://gitlab.com/gitlab-org/gitlab/-/issues/20816
# 3. https://gitlab.com/gitlab-org/gitlab/-/issues/21068
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20814')
end
private

View file

@ -134,7 +134,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
# rubocop: enable CodeReuse/ActiveRecord
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42384')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20801')
end
def incr_count_webide_merge_request

View file

@ -470,8 +470,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def disable_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-foss/issues/42441
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42438')
# Also see https://gitlab.com/gitlab-org/gitlab/-/issues/20827
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20824')
end
def reports_response(report_comparison, pipeline = nil)

View file

@ -43,6 +43,6 @@ class Projects::NetworkController < Projects::ApplicationController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42333')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20782')
end
end

View file

@ -88,6 +88,6 @@ class Projects::NotesController < Projects::ApplicationController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42383')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20800')
end
end

View file

@ -272,8 +272,8 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def disable_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-foss/issues/42343
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42339')
# Also see https://gitlab.com/gitlab-org/gitlab/-/issues/20785
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20784')
end
def authorize_update_pipeline!

View file

@ -163,7 +163,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def disable_query_limiting
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42380')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20798')
end
def load_recaptcha

View file

@ -11,7 +11,7 @@ module Mutations
description: 'The project to move the issue to.'
def resolve(project_path:, iid:, target_project_path:)
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/267762')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20816')
issue = authorized_find!(project_path: project_path, iid: iid)
source_project = issue.project

View file

@ -42,7 +42,7 @@ module Mutations
description: 'Squash commits on the source branch before merge.'
def resolve(project_path:, iid:, **args)
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42317')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796')
merge_request = authorized_find!(project_path: project_path, iid: iid)
project = merge_request.target_project

View file

@ -52,12 +52,8 @@ class HipchatService < Service
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
message = create_message(data)
return unless message.present?
gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend
# We removed the hipchat gem due to https://gitlab.com/gitlab-org/gitlab/-/issues/325851#note_537143149
# HipChat is unusable anyway, so do nothing in this method
end
def test(data)
@ -72,71 +68,14 @@ class HipchatService < Service
private
def gate
options = { api_version: api_version.presence || 'v2' }
options[:server_url] = server unless server.blank?
@gate ||= HipChat::Client.new(token, options)
end
def message_options(data = nil)
{ notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
end
def create_message(data)
object_kind = data[:object_kind]
case object_kind
when "push", "tag_push"
create_push_message(data)
when "issue"
create_issue_message(data) unless update?(data)
when "merge_request"
create_merge_request_message(data) unless update?(data)
when "note"
create_note_message(data)
when "pipeline"
create_pipeline_message(data) if should_pipeline_be_notified?(data)
end
end
def render_line(text)
markdown(text.lines.first.chomp, pipeline: :single_line) if text
end
def create_push_message(push)
ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
ref = Gitlab::Git.ref_name(push[:ref])
before = push[:before]
after = push[:after]
message = []
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
message << "pushed new #{ref_type} <a href=\""\
"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
" to #{project_link}\n"
elsif Gitlab::Git.blank_ref?(after)
message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
else
message << "pushed to #{ref_type} <a href=\""\
"#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
push[:commits].take(MAX_COMMITS).each do |commit|
message << "<br /> - #{render_line(commit[:message])} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
end
if push[:commits].count > MAX_COMMITS
message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
end
end
message.join
end
def markdown(text, options = {})
return "" unless text
@ -155,109 +94,10 @@ class HipchatService < Service
sanitized_html.truncate(200, separator: ' ', omission: '...')
end
def create_issue_message(data)
user_name = data[:user][:name]
obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
title = render_line(obj_attr[:title])
state = Issue.available_states.key(obj_attr[:state_id])
issue_iid = obj_attr[:iid]
issue_url = obj_attr[:url]
description = obj_attr[:description]
issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
message.join
end
def create_merge_request_message(data)
user_name = data[:user][:name]
obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
merge_request_id = obj_attr[:iid]
state = obj_attr[:state]
description = obj_attr[:description]
title = render_line(obj_attr[:title])
merge_request_url = "#{project_url}/-/merge_requests/#{merge_request_id}"
merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
message = ["#{user_name} #{state} #{merge_request_link} in " \
"#{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
message.join
end
def format_title(title)
"<b>#{render_line(title)}</b>"
end
def create_note_message(data)
data = HashWithIndifferentAccess.new(data)
user_name = data[:user][:name]
obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
note = obj_attr[:note]
note_url = obj_attr[:url]
noteable_type = obj_attr[:noteable_type]
commit_id = nil
case noteable_type
when "Commit"
commit_attr = HashWithIndifferentAccess.new(data[:commit])
commit_id = commit_attr[:id]
subject_desc = commit_id
subject_desc = Commit.truncate_sha(subject_desc)
subject_type = "commit"
title = format_title(commit_attr[:message])
when "Issue"
subj_attr = HashWithIndifferentAccess.new(data[:issue])
subject_id = subj_attr[:iid]
subject_desc = "##{subject_id}"
subject_type = "issue"
title = format_title(subj_attr[:title])
when "MergeRequest"
subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
subject_id = subj_attr[:iid]
subject_desc = "!#{subject_id}"
subject_type = "merge request"
title = format_title(subj_attr[:title])
when "Snippet"
subj_attr = HashWithIndifferentAccess.new(data[:snippet])
subject_id = subj_attr[:id]
subject_desc = "##{subject_id}"
subject_type = "snippet"
title = format_title(subj_attr[:title])
end
subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
message << title
message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
message.join
end
def create_pipeline_message(data)
pipeline_attributes = data[:object_attributes]
pipeline_id = pipeline_attributes[:id]
ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
ref = pipeline_attributes[:ref]
user_name = (data[:user] && data[:user][:name]) || 'API'
status = pipeline_attributes[:status]
duration = pipeline_attributes[:duration]
branch_link = "<a href=\"#{project_url}/-/commits/#{CGI.escape(ref)}\">#{ref}</a>"
pipeline_url = "<a href=\"#{project_url}/-/pipelines/#{pipeline_id}\">##{pipeline_id}</a>"
"#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
def message_color(data)
pipeline_status_color(data) || color || 'yellow'
end
@ -309,5 +149,3 @@ class HipchatService < Service
end
end
end
HipchatService.prepend_if_ee('EE::HipchatService')

View file

@ -48,7 +48,7 @@ class PostReceiveService
end
def process_mr_push_options(push_options, changes)
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/61359')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/28494')
return unless repository
unless repository.repo_type.project?

View file

@ -1,79 +1,6 @@
- form_classes = %w[wiki-form common-note-form gl-mt-3 js-quick-submit]
- page_info = { last_commit_sha: @page.last_commit_sha, persisted: @page.persisted?, title: @page.title, content: @page.content || '', format: @page.format.to_s, uploads_path: uploads_path, path: wiki_page_path(@wiki, @page), wiki_path: wiki_path(@wiki), help_path: help_page_path('user/project/wiki/index'), markdown_help_path: help_page_path('user/markdown'), markdown_preview_path: wiki_page_path(@wiki, @page, action: :preview_markdown), create_path: wiki_path(@wiki, action: :create) }
- if @page.persisted?
- form_action = wiki_page_path(@wiki, @page)
- form_method = :put
- else
- form_action = wiki_path(@wiki, action: :create)
- form_method = :post
- form_classes << 'js-new-wiki-page'
= form_for @page, url: form_action, method: form_method,
html: { class: form_classes },
data: { uploads_path: uploads_path } do |f|
.gl-mt-3
= form_errors(@page, truncate: :title)
- if @page.persisted?
= f.hidden_field :last_commit_sha, value: @page.last_commit_sha
.form-group.row
.col-sm-2.col-form-label= f.label :title, class: 'control-label-full-width'
.col-sm-10
= f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: s_('Wiki|Page title')
%span.gl-display-inline-block.gl-max-w-full.gl-mt-2.gl-text-gray-600
= sprite_icon('bulb', size: 12, css_class: 'gl-mr-n1')
- if @page.persisted?
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
= link_to sprite_icon('question-o'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'),
target: '_blank', rel: 'noopener noreferrer'
- else
= s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
= succeed '.' do
= link_to _('More information'), help_page_path('user/project/wiki/index', anchor: 'creating-a-new-wiki-page'),
target: '_blank', rel: 'noopener noreferrer'
.form-group.row
.col-sm-2.col-form-label= f.label :format, class: 'control-label-full-width'
.col-sm-10
.select-wrapper
= f.select :format, options_for_select(Wiki::MARKUPS, {selected: @page.format}), {}, class: 'form-control select-control'
= sprite_icon('chevron-down', css_class: 'gl-absolute gl-top-3 gl-right-3 gl-text-gray-200')
.form-group.row
.col-sm-2.col-form-label= f.label :content, class: 'control-label-full-width'
.col-sm-10
= render layout: 'shared/md_preview', locals: { url: wiki_page_path(@wiki, @page, action: :preview_markdown) } do
= render 'shared/zen', f: f, attr: :content, classes: 'note-textarea qa-wiki-content-textarea', placeholder: s_("WikiPage|Write your content or drag files here…"), autofocus: @page.persisted?
= render 'shared/notes/hints'
.clearfix
.error-alert
.form-text.gl-text-gray-600
= succeed '.' do
- case @page.format.to_s
- when 'rdoc'
- link_example = '{Link title}[link:page-slug]'
- when 'asciidoc'
- link_example = 'link:page-slug[Link title]'
- when 'org'
- link_example = '[[page-slug]]'
- else
- link_example = '[Link Title](page-slug)'
= html_escape(s_('WikiMarkdownTip|To link to a (new) page, simply type %{link_example}')) % { link_example: tag.code(link_example, class: 'js-markup-link-example') }
= succeed '.' do
- markdown_link = link_to s_("WikiMarkdownDocs|documentation"), help_page_path('user/markdown', anchor: 'wiki-specific-markdown')
= (s_("WikiMarkdownDocs|More examples are in the %{docs_link}") % { docs_link: markdown_link }).html_safe
.form-group.row
.col-sm-2.col-form-label= f.label :commit_message, class: 'control-label-full-width'
.col-sm-10= f.text_field :message, class: 'form-control qa-wiki-message-textbox', rows: 18, value: nil
.form-actions
- if @page && @page.persisted?
= f.submit _("Save changes"), class: 'btn gl-button btn-confirm qa-save-changes-button js-wiki-btn-submit', disabled: 'true'
.float-right
= link_to _("Cancel"), wiki_page_path(@wiki, @page), class: 'btn gl-button btn-cancel btn-default'
- else
= f.submit s_("Wiki|Create page"), class: 'btn-confirm gl-button btn qa-create-page-button rspec-create-page-button js-wiki-btn-submit', disabled: 'true'
.float-right
= link_to _("Cancel"), wiki_path(@wiki), class: 'btn gl-button btn-cancel btn-default'
#js-wiki-form{ data: { page_info: page_info.to_json, format_options: Wiki::MARKUPS.to_json } }

View file

@ -0,0 +1,5 @@
---
title: Add message for repository backup skip
merge_request: 54285
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Make HipChat project service do nothing
merge_request: 57434
author:
type: removed

View file

@ -0,0 +1,5 @@
---
title: Add multi-line styling within contribution tooltip
merge_request: 54765
author: Yogi (@yo)
type: changed

View file

@ -1,15 +0,0 @@
# frozen_string_literal: true
# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
module HipChat
class Client
connection_adapter ::Gitlab::HTTPConnectionAdapter
end
class Room
connection_adapter ::Gitlab::HTTPConnectionAdapter
end
class User
connection_adapter ::Gitlab::HTTPConnectionAdapter
end
end

View file

@ -89,7 +89,7 @@ _The artifacts are stored by default in
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1762) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
> - Since version 9.5, artifacts are [browsable](../ci/pipelines/job_artifacts.md#browsing-artifacts),
> - Since version 9.5, artifacts are [browsable](../ci/pipelines/job_artifacts.md#download-job-artifacts),
> when object storage is enabled. 9.4 lacks this feature.
> - Since version 10.6, available in [GitLab Free](https://about.gitlab.com/pricing/).
> - Since version 11.0, we support `direct_upload` to S3.
@ -509,7 +509,7 @@ If you need to manually remove job artifacts associated with multiple jobs while
NOTE:
This step also erases artifacts that users have chosen to
["keep"](../ci/pipelines/job_artifacts.md#browsing-artifacts).
["keep"](../ci/pipelines/job_artifacts.md#download-job-artifacts).
```ruby
builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago)
@ -583,3 +583,21 @@ If you need to manually remove **all** job artifacts associated with multiple jo
- `7.days.ago`
- `3.months.ago`
- `1.year.ago`
### Error `Downloading artifacts from coordinator... not found`
When a job tries to download artifacts from an earlier job, you might receive an error similar to:
```plaintext
Downloading artifacts from coordinator... not found id=12345678 responseStatus=404 Not Found
```
This might be caused by a `gitlab.rb` file with the following configuration:
```ruby
gitlab_rails['artifacts_object_store_background_upload'] = false
gitlab_rails['artifacts_object_store_direct_upload'] = true
```
To prevent this, comment out or remove those lines, or switch to their [default values](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template),
then run `sudo gitlab-ctl reconfigure`.

View file

@ -487,7 +487,7 @@ internet connectivity is gated by a proxy. To use a proxy for GitLab Pages:
### Using a custom Certificate Authority (CA)
When using certificates issued by a custom CA, [Access Control](../../user/project/pages/pages_access_control.md#gitlab-pages-access-control) and
the [online view of HTML job artifacts](../../ci/pipelines/job_artifacts.md#browsing-artifacts)
the [online view of HTML job artifacts](../../ci/pipelines/job_artifacts.md#download-job-artifacts)
fails to work if the custom CA is not recognized.
This usually results in this error:

View file

@ -8,25 +8,19 @@ type: reference, howto
# Job artifacts
> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`, and it's now possible to browse its contents, with the added ability of downloading the files separately.
> - In GitLab 8.17, builds were renamed to jobs.
> - The artifacts browser is available only for new artifacts that are sent to GitLab using GitLab Runner version 1.0 and up. You cannot browse old artifacts already uploaded to GitLab.
> Introduced in [GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16675), artifacts in internal and private projects can be previewed when [GitLab Pages access control](../../administration/pages/index.md#access-control) is enabled.
Job artifacts are a list of files and directories created by a job
once it finishes. This feature is [enabled by default](../../administration/job_artifacts.md) in all
GitLab installations.
Jobs can output an archive of files and directories. This output is known as a job artifact.
Job artifacts created by GitLab Runner are uploaded to GitLab and are downloadable as a single archive using the GitLab UI or the [GitLab API](../../api/job_artifacts.md#get-job-artifacts).
You can download job artifacts by using the GitLab UI or the [API](../../api/job_artifacts.md#get-job-artifacts).
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, watch the video [GitLab CI Pipeline, Artifacts, and Environments](https://www.youtube.com/watch?v=PCKDICEe10s).
Watch also [GitLab CI pipeline tutorial for beginners](https://www.youtube.com/watch?v=Jav4vbUrqII).
## Defining artifacts in `.gitlab-ci.yml`
## Define artifacts in the `.gitlab-ci.yml` file
A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
the following:
This example shows how to configure your `.gitlab-ci.yml` file to create job artifacts:
```yaml
pdf:
@ -38,19 +32,169 @@ pdf:
```
A job named `pdf` calls the `xelatex` command to build a PDF file from the
latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
are relative to the repository that was cloned during the build.
LaTeX source file, `mycv.tex`.
By default, the artifacts upload when the job succeeds. You can also set artifacts to upload
when the job fails, or always, by using [`artifacts:when`](../yaml/README.md#artifactswhen)
keyword. GitLab keeps these uploaded artifacts for 1 week, as defined
by the `expire_in` definition. You can keep the artifacts from expiring
via the [web interface](#browsing-artifacts). If the expiry time is not defined, it defaults
to the [instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration).
The `paths` keyword determines which files to add to the job artifacts.
All paths to files and directories are relative to the repository where the job was created.
For more examples on artifacts, follow the [artifacts reference in
`.gitlab-ci.yml`](../yaml/README.md#artifacts).
The `expire_in` keyword determines how long GitLab keeps the job artifacts.
You can also [use the UI to keep job artifacts from expiring](#download-job-artifacts).
If `expire_in` is not defined, the
[instance-wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration)
is used.
For more examples, view the [keyword reference for the `.gitlab-ci.yml` file](../yaml/README.md#artifacts).
## Download job artifacts
You can download job artifacts or view the job archive:
- On the **Pipelines** page, to the right of the pipeline:
![Job artifacts in Pipelines page](img/job_artifacts_pipelines_page.png)
- On the **Jobs** page, to the right of the job:
![Job artifacts in Builds page](img/job_artifacts_builds_page.png)
- On a job's detail page. The **Keep** button indicates an `expire_in` value was set:
![Job artifacts browser button](img/job_artifacts_browser_button.png)
- On a merge request, by the pipeline details:
![Job artifacts in Merge Request](img/job_artifacts_merge_request.png)
- When browsing an archive:
![Job artifacts browser](img/job_artifacts_browser.png)
If [GitLab Pages](../../administration/pages/index.md) is enabled in the project, you can preview
HTML files in the artifacts directly in your browser. If the project is internal or private, you must
enable [GitLab Pages access control](../../administration/pages/index.md#access-control) to preview
HTML files.
## View failed job artifacts
If the latest job has failed to upload the artifacts, you can see that
information in the UI.
![Latest artifacts button](img/job_latest_artifacts_browser.png)
## Erase job artifacts
WARNING:
This is a destructive action that leads to data loss. Use with caution.
You can erase a single job, which also removes the job's
artifacts and log. You must be:
- The owner of the job.
- A [Maintainer](../../user/permissions.md#gitlab-cicd-permissions) of the project.
To erase a job:
1. Go to a job's detail page.
1. At the top right of the job's log, select the trash icon.
1. Confirm the deletion.
## Use GitLab CI/CD to retrieve job artifacts for private projects
To retrieve a job artifact from a different project, you might need to use a
private token to [authenticate and download](../../api/job_artifacts.md#get-job-artifacts)
the artifact.
## The latest job artifacts
Job artifacts created in the most recent successful pipeline for a specific ref
are considered the latest artifacts. If you run two types of pipelines for the same ref,
timing determines which artifacts are the latest.
For example, if a merge request creates a branch pipeline at the same time as
a scheduled pipeline, the pipeline that finished most recently creates the latest job artifact.
In [GitLab 13.5](https://gitlab.com/gitlab-org/gitlab/-/issues/201784) and later, artifacts
for [parent and child pipelines](../parent_child_pipelines.md) are searched in hierarchical
order from parent to child. For example, if both parent and child pipelines have a
job with the same name, the job artifact from the parent pipeline is returned.
## Access the latest job artifacts by URL
You can download the latest job artifacts by using a URL.
To download the whole artifacts archive:
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/download?job=<job_name>
```
To download a single file from the artifacts:
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path_to_file>?job=<job_name>
```
For example, to download the latest artifacts of the job named `coverage` of
the `master` branch of the `gitlab` project that belongs to the `gitlab-org`
namespace:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/download?job=coverage
```
To download the file `coverage/index.html` from the same artifacts:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/raw/coverage/index.html?job=coverage
```
To browse the latest job artifacts:
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/browse?job=<job_name>
```
For example:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/browse?job=coverage
```
There is also a URL for specific files, including HTML files that
are shown in [GitLab Pages](../../administration/pages/index.md):
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/file/<path>?job=<job_name>
```
For example, when a job `coverage` creates the artifact `htmlcov/index.html`:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/file/htmlcov/index.html?job=coverage
```
### Keep artifacts from most recent successful jobs
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16267) in GitLab 13.0.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/229936) in GitLab 13.4.
> - [Made optional with a CI/CD setting](https://gitlab.com/gitlab-org/gitlab/-/issues/241026) in GitLab 13.8.
By default, the latest job artifacts from the most recent successful jobs are never deleted.
If a job is configured with [`expire_in`](../yaml/README.md#artifactsexpire_in),
its artifacts only expire if a more recent artifact exists.
Keeping the latest artifacts can use a large amount of storage space in projects
with a lot of jobs or large artifacts. If the latest artifacts are not needed in
a project, you can disable this behavior to save space:
1. Go to the project's **Settings > CI/CD > Artifacts**.
1. Clear the **Keep artifacts from most recent successful jobs** checkbox.
You can disable this behavior for all projects on a self-managed instance in the
[instance's CI/CD settings](../../user/admin_area/settings/continuous_integration.md#keep-the-latest-artifacts-for-all-jobs-in-the-latest-successful-pipelines).
When you disable the feature, the latest artifacts do not immediately expire.
A new pipeline must run before the latest artifacts can expire and be deleted.
### `artifacts:reports`
@ -306,191 +450,7 @@ The collected Requirements report uploads to GitLab as an artifact and
existing [requirements](../../user/project/requirements/index.md) are
marked as Satisfied.
## Browsing artifacts
> - From GitLab 9.2, PDFs, images, videos, and other formats can be previewed directly in the job artifacts browser without the need to download them.
> - Introduced in [GitLab 10.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14399), HTML files in a public project can be previewed directly in a new tab without the need to download them when [GitLab Pages](../../administration/pages/index.md) is enabled. The same applies for textual formats (currently supported extensions: `.txt`, `.json`, and `.log`).
> - Introduced in [GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16675), artifacts in internal and private projects can be previewed when [GitLab Pages access control](../../administration/pages/index.md#access-control) is enabled.
After a job finishes, if you visit the job's specific page, there are three
buttons. You can download the artifacts archive or browse its contents, whereas
the **Keep** button appears only if you've set an [expiry date](../yaml/README.md#artifactsexpire_in) to the
artifacts in case you changed your mind and want to keep them.
![Job artifacts browser button](img/job_artifacts_browser_button.png)
The archive browser shows the name and the actual file size of each file in the
archive. If your artifacts contained directories, then you're also able to
browse inside them.
Below you can see what browsing looks like. In this case we have browsed inside
the archive and at this point there is one directory, a couple files, and
one HTML file that you can view directly online when
[GitLab Pages](../../administration/pages/index.md) is enabled (opens in a new tab).
Select artifacts in internal and private projects can only be previewed when
[GitLab Pages access control](../../administration/pages/index.md#access-control) is enabled.
![Job artifacts browser](img/job_artifacts_browser.png)
## Downloading artifacts
If you need to download an artifact or the whole archive, there are buttons in various places
in the GitLab UI to do this:
1. While on the pipelines page, you can see the download icon for each job's
artifacts and archive in the right corner:
![Job artifacts in Pipelines page](img/job_artifacts_pipelines_page.png)
1. While on the **Jobs** page, you can see the download icon for each job's
artifacts and archive in the right corner:
![Job artifacts in Builds page](img/job_artifacts_builds_page.png)
1. While inside a specific job, you're presented with a download button
along with the one that browses the archive:
![Job artifacts browser button](img/job_artifacts_browser_button.png)
1. While on the details page of a merge request, you can see the download
icon for each job's artifacts on the right side of the merge request widget:
![Job artifacts in Merge Request](img/job_artifacts_merge_request.png)
1. And finally, when browsing an archive you can see the download button at
the top right corner:
![Job artifacts browser](img/job_artifacts_browser.png)
## Downloading the latest artifacts
It's possible to download the latest artifacts of a job via a well known URL
so you can use it for scripting purposes.
NOTE:
The latest artifacts are created by jobs in the **most recent** successful pipeline
for the specific ref. If you run two types of pipelines for the same ref, timing determines the latest
artifact. For example, if a merge request creates a branch pipeline at the same time as a scheduled pipeline, the pipeline that completed most recently creates the latest artifact.
In [GitLab 13.5](https://gitlab.com/gitlab-org/gitlab/-/issues/201784) and later, artifacts
for [parent and child pipelines](../parent_child_pipelines.md) are searched in hierarchical
order from parent to child. For example, if both parent and child pipelines have a
job with the same name, the artifact from the parent pipeline is returned.
Artifacts for other pipelines can be accessed with direct access to them.
The structure of the URL to download the whole artifacts archive is the following:
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/download?job=<job_name>
```
To download a single file from the artifacts use the following URL:
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path_to_file>?job=<job_name>
```
For example, to download the latest artifacts of the job named `coverage` of
the `master` branch of the `gitlab` project that belongs to the `gitlab-org`
namespace, the URL would be:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/download?job=coverage
```
To download the file `coverage/index.html` from the same
artifacts use the following URL:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/raw/coverage/index.html?job=coverage
```
There is also a URL to browse the latest job artifacts:
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/browse?job=<job_name>
```
For example:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/browse?job=coverage
```
There is also a URL to specific files, including HTML files that
are shown in [GitLab Pages](../../administration/pages/index.md):
```plaintext
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/file/<path>?job=<job_name>
```
For example, when a job `coverage` creates the artifact `htmlcov/index.html`,
you can access it at:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/file/htmlcov/index.html?job=coverage
```
The latest builds are also exposed in the UI in various places. Specifically,
look for the download button in:
- The main project's page
- The branches page
- The tags page
If the latest job has failed to upload the artifacts, you can see that
information in the UI.
![Latest artifacts button](img/job_latest_artifacts_browser.png)
## Erasing artifacts
WARNING:
This is a destructive action that leads to data loss. Use with caution.
You can erase a single job via the UI, which also removes the job's
artifacts and trace, if you are:
- The owner of the job.
- A [Maintainer](../../user/permissions.md#gitlab-cicd-permissions) of the project.
To erase a job:
1. Navigate to a job's page.
1. Click the trash icon at the top right of the job's trace.
1. Confirm the deletion.
## Retrieve artifacts of private projects when using GitLab CI
To retrieve a job artifact from a different project, you might need to use a
private token to [authenticate and download](../../api/job_artifacts.md#get-job-artifacts)
the artifact.
## Keep artifacts from most recent successful jobs
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16267) in GitLab 13.0.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/229936) in GitLab 13.4.
> - [Made optional with a CI/CD setting](https://gitlab.com/gitlab-org/gitlab/-/issues/241026) in GitLab 13.8.
By default, the latest artifacts from the most recent successful jobs are never deleted.
If a job is configured with [`expire_in`](../yaml/README.md#artifactsexpire_in),
its artifacts only expire if a more recent artifact exists.
Keeping the latest artifacts can use a large amount of storage space in projects
with a lot of jobs or large artifacts. If the latest artifacts are not needed in
a project, you can disable this behavior to save space:
1. Navigate to **Settings > CI/CD > Artifacts**.
1. Uncheck **Keep artifacts from most recent successful jobs**.
You can disable this behavior for all projects on a self-managed instance in the
[instance's CI/CD settings](../../user/admin_area/settings/continuous_integration.md#keep-the-latest-artifacts-for-all-jobs-in-the-latest-successful-pipelines). **(CORE ONLY)**
When you disable the feature, the latest artifacts do not immediately expire.
A new pipeline must run before the latest artifacts can expire and be deleted.
## Troubleshooting
## Troubleshooting job artifacts
### Error message `No files to upload`

View file

@ -3239,7 +3239,7 @@ Note the following:
- Artifacts do not display in the merge request UI when using variables to define the `artifacts:paths`.
- A maximum of 10 job artifacts per merge request can be exposed.
- Glob patterns are unsupported.
- If a directory is specified, the link is to the job [artifacts browser](../pipelines/job_artifacts.md#browsing-artifacts) if there is more than
- If a directory is specified, the link is to the job [artifacts browser](../pipelines/job_artifacts.md#download-job-artifacts) if there is more than
one file in the directory.
- For exposed single file artifacts with `.html`, `.htm`, `.txt`, `.json`, `.xml`,
and `.log` extensions, if [GitLab Pages](../../administration/pages/index.md) is:
@ -3508,7 +3508,7 @@ deploy:
If the artifacts of the job that is set as a dependency are
[expired](#artifactsexpire_in) or
[erased](../pipelines/job_artifacts.md#erasing-artifacts), then
[erased](../pipelines/job_artifacts.md#erase-job-artifacts), then
the dependent job fails.
You can ask your administrator to

View file

@ -141,7 +141,7 @@ The `gitlab-cov-fuzz` tool emits a JSON report file. For more information, see t
[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/coverage-fuzzing-report-format.json).
You can download the JSON report file from the CI pipelines page. For more information, see
[Downloading artifacts](../../../ci/pipelines/job_artifacts.md#downloading-artifacts).
[Downloading artifacts](../../../ci/pipelines/job_artifacts.md#download-job-artifacts).
Here's an example coverage fuzzing report:

View file

@ -536,7 +536,7 @@ The SAST tool emits a JSON report file. For more information, see the
[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json).
The JSON report file can be downloaded from the CI pipelines page, or the
pipelines tab on merge requests by [setting `artifacts: paths`](../../../ci/pipelines/job_artifacts.md#defining-artifacts-in-gitlab-ciyml) to `gl-sast-report.json`. For more information see [Downloading artifacts](../../../ci/pipelines/job_artifacts.md).
pipelines tab on merge requests by [setting `artifacts: paths`](../../../ci/yaml/README.md#artifactspaths) to `gl-sast-report.json`. For more information see [Downloading artifacts](../../../ci/pipelines/job_artifacts.md).
Here's an example SAST report:

View file

@ -4,7 +4,12 @@ group: Ecosystem
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Atlassian HipChat **(FREE)**
# Atlassian HipChat (Deprecated) **(FREE)**
As of [GitLab
13.11](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57434), the
HipChat integration will not send any notifications to HipChat. This
integration is also deprecated.
GitLab provides a way to send HipChat notifications upon a number of events,
such as when a user pushes code, creates a branch or tag, adds a comment, and

View file

@ -57,7 +57,7 @@ include:
creates an `a11y` job in your CI/CD pipeline, runs
Pa11y against the web pages defined in `a11y_urls`, and builds an HTML report for each.
The report for each URL is saved as an artifact that can be [viewed directly in your browser](../../../ci/pipelines/job_artifacts.md#browsing-artifacts).
The report for each URL is saved as an artifact that can be [viewed directly in your browser](../../../ci/pipelines/job_artifacts.md#download-job-artifacts).
A single `gl-accessibility.json` artifact is created and saved along with the individual HTML reports.
It includes report data for all URLs scanned.

View file

@ -355,7 +355,7 @@ After the Code Quality job completes:
The Code Quality widget in the merge request compares the reports from the base and head of the branch,
then lists any violations that are resolved or created when the branch is merged.
- The full JSON report is available as a
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#download-job-artifacts)
for the `code_quality` job.
- The full list of code quality violations generated by a pipeline is shown in the
Code Quality tab of the Pipeline Details page. **(PREMIUM)**

View file

@ -290,7 +290,7 @@ This problem most likely results from a missing `index.html` file in the public
a 404 is encountered, confirm that the public directory contains an `index.html` file. If the file contains a different name
such as `test.html`, the Pages site can still be accessed, but the full path would be needed. For example: `https//group-name.pages.example.com/project-name/test.html`.
The contents of the public directory can be confirmed by [browsing the artifacts](../../../ci/pipelines/job_artifacts.md#browsing-artifacts) from the latest pipeline.
The contents of the public directory can be confirmed by [browsing the artifacts](../../../ci/pipelines/job_artifacts.md#download-job-artifacts) from the latest pipeline.
Files listed under the public directory can be accessed through the Pages URL for the project.

View file

@ -70,7 +70,7 @@ module API
optional :variables, Array, desc: 'Array of variables available in the pipeline'
end
post ':id/pipeline' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42124')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20711')
authorize! :create_pipeline, user_project

View file

@ -112,7 +112,7 @@ module API
end
def delete_group(group)
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/46285')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/22226')
destroy_conditionally!(group) do |group|
::Groups::DestroyService.new(group, current_user).async_execute
end

View file

@ -242,7 +242,7 @@ module API
use :issue_params
end
post ':id/issues' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42320')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20773')
check_rate_limit! :issues_create, [current_user]
@ -288,7 +288,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/issues/:issue_iid' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42322')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20775')
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
@ -346,7 +346,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/issues/:issue_iid/move' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42323')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20776')
issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue

View file

@ -207,7 +207,7 @@ module API
use :optional_params
end
post ":id/merge_requests" do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42316')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20770')
authorize! :create_merge_request_from, user_project
@ -416,7 +416,7 @@ module API
at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of)
end
put ':id/merge_requests/:merge_request_iid' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42318')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20772')
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
@ -445,7 +445,7 @@ module API
optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end
put ':id/merge_requests/:merge_request_iid/merge' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42317')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796')
merge_request = find_project_merge_request(params[:merge_request_iid])

View file

@ -67,7 +67,7 @@ module API
check_rate_limit! :project_import, [current_user, :project_import]
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42437')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20823')
validate_file!

View file

@ -310,7 +310,7 @@ module API
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork'
end
post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20759')
not_found! unless can?(current_user, :fork_project, user_project)

View file

@ -21,7 +21,7 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab-foss/issues/42283')
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758')
forbidden! if gitlab_pipeline_hook_request?

View file

@ -249,7 +249,7 @@ module Backup
progress.puts " * #{display_repo_path} ... "
if repository.empty?
progress.puts " * #{display_repo_path} ... " + "[SKIPPED]".color(:cyan)
progress.puts " * #{display_repo_path} ... " + "[EMPTY] [SKIPPED]".color(:cyan)
return
end

View file

@ -19977,6 +19977,9 @@ msgstr ""
msgid "More Information"
msgstr ""
msgid "More Information."
msgstr ""
msgid "More Slack commands"
msgstr ""
@ -34256,9 +34259,6 @@ msgstr ""
msgid "WikiClone|Start Gollum and edit locally"
msgstr ""
msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title."
msgstr ""
msgid "WikiEdit|There is already a page with the same title in that path."
msgstr ""
@ -34331,18 +34331,6 @@ msgstr ""
msgid "WikiHistoricalPage|most recent version"
msgstr ""
msgid "WikiMarkdownDocs|More examples are in the %{docs_link}"
msgstr ""
msgid "WikiMarkdownDocs|documentation"
msgstr ""
msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
msgstr ""
msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
msgstr ""
msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
msgstr ""
@ -34355,10 +34343,37 @@ msgstr ""
msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{wikiLinkStart}the page%{wikiLinkEnd} and make sure your changes will not unintentionally remove theirs."
msgstr ""
msgid "WikiPageCreate|Create %{pageTitle}"
msgid "WikiPage|Commit message"
msgstr ""
msgid "WikiPageEdit|Update %{pageTitle}"
msgid "WikiPage|Content"
msgstr ""
msgid "WikiPage|Create %{pageTitle}"
msgstr ""
msgid "WikiPage|Create page"
msgstr ""
msgid "WikiPage|Format"
msgstr ""
msgid "WikiPage|Page title"
msgstr ""
msgid "WikiPage|Tip: You can move this page by adding the path to the beginning of the title."
msgstr ""
msgid "WikiPage|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
msgstr ""
msgid "WikiPage|Title"
msgstr ""
msgid "WikiPage|To link to a (new) page, simply type %{linkExample}. More examples are in the %{linkStart}documentation%{linkEnd}."
msgstr ""
msgid "WikiPage|Update %{pageTitle}"
msgstr ""
msgid "WikiPage|Write your content or drag files here…"
@ -34370,9 +34385,6 @@ msgstr ""
msgid "Wiki|Create New Page"
msgstr ""
msgid "Wiki|Create page"
msgstr ""
msgid "Wiki|Created date"
msgstr ""
@ -34385,9 +34397,6 @@ msgstr ""
msgid "Wiki|Page history"
msgstr ""
msgid "Wiki|Page title"
msgstr ""
msgid "Wiki|Page version"
msgstr ""

View file

@ -9,12 +9,11 @@ module QA
def self.included(base)
super
base.view 'app/views/shared/wikis/_form.html.haml' do
base.view 'app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue' do
element :wiki_title_textbox
element :wiki_content_textarea
element :wiki_message_textbox
element :save_changes_button
element :create_page_button
element :wiki_submit_button
end
base.view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
@ -34,12 +33,8 @@ module QA
fill_element(:wiki_message_textbox, message)
end
def click_save_changes
click_element(:save_changes_button)
end
def click_create_page
click_element(:create_page_button)
def click_submit
click_element(:wiki_submit_button)
end
def delete_page

View file

@ -27,7 +27,7 @@ module QA
edit.set_message commit_message
end
Page::Project::Wiki::Edit.perform(&:click_create_page)
Page::Project::Wiki::Edit.perform(&:click_submit)
Page::Project::Wiki::Show.perform do |wiki|
expect(wiki).to have_title new_wiki_title
@ -46,7 +46,7 @@ module QA
edit.set_message commit_message
end
Page::Project::Wiki::Edit.perform(&:click_create_page)
Page::Project::Wiki::Edit.perform(&:click_submit)
Page::Project::Wiki::Show.perform do |wiki|
expect(wiki).to have_title new_wiki_title

View file

@ -25,7 +25,7 @@ module QA
edit.set_message commit_message
end
Page::Project::Wiki::Edit.perform(&:click_save_changes)
Page::Project::Wiki::Edit.perform(&:click_submit)
Page::Project::Wiki::Show.perform do |wiki|
expect(wiki).to have_title new_wiki_title

View file

@ -20,7 +20,7 @@ module QA
edit.set_message('changing the path of the home page')
end
Page::Project::Wiki::Edit.perform(&:click_save_changes)
Page::Project::Wiki::Edit.perform(&:click_submit)
Page::Project::Wiki::Show.perform do |wiki|
expect(wiki).to have_directory('a')

View file

@ -0,0 +1,167 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
####
# Prints a report which helps reconcile occurrences of the `QueryLimiting.disable(ISSUE_LINK)`
# allowlist block against the corresponding open issues.
#
# If everything is consistent, the script should ideally not report any issues or code lines,
# other than possibly remaining "calls with no issue iid" which use variables/etc.
#
# - See https://gitlab.com/gitlab-org/gitlab/-/issues/325640
# - See https://gitlab.com/groups/gitlab-org/-/epics/5670
require 'rubygems'
require 'gitlab'
require 'optparse'
class QueryLimitingReport
GITLAB_PROJECT_ID = 278964 # gitlab-org/gitlab project
ISSUES_SEARCH_LABEL = 'querylimiting-disable'
CODE_LINES_SEARCH_STRING = 'QueryLimiting.disable'
PAGINATION_LIMIT = 500
DEFAULT_OPTIONS = {
api_token: ENV['API_TOKEN']
}.freeze
def initialize(options)
@options = options
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = options.fetch(:api_token)
end
end
def execute
# PLAN:
# Read all issues matching criteria and extract array of issue iids
# Find all code references and extract issue iids
# Print list of all issues without code references
# Print list of all code references issue iids that don't have search label
# Print list of all code references with no issue iids (i.e. dynamic or variable argument)
total_issues = find_issues_by_label(ISSUES_SEARCH_LABEL)
issues = total_issues.select { |issue| issue[:state] == 'opened' }
code_lines = find_code_lines
code_lines_grouped = code_lines.group_by { |code_line| code_line[:has_issue_iid] }
code_lines_without_issue_iid = code_lines_grouped[false]
code_lines_with_issue_iid = code_lines_grouped[true]
all_issue_iids_in_code_lines = code_lines_with_issue_iid.map { |line| line[:issue_iid] }
issues_without_code_references = issues.reject do |issue|
all_issue_iids_in_code_lines.include?(issue[:iid])
end
all_issue_iids = issues.map { |issue| issue[:iid] }
code_lines_with_missing_issues = code_lines_with_issue_iid.reject do |code_line|
all_issue_iids.include?(code_line[:issue_iid])
end
puts "\n\n\nREPORT:"
puts "\n\nFound #{total_issues.length} total issues with '#{ISSUES_SEARCH_LABEL}' search label, #{issues.length} are still opened..."
puts "\n\nFound #{code_lines.length} total occurrences of '#{CODE_LINES_SEARCH_STRING}' in code..."
puts "\n" + '-' * 80
puts "\n\nIssues without any '#{CODE_LINES_SEARCH_STRING}' code references (#{issues_without_code_references.length} total):"
pp issues_without_code_references
puts "\n" + '-' * 80
puts "\n\n'#{CODE_LINES_SEARCH_STRING}' calls with references to an issue which doesn't have '#{ISSUES_SEARCH_LABEL}' search label (#{code_lines_with_missing_issues.length} total):"
pp code_lines_with_missing_issues
puts "\n" + '-' * 80
puts "\n\n'#{CODE_LINES_SEARCH_STRING}' calls with no issue iid (#{code_lines_without_issue_iid&.length || 0} total):"
pp code_lines_without_issue_iid
end
private
attr_reader :options
def find_issues_by_label(label)
issues = []
puts("Finding issues by label #{label}...")
paginated_issues = Gitlab.issues(GITLAB_PROJECT_ID, 'labels' => label)
paginated_issues.paginate_with_limit(PAGINATION_LIMIT) do |item|
item_hash = item.to_hash
issue_iid = item_hash.fetch('iid')
issue = {
iid: issue_iid,
state: item_hash.fetch('state'),
title: item_hash.fetch('title'),
issue_url: "https://gitlab.com/gitlab-org/gitlab/issues/#{issue_iid}"
}
issues << issue
end
issues
end
def find_code_lines
code_lines = []
puts("Finding code lines...")
paginated_blobs = Gitlab.search_in_project(GITLAB_PROJECT_ID, 'blobs', CODE_LINES_SEARCH_STRING)
paginated_blobs.paginate_with_limit(PAGINATION_LIMIT) do |item|
item_hash = item.to_hash
filename = item_hash.fetch('filename')
next if filename !~ /\.rb\Z/
file_contents = Gitlab.file_contents(GITLAB_PROJECT_ID, filename)
file_lines = file_contents.split("\n")
file_lines.each_index do |index|
line = file_lines[index]
if line =~ /#{CODE_LINES_SEARCH_STRING}/
issue_iid = line.slice(%r{issues/(\d+)\D}, 1)
line_number = index + 1
code_line = {
file_location: "#{filename}:#{line_number}",
filename: filename,
line_number: line_number,
line: line,
issue_iid: issue_iid.to_i,
has_issue_iid: !issue_iid.nil?
}
code_lines << code_line
end
end
end
code_lines.sort_by! { |line| "#{line[:filename]}-#{line[:line_number].to_s.rjust(4, '0')}" }
code_lines.map do |line|
line.delete(:filename)
line.delete(:line_number)
line
end
end
end
if $0 == __FILE__
options = QueryLimitingReport::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope. Can be set as an env variable 'API_TOKEN'.") do |value|
options[:api_token] = value
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
QueryLimitingReport.new(options).execute
end

View file

@ -42,7 +42,7 @@ RSpec.describe 'Contributions Calendar', :js do
"#{contributions} #{'contribution'.pluralize(contributions)}"
end
"#{get_cell_color_selector(contributions)}[title='#{contribution_text}<br />#{date}']"
"#{get_cell_color_selector(contributions)}[title='#{contribution_text}<br /><span class=\"gl-text-gray-300\">#{date}</span>']"
end
def push_code_contribution

View file

@ -0,0 +1,222 @@
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import WikiForm from '~/pages/shared/wikis/components/wiki_form.vue';
describe('WikiForm', () => {
let wrapper;
const findForm = () => wrapper.find('form');
const findTitle = () => wrapper.find('#wiki_title');
const findFormat = () => wrapper.find('#wiki_format');
const findContent = () => wrapper.find('#wiki_content');
const findMessage = () => wrapper.find('#wiki_message');
const findSubmitButton = () => wrapper.findByTestId('wiki-submit-button');
const findCancelButton = () => wrapper.findByTestId('wiki-cancel-button');
const findTitleHelpLink = () => wrapper.findByTestId('wiki-title-help-link');
const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link');
const pageInfoNew = {
persisted: false,
uploadsPath: '/project/path/-/wikis/attachments',
wikiPath: '/project/path/-/wikis',
helpPath: '/help/user/project/wiki/index',
markdownHelpPath: '/help/user/markdown',
markdownPreviewPath: '/project/path/-/wikis/.md/preview-markdown',
createPath: '/project/path/-/wikis/new',
};
const pageInfoPersisted = {
...pageInfoNew,
persisted: true,
title: 'My page',
content: 'My page content',
format: 'markdown',
path: '/project/path/-/wikis/home',
};
function createWrapper(persisted = false, pageInfo = {}) {
wrapper = extendedWrapper(
mount(
WikiForm,
{
provide: {
formatOptions: {
Markdown: 'markdown',
RDoc: 'rdoc',
AsciiDoc: 'asciidoc',
Org: 'org',
},
pageInfo: {
...(persisted ? pageInfoPersisted : pageInfoNew),
...pageInfo,
},
},
},
{ attachToDocument: true },
),
);
jest.spyOn(wrapper.vm, 'onBeforeUnload');
}
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it.each`
title | persisted | message
${'my page'} | ${false} | ${'Create my page'}
${'my-page'} | ${false} | ${'Create my page'}
${'somedir/my-page'} | ${false} | ${'Create somedir/my page'}
${'my-page'} | ${true} | ${'Update my page'}
`(
'updates the commit message to $message when title is $title and persisted=$persisted',
async ({ title, message, persisted }) => {
createWrapper(persisted);
findTitle().setValue(title);
await wrapper.vm.$nextTick();
expect(findMessage().element.value).toBe(message);
},
);
it('sets the commit message to "Update My page" when the page first loads when persisted', async () => {
createWrapper(true);
await wrapper.vm.$nextTick();
expect(findMessage().element.value).toBe('Update My page');
});
it.each`
value | text
${'markdown'} | ${'[Link Title](page-slug)'}
${'rdoc'} | ${'{Link title}[link:page-slug]'}
${'asciidoc'} | ${'link:page-slug[Link title]'}
${'org'} | ${'[[page-slug]]'}
`('updates the link help message when format=$value is selected', async ({ value, text }) => {
createWrapper();
findFormat().find(`option[value=${value}]`).setSelected();
await wrapper.vm.$nextTick();
expect(wrapper.text()).toContain(text);
});
it('starts with no unload warning', async () => {
createWrapper();
await wrapper.vm.$nextTick();
window.dispatchEvent(new Event('beforeunload'));
expect(wrapper.vm.onBeforeUnload).not.toHaveBeenCalled();
});
it.each`
persisted | titleHelpText | titleHelpLink
${true} | ${'You can move this page by adding the path to the beginning of the title.'} | ${'/help/user/project/wiki/index#moving-a-wiki-page'}
${false} | ${'You can specify the full path for the new file. We will automatically create any missing directories.'} | ${'/help/user/project/wiki/index#creating-a-new-wiki-page'}
`(
'shows appropriate title help text and help link for when persisted=$persisted',
async ({ persisted, titleHelpLink, titleHelpText }) => {
createWrapper(persisted);
await wrapper.vm.$nextTick();
expect(wrapper.text()).toContain(titleHelpText);
expect(findTitleHelpLink().attributes().href).toEqual(titleHelpLink);
},
);
it('shows correct link for wiki specific markdown docs', async () => {
createWrapper();
await wrapper.vm.$nextTick();
expect(findMarkdownHelpLink().attributes().href).toEqual(
'/help/user/markdown#wiki-specific-markdown',
);
});
describe('when wiki content is updated', () => {
beforeEach(() => {
createWrapper();
const input = findContent();
input.setValue('Lorem ipsum dolar sit!');
input.element.dispatchEvent(new Event('input'));
return wrapper.vm.$nextTick();
});
it('sets before unload warning', () => {
window.dispatchEvent(new Event('beforeunload'));
expect(wrapper.vm.onBeforeUnload).toHaveBeenCalled();
});
it('when form submitted, unsets before unload warning', async () => {
findForm().element.dispatchEvent(new Event('submit'));
await wrapper.vm.$nextTick();
window.dispatchEvent(new Event('beforeunload'));
expect(wrapper.vm.onBeforeUnload).not.toHaveBeenCalled();
});
});
describe('submit button state', () => {
it.each`
title | content | buttonState | disabledAttr
${'something'} | ${'something'} | ${'enabled'} | ${undefined}
${''} | ${'something'} | ${'disabled'} | ${'disabled'}
${'something'} | ${''} | ${'disabled'} | ${'disabled'}
${''} | ${''} | ${'disabled'} | ${'disabled'}
${' '} | ${' '} | ${'disabled'} | ${'disabled'}
`(
"when title='$title', content='$content', then the button is $buttonState'",
async ({ title, content, disabledAttr }) => {
createWrapper();
findTitle().setValue(title);
findContent().setValue(content);
await wrapper.vm.$nextTick();
expect(findSubmitButton().attributes().disabled).toBe(disabledAttr);
},
);
it.each`
persisted | buttonLabel
${true} | ${'Save changes'}
${false} | ${'Create page'}
`('when persisted=$persisted, label is set to $buttonLabel', ({ persisted, buttonLabel }) => {
createWrapper(persisted);
expect(findSubmitButton().text()).toBe(buttonLabel);
});
});
describe('cancel button state', () => {
it.each`
persisted | redirectLink
${false} | ${'/project/path/-/wikis'}
${true} | ${'/project/path/-/wikis/home'}
`(
'when persisted=$persisted, redirects the user to appropriate path',
({ persisted, redirectLink }) => {
createWrapper(persisted);
expect(findCancelButton().attributes().href).toEqual(redirectLink);
},
);
});
});

View file

@ -25,6 +25,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
<div
class="js-vue-markdown-field md-area position-relative gfm-form js-expanded"
data-uploads-path=""
>
<markdown-header-stub
linecontent=""

View file

@ -4,159 +4,6 @@ import Wikis from '~/pages/shared/wikis/wikis';
import Tracking from '~/tracking';
describe('Wikis', () => {
const editFormHtmlFixture = (args) => `<form class="wiki-form ${
args.newPage ? 'js-new-wiki-page' : ''
}">
<input type="text" id="wiki_title" value="My title" />
<input type="text" id="wiki_message" />
<select class="form-control select-control" name="wiki[format]" id="wiki_format">
<option value="markdown">Markdown</option>
<option selected="selected" value="rdoc">RDoc</option>
<option value="asciidoc">AsciiDoc</option>
<option value="org">Org</option>
</select>
<textarea id="wiki_content"></textarea>
<code class="js-markup-link-example">{Link title}[link:page-slug]</code>
<input type="submit" class="js-wiki-btn-submit">
</input>
</form>
`;
let wikis;
let titleInput;
let contentInput;
let messageInput;
let changeFormatSelect;
let linkExample;
const findBeforeUnloadWarning = () => window.onbeforeunload?.();
const findForm = () => document.querySelector('.wiki-form');
const findSubmitButton = () => document.querySelector('.js-wiki-btn-submit');
describe('when the wiki page is being created', () => {
const formHtmlFixture = editFormHtmlFixture({ newPage: true });
beforeEach(() => {
setHTMLFixture(formHtmlFixture);
titleInput = document.getElementById('wiki_title');
messageInput = document.getElementById('wiki_message');
changeFormatSelect = document.querySelector('#wiki_format');
linkExample = document.querySelector('.js-markup-link-example');
wikis = new Wikis();
});
it('binds an event listener to the title input', () => {
wikis.handleWikiTitleChange = jest.fn();
titleInput.dispatchEvent(new Event('keyup'));
expect(wikis.handleWikiTitleChange).toHaveBeenCalled();
});
it('sets the commit message when title changes', () => {
titleInput.value = 'My title';
messageInput.value = '';
titleInput.dispatchEvent(new Event('keyup'));
expect(messageInput.value).toEqual('Create My title');
});
it('replaces hyphens with spaces', () => {
titleInput.value = 'my-hyphenated-title';
titleInput.dispatchEvent(new Event('keyup'));
expect(messageInput.value).toEqual('Create my hyphenated title');
});
});
describe('when the wiki page is being updated', () => {
const formHtmlFixture = editFormHtmlFixture({ newPage: false });
beforeEach(() => {
setHTMLFixture(formHtmlFixture);
titleInput = document.getElementById('wiki_title');
messageInput = document.getElementById('wiki_message');
wikis = new Wikis();
});
it('sets the commit message when title changes, prefixing with "Update"', () => {
titleInput.value = 'My title';
messageInput.value = '';
titleInput.dispatchEvent(new Event('keyup'));
expect(messageInput.value).toEqual('Update My title');
});
it.each`
value | text
${'markdown'} | ${'[Link Title](page-slug)'}
${'rdoc'} | ${'{Link title}[link:page-slug]'}
${'asciidoc'} | ${'link:page-slug[Link title]'}
${'org'} | ${'[[page-slug]]'}
`('updates a message when value=$value is selected', ({ value, text }) => {
changeFormatSelect.value = value;
changeFormatSelect.dispatchEvent(new Event('change'));
expect(linkExample.innerHTML).toBe(text);
});
it('starts with no unload warning', () => {
expect(findBeforeUnloadWarning()).toBeUndefined();
});
describe('when wiki content is updated', () => {
beforeEach(() => {
contentInput = document.getElementById('wiki_content');
contentInput.value = 'Lorem ipsum dolar sit!';
contentInput.dispatchEvent(new Event('input'));
});
it('sets before unload warning', () => {
expect(findBeforeUnloadWarning()).toBe('');
});
it('when form submitted, unsets before unload warning', () => {
findForm().dispatchEvent(new Event('submit'));
expect(findBeforeUnloadWarning()).toBeUndefined();
});
});
});
describe('submit button state', () => {
beforeEach(() => {
setHTMLFixture(editFormHtmlFixture({ newPage: true }));
titleInput = document.getElementById('wiki_title');
contentInput = document.getElementById('wiki_content');
wikis = new Wikis();
});
it.each`
title | text | buttonState | disabledAttr
${'something'} | ${'something'} | ${'enabled'} | ${null}
${''} | ${'something'} | ${'disabled'} | ${'true'}
${'something'} | ${''} | ${'disabled'} | ${'true'}
${''} | ${''} | ${'disabled'} | ${'true'}
${' '} | ${' '} | ${'disabled'} | ${'true'}
`(
"when title='$title', content='$content', then, buttonState='$buttonState'",
({ title, text, disabledAttr }) => {
titleInput.value = title;
titleInput.dispatchEvent(new Event('keyup'));
contentInput.value = text;
contentInput.dispatchEvent(new Event('input'));
expect(findSubmitButton().getAttribute('disabled')).toBe(disabledAttr);
},
);
});
describe('trackPageView', () => {
const trackingPage = 'projects:wikis:show';
const trackingContext = { foo: 'bar' };

View file

@ -49,307 +49,8 @@ RSpec.describe HipchatService do
WebMock.stub_request(:post, api_url)
end
it 'tests and return errors' do
allow(hipchat).to receive(:execute).and_raise(StandardError, 'no such room')
result = hipchat.test(push_sample_data)
expect(result[:success]).to be_falsey
expect(result[:result].to_s).to eq('no such room')
end
it 'uses v1 if version is provided' do
allow(hipchat).to receive(:api_version).and_return('v1')
expect(HipChat::Client).to receive(:new).with(
token,
api_version: 'v1',
server_url: server_url
).and_return(double(:hipchat_service).as_null_object)
hipchat.execute(push_sample_data)
end
it 'uses v2 as the version when nothing is provided' do
allow(hipchat).to receive(:api_version).and_return('')
expect(HipChat::Client).to receive(:new).with(
token,
api_version: 'v2',
server_url: server_url
).and_return(double(:hipchat_service).as_null_object)
hipchat.execute(push_sample_data)
end
context 'push events' do
it "calls Hipchat API for push events" do
hipchat.execute(push_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "creates a push message" do
message = hipchat.send(:create_push_message, push_sample_data)
push_sample_data[:object_attributes]
branch = push_sample_data[:ref].gsub('refs/heads/', '')
expect(message).to include("#{user.name} pushed to branch " \
"<a href=\"#{project.web_url}/commits/#{branch}\">#{branch}</a> of " \
"<a href=\"#{project.web_url}\">#{project_name}</a>")
end
end
context 'tag_push events' do
let(:push_sample_data) do
Gitlab::DataBuilder::Push.build(
project: project,
user: user,
oldrev: Gitlab::Git::BLANK_SHA,
newrev: '1' * 40,
ref: 'refs/tags/test')
end
it "calls Hipchat API for tag push events" do
hipchat.execute(push_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "creates a tag push message" do
message = hipchat.send(:create_push_message, push_sample_data)
push_sample_data[:object_attributes]
expect(message).to eq("#{user.name} pushed new tag " \
"<a href=\"#{project.web_url}/commits/test\">test</a> to " \
"<a href=\"#{project.web_url}\">#{project_name}</a>\n")
end
end
context 'issue events' do
let(:issue) { create(:issue, title: 'Awesome issue', description: '**please** fix') }
let(:issue_service) { Issues::CreateService.new(project, user) }
let(:issues_sample_data) { issue_service.hook_data(issue, 'open') }
it "calls Hipchat API for issue events" do
hipchat.execute(issues_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "creates an issue message" do
message = hipchat.send(:create_issue_message, issues_sample_data)
obj_attr = issues_sample_data[:object_attributes]
expect(message).to eq("#{user.name} opened " \
"<a href=\"#{obj_attr[:url]}\">issue ##{obj_attr["iid"]}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>Awesome issue</b>" \
"<pre><strong>please</strong> fix</pre>")
end
end
context 'merge request events' do
let(:merge_request) { create(:merge_request, description: '**please** fix', title: 'Awesome merge request', target_project: project, source_project: project) }
let(:merge_service) { MergeRequests::CreateService.new(project, user) }
let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') }
it "calls Hipchat API for merge requests events" do
hipchat.execute(merge_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "creates a merge request message" do
message = hipchat.send(:create_merge_request_message,
merge_sample_data)
obj_attr = merge_sample_data[:object_attributes]
expect(message).to eq("#{user.name} opened " \
"<a href=\"#{obj_attr[:url]}\">merge request !#{obj_attr["iid"]}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>Awesome merge request</b>" \
"<pre><strong>please</strong> fix</pre>")
end
end
context "Note events" do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user) }
context 'when commit comment event triggered' do
let(:commit_note) do
create(:note_on_commit, author: user, project: project,
commit_id: project.repository.commit.id,
note: 'a comment on a commit')
end
it "calls Hipchat API for commit comment events" do
data = Gitlab::DataBuilder::Note.build(commit_note, user)
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
message = hipchat.send(:create_message, data)
obj_attr = data[:object_attributes]
commit_id = Commit.truncate_sha(data[:commit][:id])
title = hipchat.send(:format_title, data[:commit][:message])
expect(message).to eq("#{user.name} commented on " \
"<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"#{title}" \
"<pre>a comment on a commit</pre>")
end
end
context 'when merge request comment event triggered' do
let(:merge_request) do
create(:merge_request, source_project: project,
target_project: project)
end
let(:merge_request_note) do
create(:note_on_merge_request, noteable: merge_request,
project: project,
note: "merge request **note**")
end
it "calls Hipchat API for merge request comment events" do
data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
message = hipchat.send(:create_message, data)
obj_attr = data[:object_attributes]
merge_id = data[:merge_request]['iid']
title = data[:merge_request]['title']
expect(message).to eq("#{user.name} commented on " \
"<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>#{title}</b>" \
"<pre>merge request <strong>note</strong></pre>")
end
end
context 'when issue comment event triggered' do
let(:issue) { create(:issue, project: project) }
let(:issue_note) do
create(:note_on_issue, noteable: issue, project: project,
note: "issue **note**")
end
it "calls Hipchat API for issue comment events" do
data = Gitlab::DataBuilder::Note.build(issue_note, user)
hipchat.execute(data)
message = hipchat.send(:create_message, data)
obj_attr = data[:object_attributes]
issue_id = data[:issue]['iid']
title = data[:issue]['title']
expect(message).to eq("#{user.name} commented on " \
"<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>#{title}</b>" \
"<pre>issue <strong>note</strong></pre>")
end
context 'with confidential issue' do
before do
issue.update!(confidential: true)
end
it 'calls Hipchat API with issue comment' do
data = Gitlab::DataBuilder::Note.build(issue_note, user)
hipchat.execute(data)
message = hipchat.send(:create_message, data)
expect(message).to include("<pre>issue <strong>note</strong></pre>")
end
end
end
context 'when snippet comment event triggered' do
let(:snippet) { create(:project_snippet, project: project) }
let(:snippet_note) do
create(:note_on_project_snippet, noteable: snippet,
project: project,
note: "snippet note")
end
it "calls Hipchat API for snippet comment events" do
data = Gitlab::DataBuilder::Note.build(snippet_note, user)
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
message = hipchat.send(:create_message, data)
obj_attr = data[:object_attributes]
snippet_id = data[:snippet]['id']
title = data[:snippet]['title']
expect(message).to eq("#{user.name} commented on " \
"<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>#{title}</b>" \
"<pre>snippet note</pre>")
end
end
end
context 'pipeline events' do
let(:pipeline) { create(:ci_empty_pipeline, user: project.owner) }
let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
context 'for failed' do
before do
pipeline.drop
end
it "calls Hipchat API" do
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "creates a build message" do
message = hipchat.__send__(:create_pipeline_message, data)
project_url = project.web_url
project_name = project.full_name.gsub(/\s/, '')
pipeline_attributes = data[:object_attributes]
ref = pipeline_attributes[:ref]
ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
duration = pipeline_attributes[:duration]
user_name = data[:user][:name]
expect(message).to eq("<a href=\"#{project_url}\">#{project_name}</a>: " \
"Pipeline <a href=\"#{project_url}/-/pipelines/#{pipeline.id}\">##{pipeline.id}</a> " \
"of <a href=\"#{project_url}/-/commits/#{ref}\">#{ref}</a> #{ref_type} " \
"by #{user_name} failed in #{duration} second(s)")
end
end
context 'for succeeded' do
before do
pipeline.succeed
end
it "calls Hipchat API" do
hipchat.notify_only_broken_pipelines = false
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "notifies only broken" do
hipchat.notify_only_broken_pipelines = true
hipchat.execute(data)
expect(WebMock).not_to have_requested(:post, api_url).once
end
end
it 'does nothing' do
expect { hipchat.execute(push_sample_data) }.not_to raise_error
end
describe "#message_options" do
@ -388,22 +89,4 @@ RSpec.describe HipchatService do
end
end
end
context 'with UrlBlocker' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:hipchat) { create(:hipchat_service, project: project, properties: { room: 'test' }) }
let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
describe '#execute' do
before do
hipchat.server = 'http://localhost:9123'
end
it 'raises UrlBlocker for localhost' do
expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original
expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end
end
end
end

View file

@ -240,7 +240,7 @@ RSpec.shared_examples 'User creates wiki page' do
end
end
it "shows the emoji autocompletion dropdown" do
it "shows the emoji autocompletion dropdown", :js do
click_link("New page")
page.within(".wiki-form") do

View file

@ -38,19 +38,19 @@ RSpec.shared_examples 'User previews wiki changes' do
end
end
context "when there are no spaces or hyphens in the page name" do
context "when there are no spaces or hyphens in the page name", :js do
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a/b/c/d', content: page_content) }
it_behaves_like 'rewrites relative links'
end
context "when there are spaces in the page name" do
context "when there are spaces in the page name", :js do
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a page/b page/c page/d page', content: page_content) }
it_behaves_like 'rewrites relative links'
end
context "when there are hyphens in the page name" do
context "when there are hyphens in the page name", :js do
let(:wiki_page) { build(:wiki_page, wiki: wiki, title: 'a-page/b-page/c-page/d-page', content: page_content) }
it_behaves_like 'rewrites relative links'

View file

@ -11,7 +11,7 @@ RSpec.shared_examples 'User updates wiki page' do
sign_in(user)
end
context 'when wiki is empty' do
context 'when wiki is empty', :js do
before do |example|
visit(wiki_path(wiki))
@ -57,7 +57,7 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'wiki file attachments'
end
context 'when wiki is not empty' do
context 'when wiki is not empty', :js do
let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: 'home', content: 'Home page') }
before do
@ -147,7 +147,7 @@ RSpec.shared_examples 'User updates wiki page' do
it_behaves_like 'wiki file attachments'
end
context 'when the page is in a subdir' do
context 'when the page is in a subdir', :js do
let(:page_name) { 'page_name' }
let(:page_dir) { "foo/bar/#{page_name}" }
let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: page_dir, content: 'Home page') }

View file

@ -24,7 +24,7 @@ RSpec.shared_examples 'User views empty wiki' do
# This mirrors the logic in:
# - app/views/shared/empty_states/_wikis.html.haml
# - WikiHelper#wiki_empty_state_messages
it 'shows the empty state message with the expected elements' do
it 'shows the empty state message with the expected elements', :js do
visit wiki_path(wiki)
if writable