summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/concerns/preview_markdown.rb2
-rw-r--r--app/controllers/concerns/wiki_actions.rb228
-rw-r--r--app/controllers/projects/wikis_controller.rb199
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/helpers/gitlab_routing_helper.rb6
-rw-r--r--app/helpers/markup_helper.rb6
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/services/system_notes/issuables_service.rb2
-rw-r--r--app/views/projects/wikis/git_access.html.haml8
-rw-r--r--changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml5
-rw-r--r--changelogs/unreleased/500-metrics-creation.yml5
-rw-r--r--db/fixtures/development/17_cycle_analytics.rb4
-rw-r--r--doc/administration/audit_events.md2
-rw-r--r--doc/api/audit_events.md112
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql93
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json277
-rw-r--r--doc/api/graphql/reference/index.md10
-rw-r--r--doc/api/users.md115
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/url_builder.rb12
-rw-r--r--lib/tasks/gitlab/container_registry.rake7
-rw-r--r--package.json2
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb280
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb10
-rw-r--r--spec/helpers/markup_helper_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb20
-rw-r--r--spec/support/helpers/wiki_helpers.rb4
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb290
-rw-r--r--spec/tasks/gitlab/container_registry_rake_spec.rb15
-rw-r--r--yarn.lock8
31 files changed, 1174 insertions, 560 deletions
diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb
index d8a1a758a0d..2916762e31f 100644
--- a/app/controllers/concerns/preview_markdown.rb
+++ b/app/controllers/concerns/preview_markdown.rb
@@ -32,7 +32,7 @@ module PreviewMarkdown
def markdown_context_params
case controller_name
- when 'wikis' then { pipeline: :wiki, wiki: @project_wiki, page_slug: params[:id] }
+ when 'wikis' then { pipeline: :wiki, wiki: wiki, page_slug: params[:id] }
when 'snippets' then { skip_project_check: true }
when 'groups' then { group: group }
when 'projects' then projects_filter_params
diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb
new file mode 100644
index 00000000000..7aaa53da452
--- /dev/null
+++ b/app/controllers/concerns/wiki_actions.rb
@@ -0,0 +1,228 @@
+# frozen_string_literal: true
+
+module WikiActions
+ include SendsBlob
+ include Gitlab::Utils::StrongMemoize
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :authorize_read_wiki!
+ before_action :authorize_create_wiki!, only: [:edit, :create]
+ before_action :authorize_admin_wiki!, only: :destroy
+
+ before_action :wiki
+ before_action :page, only: [:show, :edit, :update, :history, :destroy]
+ before_action :load_sidebar, except: [:pages]
+
+ before_action only: [:show, :edit, :update] do
+ @valid_encoding = valid_encoding?
+ end
+
+ before_action only: [:edit, :update], unless: :valid_encoding? do
+ redirect_to wiki_page_path(wiki, page)
+ end
+ end
+
+ def new
+ redirect_to wiki_page_path(wiki, SecureRandom.uuid, random_title: true)
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def pages
+ @wiki_pages = Kaminari.paginate_array(
+ wiki.list_pages(sort: params[:sort], direction: params[:direction])
+ ).page(params[:page])
+
+ @wiki_entries = WikiPage.group_by_directory(@wiki_pages)
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # `#show` handles a number of scenarios:
+ #
+ # - If `id` matches a WikiPage, then show the wiki page.
+ # - If `id` is a file in the wiki repository, then send the file.
+ # - If we know the user wants to create a new page with the given `id`,
+ # then display a create form.
+ # - Otherwise show the empty wiki page and invite the user to create a page.
+ #
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def show
+ if page
+ set_encoding_error unless valid_encoding?
+
+ # Assign vars expected by MarkupHelper
+ @ref = params[:version_id]
+ @path = page.path
+
+ render 'show'
+ elsif file_blob
+ send_blob(wiki.repository, file_blob, allow_caching: container.public?)
+ elsif show_create_form?
+ # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
+ title = params[:id] unless params[:random_title].present?
+
+ @page = build_page(title: title)
+
+ render 'edit'
+ else
+ render 'empty'
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ def edit
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def update
+ return render('empty') unless can?(current_user, :create_wiki, container)
+
+ @page = WikiPages::UpdateService.new(container: container, current_user: current_user, params: wiki_params).execute(page)
+
+ if page.valid?
+ redirect_to(
+ wiki_page_path(wiki, page),
+ notice: _('Wiki was successfully updated.')
+ )
+ else
+ render 'edit'
+ end
+ rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
+ @error = e
+ render 'edit'
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def create
+ @page = WikiPages::CreateService.new(container: container, current_user: current_user, params: wiki_params).execute
+
+ if page.persisted?
+ redirect_to(
+ wiki_page_path(wiki, page),
+ notice: _('Wiki was successfully updated.')
+ )
+ else
+ render action: "edit"
+ end
+ rescue Gitlab::Git::Wiki::OperationError => e
+ @page = build_page(wiki_params)
+ @error = e
+
+ render 'edit'
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def history
+ if page
+ @page_versions = Kaminari.paginate_array(page.versions(page: params[:page].to_i),
+ total_count: page.count_versions)
+ .page(params[:page])
+ else
+ redirect_to(
+ wiki_page_path(wiki, :home),
+ notice: _("Page not found")
+ )
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def destroy
+ WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page)
+
+ redirect_to wiki_page_path(wiki, :home),
+ status: :found,
+ notice: _("Page was successfully deleted")
+ rescue Gitlab::Git::Wiki::OperationError => e
+ @error = e
+ render 'edit'
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ private
+
+ def container
+ raise NotImplementedError
+ end
+
+ def show_create_form?
+ can?(current_user, :create_wiki, container) &&
+ page.nil? &&
+ # Always show the create form when the wiki has had at least one page created.
+ # Otherwise, we only show the form when the user has navigated from
+ # the 'empty wiki' page
+ (wiki.exists? || params[:view] == 'create')
+ end
+
+ def wiki
+ strong_memoize(:wiki) do
+ wiki = Wiki.for_container(container, current_user)
+
+ # Call #wiki to make sure the Wiki Repo is initialized
+ wiki.wiki
+
+ wiki
+ end
+ rescue Wiki::CouldNotCreateWikiError
+ flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
+ redirect_to container
+ false
+ end
+
+ def page
+ strong_memoize(:page) do
+ wiki.find_page(*page_params)
+ end
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def load_sidebar
+ @sidebar_page = wiki.find_sidebar(params[:version_id])
+
+ unless @sidebar_page # Fallback to default sidebar
+ @sidebar_wiki_entries, @sidebar_limited = wiki.sidebar_entries
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ def wiki_params
+ params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
+ end
+
+ def build_page(args = {})
+ WikiPage.new(wiki).tap do |page|
+ page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases
+ end
+ end
+
+ def page_params
+ keys = [:id]
+ keys << :version_id if params[:action] == 'show'
+
+ params.values_at(*keys)
+ end
+
+ def valid_encoding?
+ page_encoding == Encoding::UTF_8
+ end
+
+ def page_encoding
+ strong_memoize(:page_encoding) { page&.content&.encoding }
+ end
+
+ def set_encoding_error
+ flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
+ end
+
+ def file_blob
+ strong_memoize(:file_blob) do
+ commit = wiki.repository.commit(wiki.default_branch)
+
+ next unless commit
+
+ wiki.repository.blob_at(commit.id, params[:id])
+ end
+ end
+end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 508b1f5bd0a..85e643aa212 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -1,206 +1,11 @@
# frozen_string_literal: true
class Projects::WikisController < Projects::ApplicationController
+ include WikiActions
include PreviewMarkdown
- include SendsBlob
- include Gitlab::Utils::StrongMemoize
- before_action :authorize_read_wiki!
- before_action :authorize_create_wiki!, only: [:edit, :create]
- before_action :authorize_admin_wiki!, only: :destroy
- before_action :load_project_wiki
- before_action :load_page, only: [:show, :edit, :update, :history, :destroy]
- before_action only: [:show, :edit, :update] do
- @valid_encoding = valid_encoding?
- end
- before_action only: [:edit, :update], unless: :valid_encoding? do
- redirect_to(project_wiki_path(@project, @page))
- end
-
- def new
- redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true)
- end
-
- def pages
- @wiki_pages = Kaminari.paginate_array(
- @project_wiki.list_pages(sort: params[:sort], direction: params[:direction])
- ).page(params[:page])
-
- @wiki_entries = WikiPage.group_by_directory(@wiki_pages)
- end
-
- # `#show` handles a number of scenarios:
- #
- # - If `id` matches a WikiPage, then show the wiki page.
- # - If `id` is a file in the wiki repository, then send the file.
- # - If we know the user wants to create a new page with the given `id`,
- # then display a create form.
- # - Otherwise show the empty wiki page and invite the user to create a page.
- def show
- if @page
- set_encoding_error unless valid_encoding?
-
- # Assign vars expected by MarkupHelper
- @ref = params[:version_id]
- @path = @page.path
-
- render 'show'
- elsif file_blob
- send_blob(@project_wiki.repository, file_blob, allow_caching: @project.public?)
- elsif show_create_form?
- # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
- title = params[:id] unless params[:random_title].present?
-
- @page = build_page(title: title)
-
- render 'edit'
- else
- render 'empty'
- end
- end
-
- def edit
- end
-
- def update
- return render('empty') unless can?(current_user, :create_wiki, @project)
-
- @page = WikiPages::UpdateService.new(container: @project, current_user: current_user, params: wiki_params).execute(@page)
-
- if @page.valid?
- redirect_to(
- project_wiki_path(@project, @page),
- notice: _('Wiki was successfully updated.')
- )
- else
- render 'edit'
- end
- rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
- @error = e
- render 'edit'
- end
-
- def create
- @page = WikiPages::CreateService.new(container: @project, current_user: current_user, params: wiki_params).execute
-
- if @page.persisted?
- redirect_to(
- project_wiki_path(@project, @page),
- notice: _('Wiki was successfully updated.')
- )
- else
- render action: "edit"
- end
- rescue Gitlab::Git::Wiki::OperationError => e
- @page = build_page(wiki_params)
- @error = e
-
- render 'edit'
- end
-
- def history
- if @page
- @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i),
- total_count: @page.count_versions)
- .page(params[:page])
- else
- redirect_to(
- project_wiki_path(@project, :home),
- notice: _("Page not found")
- )
- end
- end
-
- def destroy
- WikiPages::DestroyService.new(container: @project, current_user: current_user).execute(@page)
-
- redirect_to project_wiki_path(@project, :home),
- status: :found,
- notice: _("Page was successfully deleted")
- rescue Gitlab::Git::Wiki::OperationError => e
- @error = e
- render 'edit'
- end
+ alias_method :container, :project
def git_access
end
-
- private
-
- def show_create_form?
- can?(current_user, :create_wiki, @project) &&
- @page.nil? &&
- # Always show the create form when the wiki has had at least one page created.
- # Otherwise, we only show the form when the user has navigated from
- # the 'empty wiki' page
- (@project_wiki.exists? || params[:view] == 'create')
- end
-
- def load_project_wiki
- @project_wiki = load_wiki
-
- # Call #wiki to make sure the Wiki Repo is initialized
- @project_wiki.wiki
-
- @sidebar_page = @project_wiki.find_sidebar(params[:version_id])
-
- unless @sidebar_page # Fallback to default sidebar
- @sidebar_wiki_entries, @sidebar_limited = @project_wiki.sidebar_entries
- end
- rescue ProjectWiki::CouldNotCreateWikiError
- flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
- redirect_to project_path(@project)
- false
- end
-
- def load_wiki
- ProjectWiki.new(@project, current_user)
- end
-
- def wiki_params
- params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
- end
-
- def build_page(args = {})
- WikiPage.new(@project_wiki).tap do |page|
- page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases
- end
- end
-
- def load_page
- @page ||= find_page
- end
-
- def find_page
- @project_wiki.find_page(*page_params)
- end
-
- def page_params
- keys = [:id]
- keys << :version_id if params[:action] == 'show'
-
- params.values_at(*keys)
- end
-
- def valid_encoding?
- page_encoding == Encoding::UTF_8
- end
-
- def page_encoding
- strong_memoize(:page_encoding) { @page&.content&.encoding }
- end
-
- def set_encoding_error
- flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
- end
-
- def file_blob
- strong_memoize(:file_blob) do
- commit = @project_wiki.repository.commit(@project_wiki.default_branch)
-
- next unless commit
-
- @project_wiki.repository.blob_at(commit.id, params[:id])
- end
- end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3f2e9f73e37..2b50e61db99 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -310,8 +310,8 @@ class ProjectsController < Projects::ApplicationController
render 'projects/empty' if @project.empty_repo?
else
if can?(current_user, :read_wiki, @project)
- @project_wiki = @project.wiki
- @wiki_home = @project_wiki.find_page('home', params[:version_id])
+ @wiki = @project.wiki
+ @wiki_home = @wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
@issues = issuables_collection.page(params[:page])
@issuable_meta_data = Gitlab::IssuableMetadata.new(current_user, @issues).data
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 4474534045b..b47e3884072 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -298,6 +298,12 @@ module GitlabRoutingHelper
toggle_award_emoji_snippet_url(snippet, *new_args)
end
+ # Wikis
+
+ def wiki_page_path(wiki, page, **options)
+ Gitlab::UrlBuilder.wiki_page_url(wiki, page, **options, only_path: true)
+ end
+
private
def snippet_query_params(snippet, *args)
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 1a1daa297c6..7ab2b33de8c 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -129,8 +129,8 @@ module MarkupHelper
context.merge!(
pipeline: :wiki,
project: @project,
- wiki: @project_wiki,
- repository: @project_wiki.repository,
+ wiki: @wiki,
+ repository: @wiki.repository,
page_slug: wiki_page.slug,
issuable_state_filter_enabled: true
)
@@ -300,7 +300,7 @@ module MarkupHelper
# RepositoryLinkFilter and UploadLinkFilter
commit: @commit,
- project_wiki: @project_wiki,
+ wiki: @wiki,
ref: @ref,
requested_path: @path
)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 0ba660aadbd..6faf45d1009 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -104,7 +104,7 @@ class MergeRequest < ApplicationRecord
after_create :ensure_merge_request_diff
after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed
- after_save :ensure_metrics, unless: :importing?
+ after_commit :ensure_metrics, on: [:create, :update], unless: :importing?
after_commit :expire_etag_cache, unless: :importing?
# When this attribute is true some MR validation is ignored
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index fcb7fc908f1..7d7ee8d829e 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -128,7 +128,7 @@ module SystemNotes
body = cross_reference_note_content(gfm_reference)
if noteable.is_a?(ExternalIssue)
- noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
+ noteable.project.external_issue_tracker.create_cross_reference_note(noteable, mentioner, author)
else
create_note(NoteSummary.new(noteable, noteable.project, author, body, action: 'cross_reference'))
end
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 72c9f45779a..e8696a0a86d 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -8,10 +8,10 @@
.git-access-header.w-100.d-flex.flex-column.justify-content-center
%span
= _("Clone repository")
- %strong= @project_wiki.full_path
+ %strong= @wiki.full_path
.pt-3.pt-lg-0.w-100
- = render "shared/clone_panel", project: @project_wiki
+ = render "shared/clone_panel", project: @wiki
.wiki-git-access
%h3= s_("WikiClone|Install Gollum")
@@ -22,8 +22,8 @@
%h3= s_("WikiClone|Clone your wiki")
%pre.dark
:preserve
- git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')}
- cd #{h @project_wiki.path}
+ git clone #{ content_tag(:span, h(default_url_to_repo(@wiki)), class: 'clone')}
+ cd #{h @wiki.path}
%h3= s_("WikiClone|Start Gollum and edit locally")
%pre.dark
diff --git a/changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml b/changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml
new file mode 100644
index 00000000000..b9f98664dff
--- /dev/null
+++ b/changelogs/unreleased/219151-follow-up-from-fallback-to-lowest-visibility-level-in-snippet-visi.yml
@@ -0,0 +1,5 @@
+---
+title: Add snippet DB visibility check in spec
+merge_request: 33388
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/500-metrics-creation.yml b/changelogs/unreleased/500-metrics-creation.yml
new file mode 100644
index 00000000000..83a93e2ff04
--- /dev/null
+++ b/changelogs/unreleased/500-metrics-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Fix for metrics creation when saving MR
+merge_request: 32668
+author:
+type: fixed
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index f5dc2b558d4..57993061c58 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -107,6 +107,10 @@ class Gitlab::Seeder::CycleAnalytics
pipeline = FactoryBot.create(:ci_pipeline, :success, project: project)
build = FactoryBot.create(:ci_build, pipeline: pipeline, project: project, user: developers.sample)
+ # Required because seeds run in a transaction and these are now
+ # created in an `after_commit` hook.
+ merge_request.ensure_metrics
+
merge_request.metrics.update!(
latest_build_started_at: merge_request.created_at,
latest_build_finished_at: within_end_time(merge_request.created_at + TEST_STAGE_MAX_DURATION_IN_HOURS.hours),
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 93c4c9e93a7..b80c725dad5 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -97,6 +97,8 @@ From there, you can see the following actions:
- Permission to approve merge requests by authors was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
- Number of required approvals was updated ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7531) in GitLab 12.9)
+Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events-starter)
+
### Instance events **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2336) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.3.
diff --git a/doc/api/audit_events.md b/doc/api/audit_events.md
index 36b3722475f..ce2a9afd53c 100644
--- a/doc/api/audit_events.md
+++ b/doc/api/audit_events.md
@@ -225,3 +225,115 @@ Example response:
"created_at": "2019-08-28T19:36:44.162Z"
}
```
+
+## Project Audit Events **(STARTER)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219238) in GitLab 13.1.
+
+The Project Audit Events API allows you to retrieve [project audit events](../administration/audit_events.md#project-events-starter).
+
+To retrieve project audit events using the API, you must [authenticate yourself](README.md#authentication) as a Maintainer or an Owner of the project.
+
+### Retrieve all project audit events
+
+```plaintext
+GET /projects/:id/audit_events
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `created_after` | string | no | Return project audit events created on or after the given time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
+| `created_before` | string | no | Return project audit events created on or before the given time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/projects/7/audit_events
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 5,
+ "author_id": 1,
+ "entity_id": 7,
+ "entity_type": "Project",
+ "details": {
+ "change": "prevent merge request approval from reviewers",
+ "from": "",
+ "to": "true",
+ "author_name": "Administrator",
+ "target_id": 7,
+ "target_type": "Project",
+ "target_details": "twitter/typeahead-js",
+ "ip_address": "127.0.0.1",
+ "entity_path": "twitter/typeahead-js"
+ },
+ "created_at": "2020-05-26T22:55:04.230Z"
+ },
+ {
+ "id": 4,
+ "author_id": 1,
+ "entity_id": 7,
+ "entity_type": "Project",
+ "details": {
+ "change": "prevent merge request approval from authors",
+ "from": "false",
+ "to": "true",
+ "author_name": "Administrator",
+ "target_id": 7,
+ "target_type": "Project",
+ "target_details": "twitter/typeahead-js",
+ "ip_address": "127.0.0.1",
+ "entity_path": "twitter/typeahead-js"
+ },
+ "created_at": "2020-05-26T22:55:04.218Z"
+ }
+]
+```
+
+### Retrieve a specific project audit event
+
+Only available to project maintainers or owners.
+
+```plaintext
+GET /projects/:id/audit_events/:audit_event_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `audit_event_id` | integer | yes | The ID of the audit event |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/projects/7/audit_events/5
+```
+
+Example response:
+
+```json
+{
+ "id": 5,
+ "author_id": 1,
+ "entity_id": 7,
+ "entity_type": "Project",
+ "details": {
+ "change": "prevent merge request approval from reviewers",
+ "from": "",
+ "to": "true",
+ "author_name": "Administrator",
+ "target_id": 7,
+ "target_type": "Project",
+ "target_details": "twitter/typeahead-js",
+ "ip_address": "127.0.0.1",
+ "entity_path": "twitter/typeahead-js"
+ },
+ "created_at": "2020-05-26T22:55:04.230Z"
+}
+```
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 0a08d113e37..650a3878d38 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -12614,6 +12614,36 @@ type Vulnerability {
id: ID!
"""
+ List of issue links related to the vulnerability
+ """
+ issueLinks(
+ """
+ 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
+
+ """
+ Filter issue links by link type
+ """
+ linkType: [VulnerabilityIssueLinkType!]
+ ): VulnerabilityIssueLinkConnection!
+
+ """
Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability
"""
location: VulnerabilityLocation
@@ -12696,6 +12726,69 @@ type VulnerabilityEdge {
}
"""
+Represents an issue link of a vulnerability.
+"""
+type VulnerabilityIssueLink {
+ """
+ GraphQL ID of the vulnerability
+ """
+ id: ID!
+
+ """
+ The issue attached to issue link
+ """
+ issue: Issue!
+
+ """
+ Type of the issue link
+ """
+ linkType: VulnerabilityIssueLinkType!
+}
+
+"""
+The connection type for VulnerabilityIssueLink.
+"""
+type VulnerabilityIssueLinkConnection {
+ """
+ A list of edges.
+ """
+ edges: [VulnerabilityIssueLinkEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [VulnerabilityIssueLink]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type VulnerabilityIssueLinkEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: VulnerabilityIssueLink
+}
+
+"""
+The type of the issue link related to a vulnerability.
+"""
+enum VulnerabilityIssueLinkType {
+ CREATED
+ RELATED
+}
+
+"""
Represents a vulnerability location. The fields with data will depend on the vulnerability report type
"""
union VulnerabilityLocation = VulnerabilityLocationContainerScanning | VulnerabilityLocationDast | VulnerabilityLocationDependencyScanning | VulnerabilityLocationSast
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 506c060f5e0..92ff57f6604 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -37151,6 +37151,81 @@
"deprecationReason": null
},
{
+ "name": "issueLinks",
+ "description": "List of issue links related to the vulnerability",
+ "args": [
+ {
+ "name": "linkType",
+ "description": "Filter issue links by link type",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityIssueLinkType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "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": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityIssueLinkConnection",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "location",
"description": "Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability",
"args": [
@@ -37405,6 +37480,208 @@
"possibleTypes": null
},
{
+ "kind": "OBJECT",
+ "name": "VulnerabilityIssueLink",
+ "description": "Represents an issue link of a vulnerability.",
+ "fields": [
+ {
+ "name": "id",
+ "description": "GraphQL ID of the vulnerability",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "issue",
+ "description": "The issue attached to issue link",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Issue",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "linkType",
+ "description": "Type of the issue link",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityIssueLinkType",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityIssueLinkConnection",
+ "description": "The connection type for VulnerabilityIssueLink.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityIssueLinkEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilityIssueLink",
+ "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": "VulnerabilityIssueLinkEdge",
+ "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": "VulnerabilityIssueLink",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "VulnerabilityIssueLinkType",
+ "description": "The type of the issue link related to a vulnerability.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "RELATED",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "CREATED",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
"kind": "UNION",
"name": "VulnerabilityLocation",
"description": "Represents a vulnerability location. The fields with data will depend on the vulnerability report type",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 405a32be0e8..ba335b67cc8 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1871,6 +1871,16 @@ Represents a vulnerability.
| `userPermissions` | VulnerabilityPermissions! | Permissions for the current user on the resource |
| `vulnerabilityPath` | String | URL to the vulnerability's details page |
+## VulnerabilityIssueLink
+
+Represents an issue link of a vulnerability.
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | GraphQL ID of the vulnerability |
+| `issue` | Issue! | The issue attached to issue link |
+| `linkType` | VulnerabilityIssueLinkType! | Type of the issue link |
+
## VulnerabilityLocationContainerScanning
Represents the location of a vulnerability found by a container security scan
diff --git a/doc/api/users.md b/doc/api/users.md
index cb0d24d8566..6ac1cd089e7 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -70,12 +70,12 @@ Username search is case insensitive.
GET /users
```
-| Attribute | Type | Required | Description |
-| ------------ | ------ | -------- | ----------- |
-| `order_by` | string | no | Return users ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` |
-| `sort` | string | no | Return users sorted in `asc` or `desc` order. Default is `desc` |
-| `two_factor` | string | no | Filter users by Two-factor authentication. Filter values are `enabled` or `disabled`. By default it returns all users |
-| `without_projects` | boolean | no | Filter users without projects. Default is `false` |
+| Attribute | Type | Required | Description |
+| ------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
+| `order_by` | string | no | Return users ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` |
+| `sort` | string | no | Return users sorted in `asc` or `desc` order. Default is `desc` |
+| `two_factor` | string | no | Filter users by Two-factor authentication. Filter values are `enabled` or `disabled`. By default it returns all users |
+| `without_projects` | boolean | no | Filter users without projects. Default is `false` |
```json
[
@@ -375,7 +375,7 @@ POST /users
Parameters:
| Attribute | Required | Description |
-|:-------------------------------------|:---------|:--------------------------------------------------------------------------------------------------------------------------------------------------------|
+| :----------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `admin` | No | User is admin - true or false (default) |
| `avatar` | No | Image file for user's avatar |
| `bio` | No | User's biography |
@@ -417,7 +417,7 @@ PUT /users/:id
Parameters:
| Attribute | Required | Description |
-|:-------------------------------------|:---------|:--------------------------------------------------------------------------------------------------------------------------------------------------------|
+| :----------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `admin` | No | User is admin - true or false (default) |
| `avatar` | No | Image file for user's avatar |
| `bio` | No | User's biography |
@@ -432,7 +432,7 @@ Parameters:
| `linkedin` | No | LinkedIn |
| `location` | No | User's location |
| `name` | No | Name |
-| `note` | No | Admin notes for this user |
+| `note` | No | Admin notes for this user |
| `organization` | No | Organization name |
| `password` | No | Password |
| `private_profile` | No | User's profile is private - true, false (default), or null (will be converted to false) |
@@ -609,8 +609,8 @@ Get the status of a user.
GET /users/:id_or_username/status
```
-| Attribute | Type | Required | Description |
-| ---------------- | ------ | -------- | ----------- |
+| Attribute | Type | Required | Description |
+| ---------------- | ------ | -------- | ------------------------------------------------- |
| `id_or_username` | string | yes | The ID or username of the user to get a status of |
```shell
@@ -635,10 +635,10 @@ Set the status of the current user.
PUT /user/status
```
-| Attribute | Type | Required | Description |
-| --------- | ------ | -------- | ----------- |
-| `emoji` | string | no | The name of the emoji to use as status, if omitted `speech_balloon` is used. Emoji name can be one of the specified names in the [Gemojione index](https://github.com/bonusly/gemojione/blob/master/config/index.json). |
-| `message` | string | no | The message to set as a status. It can also contain emoji codes. |
+| Attribute | Type | Required | Description |
+| --------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `emoji` | string | no | The name of the emoji to use as status. If omitted `speech_balloon` is used. Emoji name can be one of the specified names in the [Gemojione index](https://github.com/bonusly/gemojione/blob/master/config/index.json). |
+| `message` | string | no | The message to set as a status. It can also contain emoji codes. |
When both parameters `emoji` and `message` are empty, the status will be cleared.
@@ -660,9 +660,9 @@ Example responses
Get the counts (same as in top right menu) of the currently signed in user.
-| Attribute | Type | Description |
-| --------- | ---- | ----------- |
-| `merge_requests` | number | Merge requests that are active and assigned to current user. |
+| Attribute | Type | Description |
+| ---------------- | ------ | ------------------------------------------------------------ |
+| `merge_requests` | number | Merge requests that are active and assigned to current user. |
```plaintext
GET /user_counts
@@ -721,8 +721,8 @@ Get a list of a specified user's SSH keys.
GET /users/:id_or_username/keys
```
-| Attribute | Type | Required | Description |
-| ---------------- | ------ | -------- | ----------- |
+| Attribute | Type | Required | Description |
+| ---------------- | ------ | -------- | ------------------------------------------------------- |
| `id_or_username` | string | yes | The ID or username of the user to get the SSH keys for. |
## Single SSH key
@@ -758,13 +758,13 @@ Parameters:
- `title` (required) - new SSH Key's title
- `key` (required) - new SSH key
+- `expires_at` (optional) - The expiration date of the SSH key in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)
```json
{
- "created_at": "2015-01-21T17:44:33.512Z",
- "key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call",
"title": "ABC",
- "id": 4
+ "key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call",
+ "expires_at": "2016-01-21T00:00:00.000Z"
}
```
@@ -797,6 +797,7 @@ Parameters:
- `id` (required) - ID of specified user
- `title` (required) - new SSH Key's title
- `key` (required) - new SSH key
+- `expires_at` (optional) - The expiration date of the SSH key in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)
## Delete SSH key for current user
@@ -858,8 +859,8 @@ GET /user/gpg_keys/:key_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | ----------- |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
| `key_id` | integer | yes | The ID of the GPG key |
```shell
@@ -886,8 +887,8 @@ POST /user/gpg_keys
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------ | -------- | ----------- |
+| Attribute | Type | Required | Description |
+| --------- | ------ | -------- | --------------- |
| key | string | yes | The new GPG key |
```shell
@@ -916,8 +917,8 @@ DELETE /user/gpg_keys/:key_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | ----------- |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
| `key_id` | integer | yes | The ID of the GPG key |
```shell
@@ -936,8 +937,8 @@ GET /users/:id/gpg_keys
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | ----------- |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | ------------------ |
| `id` | integer | yes | The ID of the user |
```shell
@@ -966,9 +967,9 @@ GET /users/:id/gpg_keys/:key_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | ----------- |
-| `id` | integer | yes | The ID of the user |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the user |
| `key_id` | integer | yes | The ID of the GPG key |
```shell
@@ -995,9 +996,9 @@ POST /users/:id/gpg_keys
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | ----------- |
-| `id` | integer | yes | The ID of the user |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the user |
| `key_id` | integer | yes | The ID of the GPG key |
```shell
@@ -1026,9 +1027,9 @@ DELETE /users/:id/gpg_keys/:key_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | ----------- |
-| `id` | integer | yes | The ID of the user |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the user |
| `key_id` | integer | yes | The ID of the GPG key |
```shell
@@ -1347,12 +1348,12 @@ settings page.
POST /users/:user_id/impersonation_tokens
```
-| Attribute | Type | Required | Description |
-| ------------ | ------- | -------- | ----------- |
-| `user_id` | integer | yes | The ID of the user |
-| `name` | string | yes | The name of the impersonation token |
-| `expires_at` | date | no | The expiration date of the impersonation token in ISO format (`YYYY-MM-DD`)|
-| `scopes` | array | yes | The array of scopes of the impersonation token (`api`, `read_user`) |
+| Attribute | Type | Required | Description |
+| ------------ | ------- | -------- | --------------------------------------------------------------------------- |
+| `user_id` | integer | yes | The ID of the user |
+| `name` | string | yes | The name of the impersonation token |
+| `expires_at` | date | no | The expiration date of the impersonation token in ISO format (`YYYY-MM-DD`) |
+| `scopes` | array | yes | The array of scopes of the impersonation token (`api`, `read_user`) |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "name=mytoken" --data "expires_at=2017-04-04" --data "scopes[]=api" "https://gitlab.example.com/api/v4/users/42/impersonation_tokens"
@@ -1392,10 +1393,10 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `user_id` | integer | yes | The ID of the user |
-| `impersonation_token_id` | integer | yes | The ID of the impersonation token |
+| Attribute | Type | Required | Description |
+| ------------------------ | ------- | -------- | --------------------------------- |
+| `user_id` | integer | yes | The ID of the user |
+| `impersonation_token_id` | integer | yes | The ID of the impersonation token |
### Get user activities (admin only)
@@ -1420,9 +1421,9 @@ GET /user/activities
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `from` | string | no | Date string in the format YEAR-MONTH-DAY. For example, `2016-03-11`. Defaults to 6 months ago. |
+| Attribute | Type | Required | Description |
+| --------- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
+| `from` | string | no | Date string in the format YEAR-MONTH-DAY. For example, `2016-03-11`. Defaults to 6 months ago. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/user/activities"
@@ -1467,10 +1468,10 @@ GET /users/:id/memberships
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a specified user |
-| `type` | string | no | Filter memberships by type. Can be either `Project` or `Namespace` |
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | ------------------------------------------------------------------ |
+| `id` | integer | yes | The ID of a specified user |
+| `type` | string | no | Filter memberships by type. Can be either `Project` or `Namespace` |
Returns:
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 8457ebb00ed..ed9dac8b494 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -255,6 +255,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
requires :key, type: String, desc: 'The new SSH key'
requires :title, type: String, desc: 'The title of the new SSH key'
+ optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end
# rubocop: disable CodeReuse/ActiveRecord
post ":id/keys" do
@@ -720,6 +721,7 @@ module API
params do
requires :key, type: String, desc: 'The new SSH key'
requires :title, type: String, desc: 'The title of the new SSH key'
+ optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end
post "keys" do
key = current_user.keys.new(declared_params)
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 329f87d8be8..b82ab30cc34 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -39,9 +39,9 @@ module Gitlab
when User
instance.user_url(object, **options)
when Wiki
- wiki_url(object, **options)
+ wiki_page_url(object, Wiki::HOMEPAGE, **options)
when WikiPage
- instance.project_wiki_url(object.wiki.project, object.slug, **options)
+ wiki_page_url(object.wiki, object, **options)
when ::DesignManagement::Design
design_url(object, **options)
else
@@ -78,11 +78,11 @@ module Gitlab
end
end
- def wiki_url(object, **options)
- if object.container.is_a?(Project)
- instance.project_wiki_url(object.container, Wiki::HOMEPAGE, **options)
+ def wiki_page_url(wiki, page, **options)
+ if wiki.container.is_a?(Project)
+ instance.project_wiki_url(wiki.container, page, **options)
else
- raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
+ raise NotImplementedError.new("No URL builder defined for #{wiki.container.inspect} wikis")
end
end
diff --git a/lib/tasks/gitlab/container_registry.rake b/lib/tasks/gitlab/container_registry.rake
index f414ea0be6c..7687cb237cc 100644
--- a/lib/tasks/gitlab/container_registry.rake
+++ b/lib/tasks/gitlab/container_registry.rake
@@ -2,10 +2,15 @@ namespace :gitlab do
namespace :container_registry do
desc "GitLab | Container Registry | Configure"
task configure: :gitlab_environment do
+ configure
+ end
+
+ def configure
registry_config = Gitlab.config.registry
unless registry_config.enabled && registry_config.api_url.presence
- raise 'Registry is not enabled or registry api url is not present.'
+ puts "Registry is not enabled or registry api url is not present.".color(:yellow)
+ return
end
warn_user_is_not_gitlab
diff --git a/package.json b/package.json
index 06b23455b4d..8e7852b191e 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@gitlab/at.js": "1.5.5",
- "@gitlab/svgs": "1.135.0",
+ "@gitlab/svgs": "1.137.0",
"@gitlab/ui": "16.2.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3",
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index 2671e3db982..4e58822b613 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -3,282 +3,8 @@
require 'spec_helper'
RSpec.describe Projects::WikisController do
- let_it_be(:project) { create(:project, :public, :repository) }
- let(:user) { project.owner }
- let(:project_wiki) { ProjectWiki.new(project, user) }
- let(:wiki) { project_wiki.wiki }
- let(:wiki_title) { 'page title test' }
-
- before do
- create_page(wiki_title, 'hello world')
-
- sign_in(user)
- end
-
- after do
- destroy_page(wiki_title)
- end
-
- describe 'GET #new' do
- subject { get :new, params: { namespace_id: project.namespace, project_id: project } }
-
- it 'redirects to #show and appends a `random_title` param' do
- subject
-
- expect(response).to have_gitlab_http_status(:found)
- expect(Rails.application.routes.recognize_path(response.redirect_url)).to include(
- controller: 'projects/wikis',
- action: 'show'
- )
- expect(response.redirect_url).to match(/\?random_title=true\Z/)
- end
- end
-
- describe 'GET #pages' do
- subject { get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
-
- it 'does not load the pages content' do
- expect(controller).to receive(:load_wiki).and_return(project_wiki)
-
- expect(project_wiki).to receive(:list_pages).twice.and_call_original
-
- subject
- end
- end
-
- describe 'GET #history' do
- before do
- allow(controller)
- .to receive(:can?)
- .with(any_args)
- .and_call_original
-
- # The :create_wiki permission is irrelevant to reading history.
- expect(controller)
- .not_to receive(:can?)
- .with(anything, :create_wiki, any_args)
-
- allow(controller)
- .to receive(:can?)
- .with(anything, :read_wiki, any_args)
- .and_return(allow_read_wiki)
- end
-
- shared_examples 'fetching history' do |expected_status|
- before do
- get :history, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }
- end
-
- it "returns status #{expected_status}" do
- expect(response).to have_gitlab_http_status(expected_status)
- end
- end
-
- it_behaves_like 'fetching history', :ok do
- let(:allow_read_wiki) { true }
-
- it 'assigns @page_versions' do
- expect(assigns(:page_versions)).to be_present
- end
- end
-
- it_behaves_like 'fetching history', :not_found do
- let(:allow_read_wiki) { false }
- end
- end
-
- describe 'GET #show' do
- render_views
-
- let(:random_title) { nil }
-
- subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: id, random_title: random_title } }
-
- context 'when page exists' do
- let(:id) { wiki_title }
-
- it 'limits the retrieved pages for the sidebar' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:page).title).to eq(wiki_title)
- expect(assigns(:sidebar_wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
- expect(assigns(:sidebar_limited)).to be(false)
- end
-
- context 'when page content encoding is invalid' do
- it 'sets flash error' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(flash[:notice]).to eq(_('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'))
- end
- end
- end
-
- context 'when the page does not exist' do
- let(:id) { 'does not exist' }
-
- before do
- subject
- end
-
- it 'builds a new wiki page with the id as the title' do
- expect(assigns(:page).title).to eq(id)
- end
-
- context 'when a random_title param is present' do
- let(:random_title) { true }
-
- it 'builds a new wiki page with no title' do
- expect(assigns(:page).title).to be_empty
- end
- end
- end
-
- context 'when page is a file' do
- include WikiHelpers
-
- let(:id) { upload_file_to_wiki(project, user, file_name) }
-
- context 'when file is an image' do
- let(:file_name) { 'dk.png' }
-
- it 'delivers the image' do
- subject
-
- expect(response.headers['Content-Disposition']).to match(/^inline/)
- expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
- end
-
- context 'when file is a svg' do
- let(:file_name) { 'unsanitized.svg' }
-
- it 'delivers the image' do
- subject
-
- expect(response.headers['Content-Disposition']).to match(/^inline/)
- expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
- end
- end
-
- it_behaves_like 'project cache control headers'
- end
-
- context 'when file is a pdf' do
- let(:file_name) { 'git-cheat-sheet.pdf' }
-
- it 'sets the content type to sets the content response headers' do
- subject
-
- expect(response.headers['Content-Disposition']).to match(/^inline/)
- expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
- end
-
- it_behaves_like 'project cache control headers'
- end
- end
- end
-
- describe 'POST #preview_markdown' do
- it 'renders json in a correct format' do
- post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' }
-
- expect(json_response.keys).to match_array(%w(body references))
- end
- end
-
- describe 'GET #edit' do
- subject { get(:edit, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }) }
-
- context 'when page content encoding is invalid' do
- it 'redirects to show' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
-
- subject
-
- expect(response).to redirect_to_wiki(project, project_wiki.list_pages.first)
- end
- end
-
- context 'when the page has nil content' do
- let(:page) { create(:wiki_page) }
-
- it 'redirects to show' do
- allow(page).to receive(:content).and_return(nil)
- allow(controller).to receive(:find_page).and_return(page)
-
- subject
-
- expect(response).to redirect_to_wiki(project, page)
- end
- end
-
- context 'when page content encoding is valid' do
- render_views
-
- it 'shows the edit page' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to include(s_('Wiki|Edit Page'))
- end
- end
- end
-
- describe 'PATCH #update' do
- let(:new_title) { 'New title' }
- let(:new_content) { 'New content' }
-
- subject do
- patch(:update,
- params: {
- namespace_id: project.namespace,
- project_id: project,
- id: wiki_title,
- wiki: { title: new_title, content: new_content }
- })
- end
-
- context 'when page content encoding is invalid' do
- it 'redirects to show' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
-
- subject
- expect(response).to redirect_to_wiki(project, project_wiki.list_pages.first)
- end
- end
-
- context 'when page content encoding is valid' do
- render_views
-
- it 'updates the page' do
- subject
-
- wiki_page = project_wiki.list_pages(load_content: true).first
-
- expect(wiki_page.title).to eq new_title
- expect(wiki_page.content).to eq new_content
- end
- end
- end
-
- def create_page(name, content)
- wiki.write_page(name, :markdown, content, commit_details(name))
- end
-
- def commit_details(name)
- Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "created page #{name}")
- end
-
- def destroy_page(title, dir = '')
- page = wiki.page(title: title, dir: dir)
- project_wiki.delete_page(page, "test commit")
- end
-
- def redirect_to_wiki(project, page)
- redirect_to(controller.project_wiki_path(project, page))
+ it_behaves_like 'wiki controller actions' do
+ let(:container) { create(:project, :public, :repository, namespace: user.namespace) }
+ let(:routing_params) { { namespace_id: container.namespace, project_id: container } }
end
end
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 284aa77c742..b100e035d38 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -176,6 +176,8 @@ RSpec.shared_examples_for 'snippet editor' do
click_link('Internal')
expect(page).to have_content('My Snippet Title')
+ created_snippet = Snippet.last
+ expect(created_snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
end
end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 1955927e2df..2bc0d9fbb42 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -229,4 +229,14 @@ describe GitlabRoutingHelper do
end
end
end
+
+ context 'wikis' do
+ let(:wiki) { create(:project_wiki) }
+
+ describe '#wiki_page_path' do
+ it 'returns the url for the wiki page' do
+ expect(wiki_page_path(wiki, 'page')).to eq("/#{wiki.project.full_path}/-/wikis/page")
+ end
+ end
+ end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index c5e38103f03..1fc79a9762a 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -327,7 +327,7 @@ describe MarkupHelper do
expect(wiki).to receive(:content).and_return('wiki content')
expect(wiki).to receive(:slug).and_return('nested/page')
expect(wiki).to receive(:repository).and_return(wiki_repository)
- helper.instance_variable_set(:@project_wiki, wiki)
+ helper.instance_variable_set(:@wiki, wiki)
end
context 'when file is Markdown' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 1bf2c0c533a..7c0b6ac2e45 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1242,6 +1242,16 @@ describe API::Users, :do_not_mock_admin_mode do
end.to change { user.keys.count }.by(1)
end
+ it 'creates SSH key with `expires_at` attribute' do
+ optional_attributes = { expires_at: '2016-01-21T00:00:00.000Z' }
+ attributes = attributes_for(:key).merge(optional_attributes)
+
+ post api("/users/#{user.id}/keys", admin), params: attributes
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['expires_at']).to eq(optional_attributes[:expires_at])
+ end
+
it "returns 400 for invalid ID" do
post api("/users/0/keys", admin)
expect(response).to have_gitlab_http_status(:bad_request)
@@ -1798,6 +1808,16 @@ describe API::Users, :do_not_mock_admin_mode do
expect(response).to have_gitlab_http_status(:created)
end
+ it 'creates SSH key with `expires_at` attribute' do
+ optional_attributes = { expires_at: '2016-01-21T00:00:00.000Z' }
+ attributes = attributes_for(:key).merge(optional_attributes)
+
+ post api("/user/keys", user), params: attributes
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['expires_at']).to eq(optional_attributes[:expires_at])
+ end
+
it "returns a 401 error if unauthorized" do
post api("/user/keys"), params: { title: 'some title', key: 'some key' }
expect(response).to have_gitlab_http_status(:unauthorized)
diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb
index e6818ff8f0c..ae0d53d1297 100644
--- a/spec/support/helpers/wiki_helpers.rb
+++ b/spec/support/helpers/wiki_helpers.rb
@@ -8,14 +8,14 @@ module WikiHelpers
find('.svg-content .js-lazy-loaded') if example.nil? || example.metadata.key?(:js)
end
- def upload_file_to_wiki(project, user, file_name)
+ def upload_file_to_wiki(container, user, file_name)
opts = {
file_name: file_name,
file_content: File.read(expand_fixture_path(file_name))
}
::Wikis::CreateAttachmentService.new(
- container: project,
+ container: container,
current_user: user,
params: opts
).execute[:result][:file_path]
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
new file mode 100644
index 00000000000..db6af403112
--- /dev/null
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -0,0 +1,290 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'wiki controller actions' do
+ let(:container) { raise NotImplementedError }
+ let(:routing_params) { raise NotImplementedError }
+
+ let_it_be(:user) { create(:user) }
+ let(:wiki) { Wiki.for_container(container, user) }
+ let(:wiki_title) { 'page title test' }
+
+ before do
+ create(:wiki_page, wiki: wiki, title: wiki_title, content: 'hello world')
+
+ sign_in(user)
+ end
+
+ describe 'GET #new' do
+ subject { get :new, params: routing_params }
+
+ it 'redirects to #show and appends a `random_title` param' do
+ subject
+
+ expect(response).to be_redirect
+ expect(response.redirect_url).to match(%r{
+ #{Regexp.quote(wiki.wiki_base_path)} # wiki base path
+ /[-\h]{36} # page slug
+ \?random_title=true\Z # random_title param
+ }x)
+ end
+
+ context 'when the wiki repository cannot be created' do
+ before do
+ expect(Wiki).to receive(:for_container).and_return(wiki)
+ expect(wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
+ end
+
+ it 'redirects to the wiki container and displays an error message' do
+ subject
+
+ expect(response).to redirect_to(container)
+ expect(flash[:notice]).to eq('Could not create Wiki Repository at this time. Please try again later.')
+ end
+ end
+ end
+
+ describe 'GET #pages' do
+ before do
+ get :pages, params: routing_params.merge(id: wiki_title)
+ end
+
+ it 'assigns the page collections' do
+ expect(assigns(:wiki_pages)).to contain_exactly(an_instance_of(WikiPage))
+ expect(assigns(:wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
+ end
+
+ it 'does not load the page content' do
+ expect(assigns(:page)).to be_nil
+ end
+
+ it 'does not load the sidebar' do
+ expect(assigns(:sidebar_wiki_entries)).to be_nil
+ expect(assigns(:sidebar_limited)).to be_nil
+ end
+ end
+
+ describe 'GET #history' do
+ before do
+ allow(controller)
+ .to receive(:can?)
+ .with(any_args)
+ .and_call_original
+
+ # The :create_wiki permission is irrelevant to reading history.
+ expect(controller)
+ .not_to receive(:can?)
+ .with(anything, :create_wiki, any_args)
+
+ allow(controller)
+ .to receive(:can?)
+ .with(anything, :read_wiki, any_args)
+ .and_return(allow_read_wiki)
+ end
+
+ shared_examples 'fetching history' do |expected_status|
+ before do
+ get :history, params: routing_params.merge(id: wiki_title)
+ end
+
+ it "returns status #{expected_status}" do
+ expect(response).to have_gitlab_http_status(expected_status)
+ end
+ end
+
+ it_behaves_like 'fetching history', :ok do
+ let(:allow_read_wiki) { true }
+
+ it 'assigns @page_versions' do
+ expect(assigns(:page_versions)).to be_present
+ end
+ end
+
+ it_behaves_like 'fetching history', :not_found do
+ let(:allow_read_wiki) { false }
+ end
+ end
+
+ describe 'GET #show' do
+ render_views
+
+ let(:random_title) { nil }
+
+ subject { get :show, params: routing_params.merge(id: id, random_title: random_title) }
+
+ context 'when page exists' do
+ let(:id) { wiki_title }
+
+ it 'renders the page' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:page).title).to eq(wiki_title)
+ expect(assigns(:sidebar_wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
+ expect(assigns(:sidebar_limited)).to be(false)
+ end
+
+ context 'when page content encoding is invalid' do
+ it 'sets flash error' do
+ allow(controller).to receive(:valid_encoding?).and_return(false)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(flash[:notice]).to eq(_('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'))
+ end
+ end
+ end
+
+ context 'when the page does not exist' do
+ let(:id) { 'does not exist' }
+
+ before do
+ subject
+ end
+
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
+
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
+ end
+ end
+
+ context 'when page is a file' do
+ include WikiHelpers
+
+ let(:id) { upload_file_to_wiki(container, user, file_name) }
+
+ context 'when file is an image' do
+ let(:file_name) { 'dk.png' }
+
+ it 'delivers the image' do
+ subject
+
+ expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ end
+
+ context 'when file is a svg' do
+ let(:file_name) { 'unsanitized.svg' }
+
+ it 'delivers the image' do
+ subject
+
+ expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ end
+ end
+
+ it_behaves_like 'project cache control headers' do
+ let(:project) { container }
+ end
+ end
+
+ context 'when file is a pdf' do
+ let(:file_name) { 'git-cheat-sheet.pdf' }
+
+ it 'sets the content type to sets the content response headers' do
+ subject
+
+ expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ end
+
+ it_behaves_like 'project cache control headers' do
+ let(:project) { container }
+ end
+ end
+ end
+ end
+
+ describe 'POST #preview_markdown' do
+ it 'renders json in a correct format' do
+ post :preview_markdown, params: routing_params.merge(id: 'page/path', text: '*Markdown* text')
+
+ expect(json_response.keys).to match_array(%w(body references))
+ end
+ end
+
+ describe 'GET #edit' do
+ subject { get(:edit, params: routing_params.merge(id: wiki_title)) }
+
+ context 'when page content encoding is invalid' do
+ it 'redirects to show' do
+ allow(controller).to receive(:valid_encoding?).and_return(false)
+
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first)
+ end
+ end
+
+ context 'when the page has nil content' do
+ let(:page) { create(:wiki_page) }
+
+ it 'redirects to show' do
+ allow(page).to receive(:content).and_return(nil)
+ allow(controller).to receive(:page).and_return(page)
+
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, page)
+ end
+ end
+
+ context 'when page content encoding is valid' do
+ render_views
+
+ it 'shows the edit page' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to include(s_('Wiki|Edit Page'))
+ end
+ end
+ end
+
+ describe 'PATCH #update' do
+ let(:new_title) { 'New title' }
+ let(:new_content) { 'New content' }
+
+ subject do
+ patch(:update,
+ params: routing_params.merge(
+ id: wiki_title,
+ wiki: { title: new_title, content: new_content }
+ ))
+ end
+
+ context 'when page content encoding is invalid' do
+ it 'redirects to show' do
+ allow(controller).to receive(:valid_encoding?).and_return(false)
+
+ subject
+ expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first)
+ end
+ end
+
+ context 'when page content encoding is valid' do
+ render_views
+
+ it 'updates the page' do
+ subject
+
+ wiki_page = wiki.list_pages(load_content: true).first
+
+ expect(wiki_page.title).to eq new_title
+ expect(wiki_page.content).to eq new_content
+ end
+ end
+ end
+
+ def redirect_to_wiki(wiki, page)
+ redirect_to(controller.wiki_page_path(wiki, page))
+ end
+end
diff --git a/spec/tasks/gitlab/container_registry_rake_spec.rb b/spec/tasks/gitlab/container_registry_rake_spec.rb
index 41fc348aa24..181d5c8b7c8 100644
--- a/spec/tasks/gitlab/container_registry_rake_spec.rb
+++ b/spec/tasks/gitlab/container_registry_rake_spec.rb
@@ -16,10 +16,21 @@ describe 'gitlab:container_registry namespace rake tasks' do
stub_container_registry_config(enabled: true, api_url: api_url)
end
+ subject { run_rake_task('gitlab:container_registry:configure') }
+
shared_examples 'invalid config' do
it 'does not update the application settings' do
- expect { run_rake_task('gitlab:container_registry:configure') }
- .to raise_error(/Registry is not enabled or registry api url is not present./)
+ expect(application_settings).not_to receive(:update!)
+
+ subject
+ end
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'prints a warning message' do
+ expect { subject }.to output(/Registry is not enabled or registry api url is not present./).to_stdout
end
end
diff --git a/yarn.lock b/yarn.lock
index bbad72ea8fd..92a9fbfdf05 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -782,10 +782,10 @@
eslint-plugin-vue "^6.2.1"
vue-eslint-parser "^7.0.0"
-"@gitlab/svgs@1.135.0":
- version "1.135.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.135.0.tgz#b190f50c0a744d3b915f11defcdd17d7ae585928"
- integrity sha512-ziNYtJ6NXk/XVbptKvgdyVqbYocisPK63oRvWldYDSi/H8IMpdBo0upe+VhjepoEzxuUZ1yxc8Y1JMJ+RTpM+w==
+"@gitlab/svgs@1.137.0":
+ version "1.137.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.137.0.tgz#49fb1f33340cfdf0a47c83b7a613e3c7306dd53c"
+ integrity sha512-dhyiedyTKYJt/mXV+PjfY2pivAAPh3BAOHpVzNCZj6HmJ9VZFIJDzOAQTTxlxRz4UyPmHPuCiaal63q+JfLzcQ==
"@gitlab/ui@16.2.0":
version "16.2.0"