diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 5b1930faabba6f92807ea93ab7073bfaf99f1397..acee30867d90c39b64012abf5fedf0e3b5062998 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -239,6 +239,7 @@ static-analysis: dependencies: ["setup-test-env", "compile-assets pull-cache"] variables: SETUP_DB: "false" + parallel: 2 script: - scripts/static-analysis cache: diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index a181d17d743a69a4783bfce7e89fa19df1817ca2..c6afe2c824cffc44ddda23c8340086c52cf1e42b 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -343,6 +343,8 @@ linters: - 'app/views/projects/triggers/_index.html.haml' - 'app/views/projects/triggers/_trigger.html.haml' - 'app/views/projects/triggers/edit.html.haml' + - 'app/views/projects/wikis/_new.html.haml' + - 'app/views/projects/wikis/_pages_wiki_page.html.haml' - 'app/views/projects/wikis/edit.html.haml' - 'app/views/projects/wikis/history.html.haml' - 'app/views/repository_check_mailer/notify.html.haml' diff --git a/.overcommit.yml.example b/.overcommit.yml.example index 25823b9a8b34fe62f446111f9f03e4db041e8a4d..9cd04825bc252a2edbd0a43498ea393863a7d90b 100644 --- a/.overcommit.yml.example +++ b/.overcommit.yml.example @@ -16,10 +16,25 @@ # Uncomment the following lines to make the configuration take effect. PreCommit: + AuthorName: + enabled: false + EsLint: + enabled: true + # https://github.com/sds/overcommit/issues/338 + command: './node_modules/eslint/bin/eslint.js' + HamlLint: + enabled: true + MergeConflicts: + enabled: true + exclude: + - '**/conflict/file_spec.rb' + - '**/git/conflict/parser_spec.rb' + # prettier? https://github.com/sds/overcommit/issues/614 https://github.com/sds/overcommit/issues/390#issuecomment-495703284 RuboCop: enabled: true # on_warn: fail # Treat all warnings as failures -# + ScssLint: + enabled: true #PostCheckout: # ALL: # Special hook name that customizes all hooks of this type # quiet: true # Change all post-checkout hooks to only display output on failure diff --git a/app/assets/javascripts/pages/projects/wiki_directories/index.js b/app/assets/javascripts/pages/projects/wiki_directories/index.js deleted file mode 100644 index b055b7cc322131b519978bc0b4a9cd68a265f09a..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/pages/projects/wiki_directories/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// currently, this controller inherits all behaviors from wikis -import '../wikis/index'; diff --git a/app/assets/javascripts/pages/projects/wiki_pages/index.js b/app/assets/javascripts/pages/projects/wiki_pages/index.js deleted file mode 100644 index 92549cb648c175f5e83b0ec54d183585f9dc3b1c..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/pages/projects/wiki_pages/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import $ from 'jquery'; -import Vue from 'vue'; - -import Translate from '~/vue_shared/translate'; -import csrf from '~/lib/utils/csrf'; -import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; -import ZenMode from '~/zen_mode'; -import GLForm from '~/gl_form'; - -import deleteWikiModal from '../wikis/components/delete_wiki_modal.vue'; -import Wikis from '../wikis/wikis'; - -document.addEventListener('DOMContentLoaded', () => { - new Wikis(); // eslint-disable-line no-new - new ShortcutsWiki(); // eslint-disable-line no-new - new ZenMode(); // eslint-disable-line no-new - new GLForm($('.wiki-form')); // eslint-disable-line no-new - - const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper'); - - if (deleteWikiModalWrapperEl) { - Vue.use(Translate); - - const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset; - - // eslint-disable-next-line no-new - new Vue({ - el: deleteWikiModalWrapperEl, - data: { - deleteWikiUrl: '', - }, - render(createElement) { - return createElement(deleteWikiModal, { - props: { - pageTitle, - deleteWikiUrl, - csrfToken: csrf.token, - }, - }); - }, - }); - } -}); diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index 1b0d8fb4b71dd2b1e57b6fddf26af95ba97db22e..f5fd84d69ac0813847b753b445f79a75c54ce861 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -1,11 +1,41 @@ import $ from 'jquery'; +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; +import csrf from '~/lib/utils/csrf'; import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; -import GLForm from '~/gl_form'; - import Wikis from './wikis'; +import ZenMode from '../../../zen_mode'; +import GLForm from '../../../gl_form'; +import deleteWikiModal from './components/delete_wiki_modal.vue'; document.addEventListener('DOMContentLoaded', () => { new Wikis(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new + new ZenMode(); // eslint-disable-line no-new new GLForm($('.wiki-form')); // eslint-disable-line no-new + + const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper'); + + if (deleteWikiModalWrapperEl) { + Vue.use(Translate); + + const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el: deleteWikiModalWrapperEl, + data: { + deleteWikiUrl: '', + }, + render(createElement) { + return createElement(deleteWikiModal, { + props: { + pageTitle, + deleteWikiUrl, + csrfToken: csrf.token, + }, + }); + }, + }); + } }); diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js index 1bf799a5ee0c9727c4560585ab8c1b52360fa136..d41199f6374800bc34330f29b5424f5381a5c091 100644 --- a/app/assets/javascripts/pages/projects/wikis/wikis.js +++ b/app/assets/javascripts/pages/projects/wikis/wikis.js @@ -12,8 +12,8 @@ export default class Wikis { } this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page')); - this.editTitleInput = document.querySelector('form.wiki-form #wiki_page_title'); - this.commitMessageInput = document.querySelector('form.wiki-form #wiki_page_message'); + this.editTitleInput = document.querySelector('form.wiki-form #wiki_title'); + this.commitMessageInput = document.querySelector('form.wiki-form #wiki_message'); this.commitMessageI18n = this.isNewWikiPage ? s__('WikiPageCreate|Create %{pageTitle}') : s__('WikiPageEdit|Update %{pageTitle}'); diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index f73d1b41f84e1e9a1f54288df6de31b4af397ef1..a53f5d859496b4c272e5f8ca30c1fcd4eba5f1c0 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -99,18 +99,3 @@ justify-content: center; color: $gray-700; } - -.svg-icon { - display: inline-flex; - align-self: center; - - svg { - height: 1em; - width: 1em; - } - - &.svg-baseline svg { - top: 0.125em; - position: relative; - } -} diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 8f1b4cdb6d5ea1b64c15ddb21b5c647c7b1f8a43..0b65b915abf80a5c4188a941ccd323771457329b 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -1,31 +1,3 @@ -.new-wiki-page { - .new-wiki-page-slug-tip { - display: inline-block; - max-width: 100%; - margin-top: 5px; - } -} - -.wiki-history, -.wiki-page, -.edit-wiki-page { - margin-right: $gutter-width; -} - -.edit-wiki-page { - @media only screen and (min-width: map-get($grid-breakpoints, lg) + (2 * $gutter-width)) and (max-width: map-get($grid-breakpoints, lg) + (3 * $gutter-width)) { - margin-right: $gutter-width * 1.5; - } -} - -.wiki-form { - .edit-wiki-page-slug-tip { - display: inline-block; - max-width: 100%; - margin-top: 5px; - } -} - .title .edit-wiki-header { width: 780px; margin-left: auto; @@ -33,15 +5,6 @@ padding-right: 7px; } -.container-fluid.wiki-page, -.container-fluid.edit-wiki-page { - width: initial; -} - -.wiki-history.breadcrumbs { - min-height: (2 * $gl-padding) + 22; -} - .wiki-page-header { position: relative; @@ -116,7 +79,7 @@ } .sidebar-container { - padding-bottom: $gl-padding; + padding: $gl-padding 0; width: calc(100% + 100px); padding-right: 100px; height: 100%; @@ -162,7 +125,7 @@ } .wiki-sidebar-header { - padding: $gl-padding; + padding: 0 $gl-padding $gl-padding; .gutter-toggle { margin-top: 0; diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index 4189b8dcf96bb9fbf92b1a94e8105f0b0f0be397..1d84ddfba976dcd8f899586c4f0d4be5e2540a75 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -9,10 +9,11 @@ module PreviewMarkdown markdown_params = case controller_name + when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] } when 'snippets' then { skip_project_check: true } when 'groups' then { group: group } when 'projects' then projects_filter_params - else preview_markdown_params + else {} end render json: { @@ -24,7 +25,6 @@ module PreviewMarkdown } } end - # rubocop:enable Gitlab/ModuleWithInstanceVariables private @@ -35,12 +35,8 @@ module PreviewMarkdown } end - # Override this method to customise the markdown for your controller - def preview_markdown_params - {} - end - def markdown_service_params params end + # rubocop:enable Gitlab/ModuleWithInstanceVariables end diff --git a/app/controllers/concerns/project_wiki_actions.rb b/app/controllers/concerns/project_wiki_actions.rb deleted file mode 100644 index 304e16a894adaf014e031100e7c0d9ad1abaf378..0000000000000000000000000000000000000000 --- a/app/controllers/concerns/project_wiki_actions.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -# Controllers that include this concern must provide: -# * project -# * current_user -module ProjectWikiActions - extend ActiveSupport::Concern - - included do - before_action :authorize_read_wiki! - before_action :init_wiki_actions - - attr_accessor :project_wiki, :sidebar_page, :sidebar_wiki_entries - end - - def init_wiki_actions - load_project_wiki - load_wiki_sidebar - rescue ProjectWiki::CouldNotCreateWikiError - flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") - redirect_to project_path(project) - end - - def load_project_wiki - self.project_wiki = load_wiki - end - - def load_wiki_sidebar - self.sidebar_page = project_wiki.find_sidebar(params[:version_id]) - - return if sidebar_page.present? - - # Fallback to default sidebar - self.sidebar_wiki_entries = WikiDirectory.group_by_directory(project_wiki.list_pages(limit: 15)) - end - - def load_wiki - # Call #wiki to make sure the Wiki Repo is initialized - ProjectWiki.new(project, current_user).tap(&:wiki) - end -end diff --git a/app/controllers/projects/wiki_directories_controller.rb b/app/controllers/projects/wiki_directories_controller.rb deleted file mode 100644 index f66da60f4980e18f9166bad83775185d221588d2..0000000000000000000000000000000000000000 --- a/app/controllers/projects/wiki_directories_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -class Projects::WikiDirectoriesController < Projects::ApplicationController - include ProjectWikiActions - - def self.local_prefixes - [controller_path, 'shared/wiki'] - end - - def show - @wiki_dir = find_dir || WikiDirectory.new(params[:id]) - - return render('empty') if @wiki_dir.empty? - - @wiki_entries = @wiki_pages = Kaminari - .paginate_array(@wiki_dir.pages) - .page(params[:page]) - - render 'show' - end - - private - - def find_dir - dir_params = params.permit(:id, :sort, :direction).dup.tap do |h| - Gitlab::Utils.allow_hash_values(h, sort_params_config[:allowed]) - end - - project_wiki.find_dir(dir_params[:id], dir_params[:sort], dir_params[:direction]) - end -end diff --git a/app/controllers/projects/wiki_pages_controller.rb b/app/controllers/projects/wiki_pages_controller.rb deleted file mode 100644 index 0ba397b08f8df9cc6d30bb8c9a57f759d1f8236c..0000000000000000000000000000000000000000 --- a/app/controllers/projects/wiki_pages_controller.rb +++ /dev/null @@ -1,180 +0,0 @@ -# frozen_string_literal: true - -class Projects::WikiPagesController < Projects::ApplicationController - include ProjectWikiActions - include SendsBlob - include PreviewMarkdown - include Gitlab::Utils::StrongMemoize - - def self.local_prefixes - [controller_path, 'shared/wiki'] - end - - before_action :authorize_create_wiki!, only: [:edit, :create, :update] - before_action :authorize_admin_wiki!, only: :destroy - - before_action :load_page, only: [:show, :edit, :update, :history, :destroy] - before_action :valid_encoding?, - if: -> { %w[show edit update].include?(action_name) && load_page } - 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 - - # `#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 - show_page - elsif file_blob - show_blob - elsif should_create_missing_page? - create_missing_page - else - render 'missing_page' - end - end - - def edit - end - - def update - @page = WikiPages::UpdateService - .new(@project, current_user, wiki_params) - .execute(@page) - - return saved(:updated) if @page.valid? - - render 'edit' - rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e - @error = e - render 'edit' - end - - def create - @page = WikiPages::CreateService - .new(@project, current_user, wiki_params) - .execute - - return saved(:created) if @page.persisted? - - render action: "edit" - rescue Gitlab::Git::Wiki::OperationError => e - @page = project_wiki.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(@project, current_user).execute(@page) - - redirect_to project_wiki_path(@project, :home), - status: 302, - notice: _("Page was successfully deleted") - rescue Gitlab::Git::Wiki::OperationError => e - @error = e - render 'edit' - end - - private - - # Callback for PreviewMarkdown - def preview_markdown_params - { pipeline: :wiki, project_wiki: project_wiki, page_slug: params[:id] } - end - - def show_page - set_encoding_error unless valid_encoding? - - @page_dir = @project_wiki.find_dir(@page.directory) if @page.directory.present? - @show_children = true - - render 'show' - end - - def show_blob - send_blob(@project_wiki.repository, file_blob) - end - - def should_create_missing_page? - view_param = @project_wiki.exists? ? 'create' : params[:view] - view_param == 'create' && can?(current_user, :create_wiki, @project) - end - - def create_missing_page - # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new - title = params[:id] unless params[:random_title].present? - - @page = project_wiki.build_page(title: title) - - render 'edit' - end - - def wiki_params - params.require(:wiki_page).permit(:title, :content, :format, :message, :last_commit_sha) - end - - def load_page - @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? - strong_memoize(:valid_encoding) do - @page.content.encoding == Encoding::UTF_8 - end - 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 - - def saved(action) - msg = case action - when :updated - _('Wiki was successfully updated') - when :created - _('Wiki was successfully created') - end - - redirect_to(project_wiki_path(@project, @page), notice: msg) - end -end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index d531d2567217d4ed96360e1d38bc481ec543890b..b187fdb27232fb15c079797c2325f616626c373b 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -1,28 +1,120 @@ # frozen_string_literal: true class Projects::WikisController < Projects::ApplicationController - include ProjectWikiActions - include WikiHelper + include PreviewMarkdown + include SendsBlob + include Gitlab::Utils::StrongMemoize - def self.local_prefixes - [controller_path, 'shared/wiki'] + 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 :valid_encoding?, + if: -> { %w[show edit update].include?(action_name) && load_page } + 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 - @nesting = show_children_param - @show_children = @nesting != ProjectWiki::NESTING_CLOSED @wiki_pages = Kaminari.paginate_array( - project_wiki.list_pages(**sort_params) + @project_wiki.list_pages(sort: params[:sort], direction: params[:direction]) ).page(params[:page]) - @wiki_entries = case @nesting - when ProjectWiki::NESTING_FLAT - @wiki_pages - else - WikiDirectory.group_by_directory(@wiki_pages) - end + @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? + + render 'show' + elsif file_blob + send_blob(@project_wiki.repository, file_blob) + 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(@project, current_user, 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(@project, current_user, 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(@project, current_user).execute(@page) - render 'show' + redirect_to project_wiki_path(@project, :home), + status: 302, + notice: _("Page was successfully deleted") + rescue Gitlab::Git::Wiki::OperationError => e + @error = e + render 'edit' end def git_access @@ -30,13 +122,74 @@ class Projects::WikisController < Projects::ApplicationController private - def sort_params - process_params(sort_params_config) + 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 show_children_param - config = nesting_params_config(params[:sort]) + 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 = WikiPage.group_by_directory(@project_wiki.list_pages(limit: 15)) + 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 ||= @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? + strong_memoize(:valid_encoding) do + @page.content.encoding == Encoding::UTF_8 + end + 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 - process_params(config) + @project_wiki.repository.blob_at(commit.id, params[:id]) + end end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 7b5a1a02b989f44b63c7e203b8cd8d65ab649f11..f90ad0c300c62675c4a36377795ff35054ae92c1 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -290,6 +290,7 @@ module ApplicationSettingsHelper :snowplow_cookie_domain, :snowplow_enabled, :snowplow_site_id, + :snowplow_iglu_registry_url, :push_event_hooks_limit, :push_event_activities_limit, :custom_http_clone_url_root, diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 66b88f6b6268f39dd0f3f6552a585eaf9c8207dd..4f73270577fc60683526a4c514978e05a4615247 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -41,12 +41,6 @@ module IconsHelper ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url) end - def sprite_icon_with_text(icon_name, content, opts = {}) - wrapper_class = opts.delete(:wrapper_class) - icon = sprite_icon(icon_name, opts) - content_tag(:span, [icon, content].join('').html_safe, class: wrapper_class) - end - def sprite_icon(icon_name, size: nil, css_class: nil) if Gitlab::Sentry.should_raise_for_dev? unless known_sprites.include?(icon_name) diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb index 3cf610cbd38e13e7ebd2c5f5dcea59e447014d49..dd8fde2a697a13a450cc4a1116db195320fc3dd4 100644 --- a/app/helpers/wiki_helper.rb +++ b/app/helpers/wiki_helper.rb @@ -48,23 +48,14 @@ module WikiHelper expose_url(api_v4_projects_wikis_attachments_path(id: @project.id)) end - WIKI_SORT_CSS_CLASSES = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' - - def wiki_sort_controls(sort_params = {}, &block) - current_sort = sort_params[:sort] || ProjectWiki::TITLE_ORDER - current_direction = (sort_params[:direction] || 'asc').inquiry - - reversed_direction = current_direction.desc? ? 'asc' : 'desc' - icon_class = current_direction.desc? ? 'highest' : 'lowest' - - sorting = sort_params.merge(sort: current_sort, direction: reversed_direction) - opts = { - type: 'button', - class: WIKI_SORT_CSS_CLASSES, - title: _('Sort direction') - } - - link_to(yield(sorting), opts) do + def wiki_sort_controls(project, sort, direction) + sort ||= ProjectWiki::TITLE_ORDER + link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort' + reversed_direction = direction == 'desc' ? 'asc' : 'desc' + icon_class = direction == 'desc' ? 'highest' : 'lowest' + + link_to(project_wikis_pages_path(project, sort: sort, direction: reversed_direction), + type: 'button', class: link_class, title: _('Sort direction')) do sprite_icon("sort-#{icon_class}", size: 16) end end @@ -76,86 +67,4 @@ module WikiHelper s_("Wiki|Title") end end - - # Render the sprite icon given the current show_children state - def wiki_show_children_icon(nesting) - icon_name, icon_text = - case nesting - when ProjectWiki::NESTING_TREE - ['folder-open', s_("Wiki|Show folder contents")] - when ProjectWiki::NESTING_CLOSED - ['folder-o', s_("Wiki|Hide folder contents")] - else - ['list-bulleted', s_("Wiki|Show files separately")] - end - - sprite_icon_with_text(icon_name, icon_text, size: 16) - end - - def wiki_page_link(wiki_page, nesting, project) - link = link_to(wiki_page.title, - project_wiki_path(project, wiki_page), - class: 'wiki-page-title') - - case nesting - when ProjectWiki::NESTING_FLAT - tags = [] - if wiki_page.directory.present? - wiki_dir = WikiDirectory.new(wiki_page.directory) - tags << link_to(wiki_dir.slug, project_wiki_dir_path(project, wiki_dir), class: 'wiki-page-dir-name') - tags << content_tag(:span, '/', class: 'wiki-page-name-separator') - end - - tags << link - tags.join.html_safe - else - link - end - end - - def sort_params_config - { - keys: [:sort, :direction], - defaults: { - sort: ProjectWiki::TITLE_ORDER, direction: ProjectWiki::DIRECTION_ASC - }, - allowed: { - sort: ProjectWiki::SORT_ORDERS, direction: ProjectWiki::SORT_DIRECTIONS - } - } - end - - def nesting_params_config(sort_key) - default_val = case sort_key - when ProjectWiki::CREATED_AT_ORDER - ProjectWiki::NESTING_FLAT - else - ProjectWiki::NESTING_CLOSED - end - { - keys: [:show_children], - defaults: { show_children: default_val }, - allowed: { show_children: ProjectWiki::NESTINGS } - } - end - - def process_params(config) - unprocessed = params.permit(*config[:keys]) - - processed = unprocessed - .with_defaults(config[:defaults]) - .tap { |h| Gitlab::Utils.allow_hash_values(h, config[:allowed]) } - .to_hash - .transform_keys(&:to_sym) - - if processed.keys == config[:keys] - processed.size == 1 ? processed.values.first : processed - else - raise ActionController::BadRequest, "illegal parameters: #{unprocessed}" - end - end - - def home_page? - params[:id] == 'home' - end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 4ecc9bc2153baa5c965e8ef208b7a2d418efd125..cfae79ec016dc96c2fbcb93e8a3e70033b4d54b4 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -104,6 +104,11 @@ class ApplicationSetting < ApplicationRecord hostname: true, if: :snowplow_enabled + validates :snowplow_iglu_registry_url, + addressable_url: true, + allow_blank: true, + if: :snowplow_enabled + validates :pendo_url, presence: true, public_url: true, diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 0312183d11f1be4c10270669a4ac0039368aa3f3..6cc77cca8a334c6db225fe57bd6ea413d99f38d4 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -129,6 +129,7 @@ module ApplicationSettingImplementation snowplow_cookie_domain: nil, snowplow_enabled: false, snowplow_site_id: nil, + snowplow_iglu_registry_url: nil, custom_http_clone_url_root: nil, pendo_enabled: false, pendo_url: nil diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 7ccd5e98360d17f1fe8a2712a32226131c821502..698988e92955aa6af7a3988d77c49221faae5fcb 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -75,6 +75,11 @@ class Deployment < ApplicationRecord find(ids) end + def self.distinct_on_environment + order('environment_id, deployments.id DESC') + .select('DISTINCT ON (environment_id) deployments.*') + end + def self.find_successful_deployment!(iid) success.find_by!(iid: iid) end diff --git a/app/models/environment.rb b/app/models/environment.rb index b426941d8afc70bb655d7b82e7630f4e8cd9526d..602dce89c4af3d496ab35e4053d6c0f4455d74eb 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -10,6 +10,7 @@ class Environment < ApplicationRecord has_many :successful_deployments, -> { success }, class_name: 'Deployment' has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment' + has_one :last_visible_deployment, -> { visible.distinct_on_environment }, class_name: 'Deployment' before_validation :nullify_external_url before_validation :generate_slug, if: ->(env) { env.slug.blank? } diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index e0fcebf2642b82e91cdfce09e974936ca644831f..bb222ac7629776496b7dc558f83d06b8854fa567 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -17,13 +17,6 @@ class ProjectWiki CREATED_AT_ORDER = 'created_at' DIRECTION_DESC = 'desc' DIRECTION_ASC = 'asc' - SORT_ORDERS = [TITLE_ORDER, CREATED_AT_ORDER].freeze - SORT_DIRECTIONS = [DIRECTION_ASC, DIRECTION_DESC].freeze - - NESTING_FLAT = 'flat' - NESTING_TREE = 'tree' - NESTING_CLOSED = 'hidden' - NESTINGS = [NESTING_TREE, NESTING_CLOSED, NESTING_FLAT].freeze # Returns a string describing what went wrong after # an operation fails. @@ -65,11 +58,7 @@ class ProjectWiki end def wiki_base_path - ::File.join(project_base_path, 'wikis') - end - - def wiki_page_path - ::File.join(project_base_path, '-', 'wiki_pages') + [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('') end # Returns the Gitlab::Git::Wiki object. @@ -136,23 +125,6 @@ class ProjectWiki end end - # Finds directory within the repository based on a slug - # - # dir_name - The directory prefix. - # - # Returns an initialized WikiDirectory instance or nil - def find_dir(dir_name, sort = nil, direction = DIRECTION_ASC) - descending = direction == DIRECTION_DESC - # WikiListPagesRequest currently does not support server-side - # filtering. Ideally this logic should be moved to the gitaly - # side. - pages = wiki - .list_pages(sort: sort, direction_desc: descending) - .map { |page| WikiPage.new(self, page, true) } - .select { |wp| wp.directory == dir_name } - WikiDirectory.new(dir_name, pages) if pages.present? - end - def find_sidebar(version = nil) find_page(SIDEBAR, version) end @@ -172,12 +144,6 @@ class ProjectWiki false end - def build_page(attrs) - WikiPage.new(self).tap do |page| - page.update_attributes(attrs) # rubocop:disable Rails/ActiveRecordAliases - end - end - def update_page(page, content:, title: nil, format: :markdown, message: nil) commit = commit_details(:updated, message, page.title) @@ -205,7 +171,7 @@ class ProjectWiki title_array = title.split("/") title = title_array.pop - [title, ::File.join(title_array)] + [title, title_array.join("/")] end def repository @@ -232,10 +198,6 @@ class ProjectWiki private - def project_base_path - ::File.join(Gitlab.config.gitlab.relative_url_root, @project.full_path) - end - def create_repo!(raw_repository) gitlab_shell.create_wiki_repository(project) diff --git a/app/models/wiki_directory.rb b/app/models/wiki_directory.rb index 6c86c11e7fbea0d37b0988cf0e8c381141c5be26..712ba79bbd2595256a0822c6715ec31a1869ce3a 100644 --- a/app/models/wiki_directory.rb +++ b/app/models/wiki_directory.rb @@ -1,75 +1,20 @@ # frozen_string_literal: true class WikiDirectory - include StaticModel include ActiveModel::Validations attr_accessor :slug, :pages validates :slug, presence: true - # StaticModel overrides and configuration: - - def self.primary_key - 'slug' - end - - def id - "#{slug}@#{last_version&.sha}" - end - - def self.model_name - ActiveModel::Name.new(self, nil, 'wiki_dir') - end - - alias_method :to_param, :slug - alias_method :title, :slug - - # Sorts and groups pages by directory. - # - # pages - an array of WikiPage objects. - # - # Returns an array of WikiPage and WikiDirectory objects. - # The entries are sorted in the order of the input array, where - # directories appear in the position of their first member. - def self.group_by_directory(pages) - grouped = [] - dirs = Hash.new do |h, k| - new(k).tap { |dir| grouped << (h[k] = dir) } - end - - Array.wrap(pages).each_with_object(grouped) do |page, top_level| - group = page.directory.present? ? dirs[page.directory] : top_level - - group << page - end - end - def initialize(slug, pages = []) @slug = slug @pages = pages end - def <<(page) - @pages << page - @last_version = nil - end - - def last_version - @last_version ||= @pages.map(&:last_version).max_by(&:authored_date) - end - - def page_count - @pages.size - end - - def empty? - page_count.zero? - end - - def to_partial_path(context = nil) - name = [context, 'wiki_directory'].compact.join('_') - - "projects/wiki_directories/#{name}" + # Relative path to the partial to be used when rendering collections + # of this object. + def to_partial_path + 'projects/wikis/wiki_directory' end end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 3006753e9e75840914f566e531af699d284c76ec..68241d2bd95f384d57574efcf09cff0396c5f6d3 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -15,7 +15,30 @@ class WikiPage end def self.model_name - ActiveModel::Name.new(self, nil, 'wiki_page') + ActiveModel::Name.new(self, nil, 'wiki') + end + + # Sorts and groups pages by directory. + # + # pages - an array of WikiPage objects. + # + # Returns an array of WikiPage and WikiDirectory objects. The entries are + # sorted by alphabetical order (directories and pages inside each directory). + # Pages at the root level come before everything. + def self.group_by_directory(pages) + return [] if pages.blank? + + pages.each_with_object([]) do |page, grouped_pages| + next grouped_pages << page unless page.directory.present? + + directory = grouped_pages.find do |obj| + obj.is_a?(WikiDirectory) && obj.slug == page.directory + end + + next directory.pages << page if directory + + grouped_pages << WikiDirectory.new(page.directory, [page]) + end end def self.unhyphenize(name) @@ -43,16 +66,6 @@ class WikiPage Gitlab::HookData::WikiPageBuilder.new(self).build end - # Create a new WikiPage - # - # == Parameters: - # wiki:: - # A `ProjectWiki` model object - # page:: - # A `Gitlab::Git::WikiPage` business object, to which this class provides a facade - # persisted:: - # Is this page fully saved on disk? - # def initialize(wiki, page = nil, persisted = false) @wiki = wiki @page = page @@ -243,10 +256,10 @@ class WikiPage end end - def to_partial_path(context = nil) - name = [context, 'wiki_page'].compact.join('_') - - "projects/wiki_pages/#{name}" + # Relative path to the partial to be used when rendering collections + # of this object. + def to_partial_path + 'projects/wikis/wiki_page' end def id diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index c136803ef3bea95441ca04593425846cf033c368..9e6cbfa06fef980adc02f74ded23c8dc3ea7da49 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -42,6 +42,10 @@ module Notes clear_noteable_diffs_cache(note) Suggestions::CreateService.new(note).execute increment_usage_counter(note) + + if Feature.enabled?(:notes_create_service_tracking, project) + Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note)) + end end if quick_actions_service.commands_executed_count.to_i > 0 @@ -59,5 +63,16 @@ module Notes note end + + private + + def tracking_data_for(note) + label = Gitlab.ee? && note.author == User.visual_review_bot ? 'anonymous_visual_review_note' : 'note' + + { + label: label, + value: note.id + } + end end end diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml index 31fd12d191e76c3316a496de118de602b5537b9b..dd454ce5dd7114f7bd9742a949295cb5b311e591 100644 --- a/app/views/admin/application_settings/_snowplow.html.haml +++ b/app/views/admin/application_settings/_snowplow.html.haml @@ -26,5 +26,8 @@ .form-group = f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light' = f.text_field :snowplow_cookie_domain, class: 'form-control' + .form-group + = f.label :snowplow_iglu_registry_url, _('Iglu registry URL (optional)'), class: 'label-light' + = f.text_field :snowplow_iglu_registry_url, class: 'form-control' = f.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index fdad2a64a80c35cfe51ac98237474ab564334824..2c0cb9735427b3e0f8992e288bfd3a0b78a62057 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -264,7 +264,7 @@ dismiss_endpoint: user_callouts_path } } - if show_cluster_hint .feature-highlight-popover-content - = image_tag 'illustrations/cluster_popover.svg', class: 'feature-highlight-illustration' + = image_tag 'illustrations/cluster_popover.svg', class: 'feature-highlight-illustration', lazy: false, alt: _('Kubernetes popover') .feature-highlight-popover-sub-content %p= _('Allows you to add and manage Kubernetes clusters.') %p @@ -282,14 +282,14 @@ - if project_nav_tab? :wiki - wiki_url = project_wiki_path(@project, :home) - = nav_link(controller: [:wikis, :wiki_pages, :wiki_directories]) do + = nav_link(controller: :wikis) do = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do .nav-icon-container = sprite_icon('book') %span.nav-item-name = _('Wiki') %ul.sidebar-sub-level-items.is-fly-out-only - = nav_link(controller: [:wikis, :wiki_pages, :wiki_directories], html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do = link_to wiki_url do %strong.fly-out-top-item-name = _('Wiki') diff --git a/app/views/projects/wiki_directories/_pages_wiki_directory.html.haml b/app/views/projects/wiki_directories/_pages_wiki_directory.html.haml deleted file mode 100644 index a9d2f38da88599dc1d87828d35a9785380e494bd..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_directories/_pages_wiki_directory.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -%li - %span.text-secondary-500.svg-icon.svg-baseline - - if @show_children - = sprite_icon('folder-open', size: 16) - - else - = sprite_icon('folder-o', size: 16) - - = link_to wiki_dir.slug, project_wiki_dir_path(@project, wiki_dir) - - unless @show_children - %span.badge.badge-pill.wiki-dir-page-count= wiki_dir.page_count - .float-right - %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_dir.last_version.authored_date) }).html_safe - - if @show_children - %ul - = render wiki_dir.pages, context: context diff --git a/app/views/projects/wiki_directories/_sidebar_wiki_directory.html.haml b/app/views/projects/wiki_directories/_sidebar_wiki_directory.html.haml deleted file mode 100644 index 2f62e9d4516e7e36a559113206ba8a3e945bf01b..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_directories/_sidebar_wiki_directory.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%li - %span.text-secondary-300.svg-icon.svg-baseline - = sprite_icon('folder-open', size: 16) - - = link_to wiki_dir.slug, project_wiki_dir_path(@project, wiki_dir) - %ul= render wiki_dir.pages, context: context - diff --git a/app/views/projects/wiki_directories/_wiki_directory.html.haml b/app/views/projects/wiki_directories/_wiki_directory.html.haml deleted file mode 100644 index 022c209c5bd5601e3047eb3df4c5520d2132cc95..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_directories/_wiki_directory.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render wiki_directory.to_partial_path(context), wiki_dir: wiki_directory, context: context diff --git a/app/views/projects/wiki_directories/empty.html.haml b/app/views/projects/wiki_directories/empty.html.haml deleted file mode 100644 index cab1c3acb60c46e6190efc7a36922a5c4edbc3fd..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_directories/empty.html.haml +++ /dev/null @@ -1,34 +0,0 @@ -- layout_path = 'shared/empty_states/wikis_layout' -- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) -- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) -- breadcrumb_title s_(@wiki_dir.slug) -- page_title @wiki_dir.slug - -- if can?(current_user, :create_wiki, @project) - - create_path = project_wiki_path(@project, params[:id], { view: 'create', params: { title: "#{params[:id]}/" } }) - - create_link = link_to s_('WikiDirEmpty|Create a page in this directory'), create_path, class: 'btn btn-success qa-create-first-page-link', title: s_('WikiDirEmpty|Create a page') - - = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do - %h4.text-left - = s_('WikiDirEmpty|This directory has no wiki pages') - %p.text-left - = s_("WikiDirEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.") - = create_link - -- elsif can?(current_user, :read_issue, @project) - - issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project) - - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn btn-success', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement') - - = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do - %h4 - = s_('WikiDirEmpty|This directory has no wiki pages') - %p.text-left - = s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link } - = new_issue_link - -- else - = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do - %h4 - = s_('WikiDirEmpty|This directory has no wiki pages') - %p - = s_('WikiEmpty|You must be a project member in order to add wiki pages.') diff --git a/app/views/projects/wiki_directories/show.html.haml b/app/views/projects/wiki_directories/show.html.haml deleted file mode 100644 index 4c7978d1216ac228297a4242fbdf960155e7b3ac..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_directories/show.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) -- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) -- breadcrumb_title s_(@wiki_dir.slug) -- page_title @wiki_dir.slug - -= render 'page_listing', { allow_change_nesting: false, wiki_page_title: page_title, page_path: ->(opts) { project_wiki_dir_path(@project, @wiki_dir, opts) } } diff --git a/app/views/projects/wiki_pages/_page_title.html.haml b/app/views/projects/wiki_pages/_page_title.html.haml deleted file mode 100644 index a3b077999e6603b501e67acabd946667e8332d9f..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_pages/_page_title.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -= link_to @page.human_title, project_wiki_path(@project, @page) -%span.light - = _('·').html_safe - = subtitle diff --git a/app/views/projects/wiki_pages/_pages_wiki_page.html.haml b/app/views/projects/wiki_pages/_pages_wiki_page.html.haml deleted file mode 100644 index c177d03bee119a552918b0d8ff422340a14cb41e..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_pages/_pages_wiki_page.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -%li - %span.text-secondary-500.svg-icon.svg-baseline= sprite_icon('book', size: 16) - = wiki_page_link(wiki_page, @nesting, @project) - .float-right - %span.badge.badge-pill.wiki-page-format= _(wiki_page.format) - - if wiki_page.last_version - = '/' - %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe diff --git a/app/views/projects/wiki_pages/_sidebar_wiki_page.html.haml b/app/views/projects/wiki_pages/_sidebar_wiki_page.html.haml deleted file mode 100644 index 205c3dd76bf87c238a5f8e47197a4814c4848042..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_pages/_sidebar_wiki_page.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- is_active = params[:id] == wiki_page.slug -- icon_active_class = is_active ? 'text-secondary-800' : 'text-secondary-300' - -%li{ class: active_when(is_active) } - %span.svg-icon.svg-baseline{ class: icon_active_class } - = sprite_icon('book', size: 16) - - if is_active - = wiki_page.human_title - - else - = link_to project_wiki_path(@project, wiki_page) do - = wiki_page.human_title diff --git a/app/views/projects/wiki_pages/_wiki_page.html.haml b/app/views/projects/wiki_pages/_wiki_page.html.haml deleted file mode 100644 index 947e96fed6b5d10e8125a70164f64cc9bd045285..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_pages/_wiki_page.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render wiki_page.to_partial_path(context), wiki_page: wiki_page diff --git a/app/views/projects/wiki_pages/show.html.haml b/app/views/projects/wiki_pages/show.html.haml deleted file mode 100644 index 879ddfb03c885005f369a5c7d83dae7ebabe4a2d..0000000000000000000000000000000000000000 --- a/app/views/projects/wiki_pages/show.html.haml +++ /dev/null @@ -1,34 +0,0 @@ -- @content_class = 'wiki-page' + (fluid_layout ? '' : ' limit-container-width') -- breadcrumb_title @page.human_title -- page_title @page.human_title, _("Wiki") -- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) -- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) -- if @page_dir.present? - - add_to_breadcrumbs _(@page_dir.slug), project_wiki_dir_path(@project, @page_dir) - -.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row - %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } - = icon('angle-double-left') - - .nav-text.flex-fill - %h2.wiki-page-title= @page.human_title - %span.wiki-last-edit-by - - if @page.last_version - = (_("Last edited by %{name}") % { name: "#{@page.last_version.author_name}" }).html_safe - #{time_ago_with_tooltip(@page.last_version.authored_date)} - - .nav-controls.pb-md-3.pb-lg-0 - = render 'main_links' - -- if @page.historical? - .warning_message - = s_("WikiHistoricalPage|This is an old version of this page.") - - most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page) - - history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page) - = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe - -.prepend-top-default.append-bottom-default - .md.md-file.qa-wiki-page-content - = render_wiki_content(@page) - -= render 'sidebar' diff --git a/app/views/projects/wiki_pages/_form.html.haml b/app/views/projects/wikis/_form.html.haml similarity index 91% rename from app/views/projects/wiki_pages/_form.html.haml rename to app/views/projects/wikis/_form.html.haml index 8f5757d6d98cc5caf9ed65434dfc73a4ac76a497..a153f527ee0fb5c153f77a2e4ebd9cf53b74a612 100644 --- a/app/views/projects/wiki_pages/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,11 +1,9 @@ - form_classes = 'wiki-form common-note-form prepend-top-default js-quick-submit' - form_classes += ' js-new-wiki-page' unless @page.persisted? -= form_for [@project.namespace.becomes(Namespace), @project, @page], - method: @page.persisted? ? 'put' : 'post', - url: { controller: 'wiki_pages', action: @page.persisted? ? :update : :create }, - html: { class: form_classes }, - data: { uploads_path: uploads_path } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, + html: { class: form_classes }, + data: { uploads_path: uploads_path } do |f| = form_errors(@page) - if @page.persisted? @@ -14,7 +12,7 @@ .form-group.row .col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12 - = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: s_('Wiki|Page title') + = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: _('Wiki|Page title') %span.d-inline-block.mw-100.prepend-top-5 = icon('lightbulb-o') - if @page.persisted? diff --git a/app/views/shared/wiki/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml similarity index 79% rename from app/views/shared/wiki/_main_links.html.haml rename to app/views/projects/wikis/_main_links.html.haml index 5e41bb6a9cddaf633ad321d9aa70e3115a99efc6..2e1e176c42a6f709530e8b88fea0ea3c07aa5492 100644 --- a/app/views/shared/wiki/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,6 +1,6 @@ - if (@page && @page.persisted?) - if can?(current_user, :create_wiki, @project) - = link_to project_wiki_pages_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do + = link_to project_wikis_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do = s_("Wiki|New page") = link_to project_wiki_history_path(@project, @page), class: "btn", role: "button" do = s_("Wiki|Page history") diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..c156f8cbf5032c8cbad2c31ee12a2d0ea6940073 --- /dev/null +++ b/app/views/projects/wikis/_pages_wiki_page.html.haml @@ -0,0 +1,6 @@ +%li + = link_to wiki_page.title, project_wiki_path(@project, wiki_page) + %small (#{wiki_page.format}) + .float-right + - if wiki_page.last_version + %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe diff --git a/app/views/shared/wiki/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml similarity index 100% rename from app/views/shared/wiki/_sidebar.html.haml rename to app/views/projects/wikis/_sidebar.html.haml diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..769d869bd537998762f575f58830b902cdcd0edd --- /dev/null +++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml @@ -0,0 +1,3 @@ +%li{ class: active_when(params[:id] == wiki_page.slug) } + = link_to project_wiki_path(@project, wiki_page) do + = wiki_page.human_title diff --git a/app/views/projects/wikis/_wiki_directory.html.haml b/app/views/projects/wikis/_wiki_directory.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..0e5f32ed859e3f5f4f1919cb9007176139f2b4e7 --- /dev/null +++ b/app/views/projects/wikis/_wiki_directory.html.haml @@ -0,0 +1,4 @@ +%li + = wiki_directory.slug + %ul + = render wiki_directory.pages, context: context diff --git a/app/views/projects/wikis/_wiki_page.html.haml b/app/views/projects/wikis/_wiki_page.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..c84d06dad02abe77d71f070ae9e176e50658d563 --- /dev/null +++ b/app/views/projects/wikis/_wiki_page.html.haml @@ -0,0 +1 @@ += render "#{context}_wiki_page", wiki_page: wiki_page diff --git a/app/views/projects/wiki_pages/edit.html.haml b/app/views/projects/wikis/edit.html.haml similarity index 63% rename from app/views/projects/wiki_pages/edit.html.haml rename to app/views/projects/wikis/edit.html.haml index 4e5f6a077a4b7623989157cde72dcdec5f031032..9ccf5acfefc080a00bd1b6b6d341a52ecf3ffa85 100644 --- a/app/views/projects/wiki_pages/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,10 +1,5 @@ -- @content_class = 'edit-wiki-page' + (fluid_layout ? '' : ' limit-container-width') -- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) -- add_to_breadcrumbs s_("Wiki|Pages"), project_wikis_pages_path(@project) -- if @page.persisted? && @page_dir.present? - - add_to_breadcrumbs _(@page_dir.slug), project_wiki_dir_path(@project, @page_dir) -- if @page.persisted? - - add_to_breadcrumbs @page.human_title, project_wiki_path(@project, @page) +- @content_class = "limit-container-width" unless fluid_layout +- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, @page) - breadcrumb_title @page.persisted? ? _("Edit") : _("New") - page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki") @@ -17,7 +12,10 @@ .nav-text %h2.wiki-page-title - if @page.persisted? - = render partial: 'page_title', locals: { subtitle: s_("Wiki|Edit Page") } + = link_to @page.human_title, project_wiki_path(@project, @page) + %span.light + · + = s_("Wiki|Edit Page") - else = s_("Wiki|Create New Page") diff --git a/app/views/projects/wiki_pages/missing_page.html.haml b/app/views/projects/wikis/empty.html.haml similarity index 100% rename from app/views/projects/wiki_pages/missing_page.html.haml rename to app/views/projects/wikis/empty.html.haml diff --git a/app/views/projects/wiki_pages/history.html.haml b/app/views/projects/wikis/history.html.haml similarity index 88% rename from app/views/projects/wiki_pages/history.html.haml rename to app/views/projects/wikis/history.html.haml index d60a32750dface4ae70c5fdb5e3e97d752fb32d0..d3a55c53649a1c4c06b52b63901e64026c1e87bc 100644 --- a/app/views/projects/wiki_pages/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,4 +1,3 @@ -- @content_class = 'wiki-history' - page_title _("History"), @page.human_title, _("Wiki") .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row @@ -7,7 +6,10 @@ .nav-text %h2.wiki-page-title - = render partial: 'page_title', locals: { subtitle: _("History") } + = link_to @page.human_title, project_wiki_path(@project, @page) + %span.light + · + = _("History") .table-holder %table.table @@ -37,4 +39,4 @@ = version.format = paginate @page_versions, theme: 'gitlab' -= render 'shared/wiki/sidebar' += render 'sidebar' diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..d9dcd8f9acd49c1af27102d80786f8bd21298d65 --- /dev/null +++ b/app/views/projects/wikis/pages.html.haml @@ -0,0 +1,32 @@ +- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) +- breadcrumb_title s_("Wiki|Pages") +- page_title s_("Wiki|Pages"), _("Wiki") +- sort_title = wiki_sort_title(params[:sort]) + +.wiki-page-header.top-area.flex-column.flex-lg-row + + .nav-text.flex-fill + %h2.wiki-page-title + = s_("Wiki|Wiki Pages") + + .nav-controls.pb-md-3.pb-lg-0 + = link_to project_wikis_git_access_path(@project), class: 'btn' do + = icon('cloud-download') + = _("Clone repository") + + .dropdown.inline.wiki-sort-dropdown + .btn-group{ role: 'group' } + .btn-group{ role: 'group' } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + = sort_title + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort + %li + = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title) + = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title) + = wiki_sort_controls(@project, params[:sort], params[:direction]) + +%ul.wiki-pages-list.content-list + = render @wiki_entries, context: 'pages' + += paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 9c732a933cf0455596ec1635cd8d07cc1c8956a9..ebd99cf8605ebfe6f5aeafa2f7418df7c091e959 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,5 +1,32 @@ -- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) -- breadcrumb_title s_("Wiki|Pages") -- page_title s_("Wiki|Contents"), _("Wiki") +- @content_class = "limit-container-width" unless fluid_layout +- breadcrumb_title @page.human_title +- wiki_breadcrumb_dropdown_links(@page.slug) +- page_title @page.human_title, _("Wiki") +- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home) -= render 'page_listing', { allow_change_nesting: ::Feature.enabled?(:wikis_allow_change_nesting), wiki_page_title: page_title, page_path: ->(opts) { project_wikis_pages_path(@project, opts) } } +.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + + .nav-text.flex-fill + %h2.wiki-page-title= @page.human_title + %span.wiki-last-edit-by + - if @page.last_version + = (_("Last edited by %{name}") % { name: "#{@page.last_version.author_name}" }).html_safe + #{time_ago_with_tooltip(@page.last_version.authored_date)} + + .nav-controls.pb-md-3.pb-lg-0 + = render 'main_links' + +- if @page.historical? + .warning_message + = s_("WikiHistoricalPage|This is an old version of this page.") + - most_recent_link = link_to s_("WikiHistoricalPage|most recent version"), project_wiki_path(@project, @page) + - history_link = link_to s_("WikiHistoricalPage|history"), project_wiki_history_path(@project, @page) + = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe + +.prepend-top-default.append-bottom-default + .md.md-file{ data: { qa_selector: 'wiki_page_content' } } + = render_wiki_content(@page) + += render 'sidebar' diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml index e05230de45782375aed31316c6928e251d178a4a..73eedcc1dc96ee15e1362e5927d950613119823e 100644 --- a/app/views/shared/empty_states/_wikis.html.haml +++ b/app/views/shared/empty_states/_wikis.html.haml @@ -1,11 +1,8 @@ - layout_path = 'shared/empty_states/wikis_layout' -- wiki_is_empty = @project_wiki.empty? -- empty_msg = wiki_is_empty ? s_('WikiEmpty|This project has no wiki pages') : s_('WikiEmpty|This page does not exist') -- create_msg = wiki_is_empty ? s_('WikiEmpty|Create your first page') : s_('WikiEmpty|Create this page') - if can?(current_user, :create_wiki, @project) - create_path = project_wiki_path(@project, params[:id], { view: 'create' }) - - create_link = link_to create_msg, create_path, class: 'btn btn-success qa-create-first-page-link', title: create_msg + - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-success qa-create-first-page-link', title: s_('WikiEmpty|Create your first page') = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do %h4.text-left @@ -20,7 +17,7 @@ = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do %h4 - = empty_msg + = s_('WikiEmpty|This project has no wiki pages') %p.text-left = s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link } = new_issue_link @@ -28,6 +25,6 @@ - else = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do %h4 - = empty_msg + = s_('WikiEmpty|This project has no wiki pages') %p = s_('WikiEmpty|You must be a project member in order to add wiki pages.') diff --git a/app/views/shared/wiki/_page_listing.html.haml b/app/views/shared/wiki/_page_listing.html.haml deleted file mode 100644 index 80f3071a8b26801585ccbb348d2ef6ddc8488da8..0000000000000000000000000000000000000000 --- a/app/views/shared/wiki/_page_listing.html.haml +++ /dev/null @@ -1,45 +0,0 @@ -- @no_container = true -- current_sorting = params.permit(:sort, :direction) -- sort_title = wiki_sort_title(params[:sort]) - -%div{ class: container_class } - .wiki-page-header.top-area.flex-column.flex-lg-row - - .nav-text.flex-fill - %h2.wiki-page-title - = wiki_page_title - - .nav-controls.pb-md-3.pb-lg-0 - - if can?(current_user, :create_wiki, @project) - = link_to project_wiki_pages_new_path(@project), class: "add-new-wiki btn btn-success" do - = s_("Wiki|New page") - - = link_to project_wikis_git_access_path(@project), class: 'btn qa-clone-repository-link' do - = sprite_icon('download', size: 16) - = _("Clone repository") - - - if @nesting.present? && allow_change_nesting - .dropdown.inline.wiki-nesting-dropdown - .btn-group{ role: 'group' } - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } - = wiki_show_children_icon(@nesting) - = sprite_icon('chevron-down', size: 16) - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort - - ProjectWiki::NESTINGS.each do |choice| - %li= link_to wiki_show_children_icon(choice), page_path.call(current_sorting.merge(show_children: choice)), class: @nesting == choice ? 'is-active' : '' - - .dropdown.inline.wiki-sort-dropdown - .btn-group{ role: 'group' } - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } - = sort_title - = sprite_icon('chevron-down', size: 16) - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort - %li - = sortable_item(s_("Wiki|Title"), page_path.call(sort: ProjectWiki::TITLE_ORDER), sort_title) - = sortable_item(s_("Wiki|Created date"), page_path.call(sort: ProjectWiki::CREATED_AT_ORDER), sort_title) - = wiki_sort_controls(current_sorting.merge(show_children: @nesting), &page_path) - - %ul.wiki-pages-list.content-list - = render @wiki_entries, context: 'pages' - - = paginate @wiki_pages, theme: 'gitlab' diff --git a/changelogs/unreleased/34230-fix-popover-image.yml b/changelogs/unreleased/34230-fix-popover-image.yml new file mode 100644 index 0000000000000000000000000000000000000000..c9cf230ac6cbe11d217a7fc9d123b059ea836dbb --- /dev/null +++ b/changelogs/unreleased/34230-fix-popover-image.yml @@ -0,0 +1,5 @@ +--- +title: Fix cluster feature highlight popover image +merge_request: 19372 +author: +type: fixed diff --git a/changelogs/unreleased/add-snowplow-iglu-registry-application-setting.yml b/changelogs/unreleased/add-snowplow-iglu-registry-application-setting.yml new file mode 100644 index 0000000000000000000000000000000000000000..b1e7447eaad226d230355911346d7eb33456b3fc --- /dev/null +++ b/changelogs/unreleased/add-snowplow-iglu-registry-application-setting.yml @@ -0,0 +1,5 @@ +--- +title: Add ApplicationSetting for snowplow_iglu_registry_url +merge_request: 18449 +author: +type: added diff --git a/changelogs/unreleased/sort-wiki-pages-by-date.yml b/changelogs/unreleased/sort-wiki-pages-by-date.yml deleted file mode 100644 index be5238fd6c3a6ff767cf786180ab8f8f6a034f60..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/sort-wiki-pages-by-date.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Sort wiki pages by date -merge_request: 30245 -type: added diff --git a/config/routes/project.rb b/config/routes/project.rb index cc86e318dae508bb332d04bbd61c57d167e0d659..a132fb62647123181d3300e621fcea26bd9ff4a8 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -617,7 +617,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end # Since both wiki and repository routing contains wildcard characters - # its preferable to keep them below all other project routes + # its preferable to keep it below all other project routes draw :wiki draw :repository diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb index 3e71c5eb924133955949647e22d22a2c7cee6747..d439c99270ed3d06c813b9487a87095e9ce978ae 100644 --- a/config/routes/wiki.rb +++ b/config/routes/wiki.rb @@ -1,21 +1,13 @@ scope(controller: :wikis) do - scope(path: 'wikis/pages', as: :wiki_pages, format: false) do - get :new, to: 'wiki_pages#new' - post '/', to: 'wiki_pages#create' - end - scope(path: 'wikis', as: :wikis) do get :git_access get :pages - get '/', to: redirect('%{namespace_id}/%{project_id}/-/wiki_pages/home') - get '/*id', to: redirect('%{namespace_id}/%{project_id}/-/wiki_pages/%{id}') - end - - scope(path: '-/wiki_pages', as: :wiki_page, format: false) do - post '/', to: 'wiki_pages#create' + get :new + get '/', to: redirect('%{namespace_id}/%{project_id}/wikis/home') + post '/', to: 'wikis#create' end - scope(path: '-/wiki_pages/*id', as: :wiki, format: false, controller: :wiki_pages) do + scope(path: 'wikis/*id', as: :wiki, format: false) do get :edit get :history post :preview_markdown @@ -23,8 +15,4 @@ scope(controller: :wikis) do put '/', action: :update delete '/', action: :destroy end - - scope(path: '-/wiki_dirs/*id', as: :wiki_dir, format: false, controller: :wiki_directories) do - get '/', action: :show - end end diff --git a/db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb b/db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..a40ce8dbee5e0db5b82a5d7e58dbf602b6552a88 --- /dev/null +++ b/db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddSnowplowIgluRegistryUrlToApplicationSettings < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column :application_settings, :snowplow_iglu_registry_url, :string, limit: 255 + end +end diff --git a/db/migrate/20191024134020_add_index_to_zoom_meetings.rb b/db/migrate/20191024134020_add_index_to_zoom_meetings.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef3657b6a5edbe833ae5ede286e4bcfdaf81c507 --- /dev/null +++ b/db/migrate/20191024134020_add_index_to_zoom_meetings.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexToZoomMeetings < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :zoom_meetings, :issue_status + end + + def down + remove_concurrent_index :zoom_meetings, :issue_status if index_exists?(:zoom_meetings, :issue_status) + end +end diff --git a/db/schema.rb b/db/schema.rb index df56b84d61b73d7e945ff8b443031f80e4eb17d4..b07ba57ba77c9c25a29678e3f1bae3559474582a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -338,6 +338,7 @@ ActiveRecord::Schema.define(version: 2019_10_26_124116) do t.boolean "throttle_incident_management_notification_enabled", default: false, null: false t.integer "throttle_incident_management_notification_period_in_seconds", default: 3600 t.integer "throttle_incident_management_notification_per_period", default: 3600 + t.string "snowplow_iglu_registry_url", limit: 255 t.integer "push_event_hooks_limit", default: 3, null: false t.integer "push_event_activities_limit", default: 3, null: false t.string "custom_http_clone_url_root", limit: 511 @@ -4041,6 +4042,7 @@ ActiveRecord::Schema.define(version: 2019_10_26_124116) do t.string "url", limit: 255 t.index ["issue_id", "issue_status"], name: "index_zoom_meetings_on_issue_id_and_issue_status", unique: true, where: "(issue_status = 1)" t.index ["issue_id"], name: "index_zoom_meetings_on_issue_id" + t.index ["issue_status"], name: "index_zoom_meetings_on_issue_status" t.index ["project_id"], name: "index_zoom_meetings_on_project_id" end diff --git a/doc/api/settings.md b/doc/api/settings.md index 36419ae8ec4c7d05f8268d6765d0aeb0e7e0a684..a1c343ec208ab4662ae7c080d0af2672ce818174 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -316,6 +316,7 @@ are listed in the descriptions of the relevant settings. | `snowplow_cookie_domain` | string | no | The Snowplow cookie domain. (e.g. `.gitlab.com`) | | `snowplow_enabled` | boolean | no | Enable snowplow tracking. | | `snowplow_site_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) | +| `snowplow_iglu_registry_url` | string | no | The Snowplow base Iglu Schema Registry URL to use for custom context and self describing events'| | `pendo_url` | string | required by: `pendo_enabled` | The Pendo endpoint url with js snippet. (e.g. `https://cdn.pendo.io/agent/static/your-api-key/pendo.js`) | | `pendo_enabled` | boolean | no | Enable pendo tracking. | | `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to `0` for unlimited time. | diff --git a/lib/api/settings.rb b/lib/api/settings.rb index ffa0472bdbbc86c2436c599a9d4ce9af3fe601c8..0772423dcb7c64a362b8c8ed78066bded52e3b21 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -135,6 +135,7 @@ module API optional :local_markdown_version, type: Integer, desc: 'Local markdown version, increase this value when any cached markdown should be invalidated' optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5 optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking' + optional :snowplow_iglu_registry_url, type: String, desc: 'The Snowplow base Iglu Schema Registry URL to use for custom context and self describing events' given snowplow_enabled: ->(val) { val } do requires :snowplow_collector_hostname, type: String, desc: 'The Snowplow collector hostname' optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain' diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb index e346b03754b7e7db582d27034f819169bf607b1c..f4cc8beeb5267fcf1e2689f368b7e94a36a00183 100644 --- a/lib/banzai/filter/wiki_link_filter/rewriter.rb +++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb @@ -6,7 +6,7 @@ module Banzai class Rewriter def initialize(link_string, wiki:, slug:) @uri = Addressable::URI.parse(link_string) - @wiki_base_path = wiki && wiki.wiki_page_path + @wiki_base_path = wiki && wiki.wiki_base_path @slug = slug end diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb index 2470685bc00ac3857552d1a8f2a616aee658fb8e..af6b13b0d3657642bc8f45dded2c42d6f0ca5dc5 100644 --- a/lib/gitlab/tracking.rb +++ b/lib/gitlab/tracking.rb @@ -47,7 +47,8 @@ module Gitlab cookie_domain: Gitlab::CurrentSettings.snowplow_cookie_domain, app_id: Gitlab::CurrentSettings.snowplow_site_id, form_tracking: additional_features, - link_click_tracking: additional_features + link_click_tracking: additional_features, + iglu_registry_url: Gitlab::CurrentSettings.snowplow_iglu_registry_url }.transform_keys! { |key| key.to_s.camelize(:lower).to_sym } end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 504780d0402f823d5ce9f11b2d434e93af651755..76324b9379939980c00ed338310fb80235dbf474 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -78,6 +78,8 @@ module Gitlab in_review_folder: count(::Environment.in_review_folder), groups: count(Group), issues: count(Issue), + issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue), + issues_using_zoom_quick_actions: count(ZoomMeeting.select(:issue_id).distinct), keys: count(Key), label_lists: count(List.label), lfs_objects: count(LfsObject), diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index 5cba6c52773eda81d3ee9ec1951e7b1ed79c57d0..7fbfc4c45c44c37586e052083291364e5ab6200d 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -130,15 +130,5 @@ module Gitlab IPAddr.new(str) rescue IPAddr::InvalidAddressError end - - # Filter a Hash against a mapping of keys to sets of allowed values. - # - # Keys that do not pass the filter will be removed from the Hash. - # This mutates the input hash. - def allow_hash_values(hash, allowed) - allowed.each do |key, allowed_values| - hash.delete(key) if hash.key?(key) && !allowed_values.include?(hash[key]) - end - end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 193eddc191d75f5cd5b7380fb2b2656d1024c4a6..b0a5500a38dc9d19877661dd2f4fe681ec631541 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -411,9 +411,6 @@ msgstr "" msgid "%{verb} %{time_spent_value} spent time." msgstr "" -msgid "·" -msgstr "" - msgid "'%{level}' is not a valid visibility level" msgstr "" @@ -8848,6 +8845,9 @@ msgstr "" msgid "If your HTTP repository is not publicly accessible, add your credentials." msgstr "" +msgid "Iglu registry URL (optional)" +msgstr "" + msgid "ImageDiffViewer|2-up" msgstr "" @@ -9543,6 +9543,9 @@ msgstr "" msgid "Kubernetes error: %{error_code}" msgstr "" +msgid "Kubernetes popover" +msgstr "" + msgid "LDAP" msgstr "" @@ -18891,10 +18894,7 @@ msgstr "" msgid "Wiki pages" msgstr "" -msgid "Wiki was successfully created" -msgstr "" - -msgid "Wiki was successfully updated" +msgid "Wiki was successfully updated." msgstr "" msgid "WikiClone|Clone your wiki" @@ -18912,18 +18912,6 @@ msgstr "" msgid "WikiClone|Start Gollum and edit locally" msgstr "" -msgid "WikiDirEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on." -msgstr "" - -msgid "WikiDirEmpty|Create a page" -msgstr "" - -msgid "WikiDirEmpty|Create a page in this directory" -msgstr "" - -msgid "WikiDirEmpty|This directory has no wiki pages" -msgstr "" - msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." msgstr "" @@ -18942,9 +18930,6 @@ msgstr "" msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on." msgstr "" -msgid "WikiEmpty|Create this page" -msgstr "" - msgid "WikiEmpty|Create your first page" msgstr "" @@ -18954,9 +18939,6 @@ msgstr "" msgid "WikiEmpty|The wiki lets you write documentation for your project" msgstr "" -msgid "WikiEmpty|This page does not exist" -msgstr "" - msgid "WikiEmpty|This project has no wiki pages" msgstr "" @@ -19011,9 +18993,6 @@ msgstr "" msgid "WikiPage|Write your content or drag files here…" msgstr "" -msgid "Wiki|Contents" -msgstr "" - msgid "Wiki|Create New Page" msgstr "" @@ -19026,9 +19005,6 @@ msgstr "" msgid "Wiki|Edit Page" msgstr "" -msgid "Wiki|Hide folder contents" -msgstr "" - msgid "Wiki|More Pages" msgstr "" @@ -19047,13 +19023,10 @@ msgstr "" msgid "Wiki|Pages" msgstr "" -msgid "Wiki|Show files separately" -msgstr "" - -msgid "Wiki|Show folder contents" +msgid "Wiki|Title" msgstr "" -msgid "Wiki|Title" +msgid "Wiki|Wiki Pages" msgstr "" msgid "Will deploy to" diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb index bdc1cda8950ef7a16b808b80a0e70d53f0109424..f6edc28c41a7feed6326704629c659ef6ef8937f 100644 --- a/qa/qa/page/project/wiki/edit.rb +++ b/qa/qa/page/project/wiki/edit.rb @@ -5,7 +5,7 @@ module QA module Project module Wiki class Edit < Page::Base - view 'app/views/shared/wiki/_main_links.html.haml' do + view 'app/views/projects/wikis/_main_links.html.haml' do element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb index abad499d70047efe9f8241c7af9804c12e0b0cab..792eba4bab7971cae5d93fa7e7a399c5497367f4 100644 --- a/qa/qa/page/project/wiki/new.rb +++ b/qa/qa/page/project/wiki/new.rb @@ -7,7 +7,7 @@ module QA class New < Page::Base include Component::LazyLoader - view 'app/views/projects/wiki_pages/_form.html.haml' do + view 'app/views/projects/wikis/_form.html.haml' do element :wiki_title_textbox element :wiki_content_textarea element :wiki_message_textbox diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb index c6b34ddc317cf9e29525cb7f6d904e2eed411f4d..44619d177b1dd04d4478b04e34dfef373f04654d 100644 --- a/qa/qa/page/project/wiki/show.rb +++ b/qa/qa/page/project/wiki/show.rb @@ -7,11 +7,11 @@ module QA class Show < Page::Base include Page::Component::LegacyClonePanel - view 'app/views/shared/wiki/_page_listing.html.haml' do + view 'app/views/projects/wikis/pages.html.haml' do element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern end - view 'app/views/projects/wiki_pages/show.html.haml' do + view 'app/views/projects/wikis/show.html.haml' do element :wiki_page_content end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb index b996492ce08c8d0e3d5ee7cca5e555d2b9240ec3..2c3f2c86c23c9a3ca2c8f665f707da1bd58b74ae 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb @@ -13,7 +13,7 @@ module QA resource.message = 'Update home' end - validate_created('My First Wiki Content') + validate_content('My First Wiki Content') Page::Project::Wiki::Edit.perform(&:click_edit) Page::Project::Wiki::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName @@ -21,7 +21,7 @@ module QA page.save_changes end - validate_edited('My Second Wiki Content') + validate_content('My Second Wiki Content') Resource::Repository::WikiPush.fabricate! do |push| push.wiki = wiki @@ -34,12 +34,7 @@ module QA expect(page).to have_content('My Third Wiki Content') end - def validate_created(content) - expect(page).to have_content('Wiki was successfully created') - expect(page).to have_content(/#{content}/) - end - - def validate_edited(content) + def validate_content(content) expect(page).to have_content('Wiki was successfully updated') expect(page).to have_content(/#{content}/) end diff --git a/scripts/static-analysis b/scripts/static-analysis index 602cd847a71e23ca5c3bbaafd464161b1dae6aa9..b7f7100c3651082fa20f765aeb00a0b765b635b8 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -26,17 +26,35 @@ def emit_errors(static_analysis) end end -tasks = [ - %w[bin/rake lint:all], - %w[bundle exec license_finder], - %w[yarn run eslint], - %w[yarn run stylelint], - %w[yarn run prettier-all], - %w[bundle exec rubocop --parallel], - %w[scripts/lint-conflicts.sh], - %w[scripts/lint-rugged] -] +def jobs_to_run(node_index, node_total) + all_tasks = [ + %w[bin/rake lint:all], + %w[bundle exec license_finder], + %w[yarn run eslint], + %w[yarn run stylelint], + %w[yarn run prettier-all], + %w[bundle exec rubocop --parallel], + %w[scripts/lint-conflicts.sh], + %w[scripts/lint-rugged] + ] + case node_total + when 1 + all_tasks + when 2 + rake_lint_all, *rest_jobs = all_tasks + case node_index + when 1 + [rake_lint_all] + else + rest_jobs + end + else + raise "Parallelization > 2 (currently set to #{node_total}) isn't supported yet!" + end +end + +tasks = jobs_to_run((ENV['CI_NODE_INDEX'] || 1).to_i, (ENV['CI_NODE_TOTAL'] || 1).to_i) static_analysis = Gitlab::Popen::Runner.new static_analysis.run(tasks) do |cmd, &run| diff --git a/spec/controllers/projects/wiki_directories_controller_spec.rb b/spec/controllers/projects/wiki_directories_controller_spec.rb deleted file mode 100644 index b09e1bc2ca49ec36fa6a78bb3351523b843e4dfb..0000000000000000000000000000000000000000 --- a/spec/controllers/projects/wiki_directories_controller_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Projects::WikiDirectoriesController do - set(:project) { create(:project, :public, :repository) } - - let(:user) { project.owner } - let(:project_wiki) { ProjectWiki.new(project, user) } - let(:wiki) { project_wiki.wiki } - let(:dir_slug) { 'the-directory' } - let(:dir_contents) { [create(:wiki_page)] } - let(:the_dir) { WikiDirectory.new(dir_slug, dir_contents) } - - before do - allow(controller).to receive(:find_dir).and_return(the_dir) - - sign_in(user) - end - - describe 'GET #show' do - let(:show_params) do - { - namespace_id: project.namespace, - project_id: project, - id: dir_slug - } - end - - before do - get :show, params: show_params - end - - context 'the directory is empty' do - let(:the_dir) { nil } - - it { is_expected.to render_template('empty') } - end - - context 'the directory does exist' do - it { is_expected.to render_template('show') } - - it 'sets the wiki_dir attribute' do - expect(assigns(:wiki_dir)).to eq(the_dir) - end - - it 'assigns the wiki pages' do - expect(assigns(:wiki_pages)).to eq(dir_contents) - end - end - end -end diff --git a/spec/controllers/projects/wiki_pages_controller_spec.rb b/spec/controllers/projects/wiki_pages_controller_spec.rb deleted file mode 100644 index 01a84fbbf201c689b5fad1f4f6edcffb451cbf53..0000000000000000000000000000000000000000 --- a/spec/controllers/projects/wiki_pages_controller_spec.rb +++ /dev/null @@ -1,399 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Projects::WikiPagesController do - set(: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' } - let(:parent_ids) { { namespace_id: project.namespace.path, project_id: project.name } } - let(:redirect_destination) { Rails.application.routes.recognize_path(response.redirect_url) } - - before do - create_page(wiki_title, 'hello world') - - sign_in(user) - end - - after do - destroy_page(wiki_title) - end - - def helper - Helper.instance - end - - class Helper - include Singleton - include ActionView::Helpers::UrlHelper - end - - describe 'GET #new' do - subject { get :new, params: parent_ids } - - it 'redirects to #show and appends a `random_title` param' do - subject - - expect(response).to have_http_status(302) - - expect(redirect_destination) - .to include(parent_ids.merge(controller: 'projects/wiki_pages', action: 'show')) - - expect(response.redirect_url).to match(/\?random_title=true\Z/) - end - end - - describe 'GET #show' do - render_views - let(:requested_wiki_page) { wiki_title } - let(:random_title) { nil } - - subject do - get :show, params: { - namespace_id: project.namespace, - project_id: project, - id: requested_wiki_page, - random_title: random_title - } - end - - context 'when the wiki repo cannot be created' do - before do - allow(controller).to receive(:load_wiki) { raise ProjectWiki::CouldNotCreateWikiError } - end - - it 'redirects to the project path' do - headers = { 'Location' => a_string_ending_with(Gitlab::Routing.url_helpers.project_path(project)) } - - subject - - expect(response).to be_redirect - expect(response.header.to_hash).to include(headers) - end - end - - context 'when the page exists' do - it 'limits the retrieved pages for the sidebar' do - expect(controller).to receive(:load_wiki).and_return(project_wiki) - - # Sidebar entries - expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original - - subject - - expect(response).to have_http_status(:ok) - expect(response.body).to include(wiki_title) - 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_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(:requested_wiki_page) { 'this-page-does-not-yet-exist' } - - context 'the current user can create wiki pages' do - it { is_expected.to render_template('edit') } - - it 'makes a call to see if the wiki is empty' do - expect(controller).to receive(:load_wiki).and_return(project_wiki) - expect(project_wiki).to receive(:list_pages).once.with(limit: anything).and_call_original - expect(project_wiki).to receive(:list_pages).with(limit: 1).and_call_original - subject - end - - describe 'assigned title' do - shared_examples :wiki_page_with_correct_title do - it 'assigns the correct title' do - subject - - expect(assigns(:page)).to have_attributes(title: assigned_title) - end - end - - context 'random_title is absent' do - let(:random_title) { nil } - - it_behaves_like :wiki_page_with_correct_title do - let(:assigned_title) { WikiPage.unhyphenize(requested_wiki_page) } - end - end - - context 'random_title is present' do - let(:random_title) { true } - - it_behaves_like :wiki_page_with_correct_title do - let(:assigned_title) { be_empty } - end - end - end - end - - context 'the current user cannot create wiki pages' do - before do - forbid_controller_ability! :create_wiki - end - it { is_expected.to render_template('missing_page') } - end - end - - context 'when page is a file' do - include WikiHelpers - - let(:path) { upload_file_to_wiki(project, user, file_name) } - - before do - get :show, params: { namespace_id: project.namespace, project_id: project, id: path } - end - - context 'when file is an image' do - let(:file_name) { 'dk.png' } - - it 'delivers the image' do - 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 - expect(response.headers['Content-Disposition']).to match(/^inline/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - 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 - expect(response.headers['Content-Disposition']).to match(/^inline/) - expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" - end - end - end - end - - describe 'POST #preview_markdown' do - let(:page_id) { 'page/path' } - let(:markdown_text) { '*Markdown* text' } - let(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: wiki_title }) } - let(:processed_md) { json_response.fetch('body') } - - let(:preview_params) do - { namespace_id: project.namespace, project_id: project, id: wiki_page.slug, text: markdown_text } - end - - before do - post :preview_markdown, params: preview_params - end - - it 'renders json in a correct format' do - expect(response).to have_http_status(:ok) - expect(json_response).to include('body' => String, 'references' => Hash) - end - - describe 'double brackets within backticks' do - let(:markdown_text) do - <<-HEREDOC - `[[do_not_linkify]]` - ``` - [[also_do_not_linkify]] - ``` - HEREDOC - end - - it "does not linkify double brackets inside code blocks as expected" do - expect(processed_md).to include('[[do_not_linkify]]', '[[also_do_not_linkify]]') - end - end - - describe 'link re-writing' do - let(:links) do - [ - { text: 'regular link', path: 'regular' }, - { text: 'relative link 1', path: '../relative' }, - { text: 'relative link 2', path: './relative' }, - { text: 'relative link 3', path: './e/f/relative' }, - { text: 'spaced link', path: 'title with spaces' } - ] - end - - shared_examples :wiki_link_rewriter do - let(:markdown_text) { links.map { |text:, path:| "[#{text}](#{path})" }.join("\n") } - let(:expected_links) do - links.zip(paths).map do |(link, path)| - helper.link_to(link[:text], "#{project_wiki.wiki_page_path}/#{path}") - end - end - - it 'processes the links correctly' do - expect(processed_md).to include(*expected_links) - end - end - - context 'the current page has spaces in its title' do - let(:wiki_title) { 'page a/page b/page c/page d' } - it_behaves_like :wiki_link_rewriter do - let(:paths) do - ['regular', - 'page-a/page-b/relative', - 'page-a/page-b/page-c/relative', - 'page-a/page-b/page-c/e/f/relative', - 'title%20with%20spaces'] - end - end - end - - context 'the current page has an unproblematic title' do - let(:wiki_title) { 'a/b/c/d' } - it_behaves_like :wiki_link_rewriter do - let(:paths) do - ['regular', 'a/b/relative', 'a/b/c/relative', 'a/b/c/e/f/relative', 'title%20with%20spaces'] - end - end - end - - context "when there are hyphens in the page name" do - let(:wiki_title) { 'page-a/page-b/page-c/page-d' } - it_behaves_like :wiki_link_rewriter do - let(:paths) do - ['regular', - 'page-a/page-b/relative', - 'page-a/page-b/page-c/relative', - 'page-a/page-b/page-c/e/f/relative', - 'title%20with%20spaces'] - end - end - end - 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(project_wiki_path(project, project_wiki.list_pages.first)) - end - end - - context 'when page content encoding is valid' do - render_views - - it 'shows the edit page' do - subject - - expect(response).to have_http_status(:ok) - expect(response.body).to include('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_page: { 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(project_wiki_path(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 - - 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_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 - - private - - 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 -end diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb index 7fbefa5787be83ab2dbe7c3bc15aec1ca3d3b137..f46da908218de07ddfc70f71e55bf404bf20a878 100644 --- a/spec/controllers/projects/wikis_controller_spec.rb +++ b/spec/controllers/projects/wikis_controller_spec.rb @@ -4,10 +4,10 @@ require 'spec_helper' describe Projects::WikisController do let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:user) { project.owner } - let_it_be(:project_wiki) { ProjectWiki.new(project, user) } - let_it_be(:wiki) { project_wiki.wiki } - let_it_be(:wiki_title) { 'page title test' } + 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') @@ -19,86 +19,231 @@ describe Projects::WikisController do destroy_page(wiki_title) end - describe 'GET #pages' do - subject do - get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }.merge(extra_params) + 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_http_status(302) + 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 - let(:extra_params) { {} } + 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 'illegal params' do - shared_examples :a_bad_request do - it do - expect { subject }.to raise_error(ActionController::BadRequest) - end - end + describe 'GET #history' do + before do + allow(controller) + .to receive(:can?) + .with(any_args) + .and_call_original - describe ':sort' do - let(:extra_params) { { sort: 'wibble' } } + # The :create_wiki permission is irrelevant to reading history. + expect(controller) + .not_to receive(:can?) + .with(anything, :create_wiki, any_args) - it_behaves_like :a_bad_request - end + allow(controller) + .to receive(:can?) + .with(anything, :read_wiki, any_args) + .and_return(allow_read_wiki) + end - describe ':direction' do - let(:extra_params) { { direction: 'wibble' } } + shared_examples 'fetching history' do |expected_status| + before do + get :history, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } + end - it_behaves_like :a_bad_request + it "returns status #{expected_status}" do + expect(response).to have_http_status(expected_status) end + end - describe ':show_children' do - let(:extra_params) { { show_children: 'wibble' } } + it_behaves_like 'fetching history', :ok do + let(:allow_read_wiki) { true } - it_behaves_like :a_bad_request + it 'assigns @page_versions' do + expect(assigns(:page_versions)).to be_present end end - shared_examples 'sorting-and-nesting' do |sort_key, default_nesting| - context "the user is sorting by #{sort_key}" do - let(:extra_params) { sort_params.merge(nesting_params) } - let(:sort_params) { { sort: sort_key } } - let(:nesting_params) { {} } + 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 + expect(controller).to receive(:load_wiki).and_return(project_wiki) + expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original + + subject + + expect(response).to have_http_status(:ok) + expect(assigns(:page).title).to eq(wiki_title) + end + + context 'when page content encoding is invalid' do + it 'sets flash error' do + allow(controller).to receive(:valid_encoding?).and_return(false) - before do subject + + expect(response).to have_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' } - it "sets nesting to #{default_nesting} by default" do - expect(assigns :nesting).to eq default_nesting + 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 - it 'hides children if the default requires it' do - expect(assigns :show_children).to be(default_nesting != ProjectWiki::NESTING_CLOSED) + context 'when page is a file' do + include WikiHelpers + + let(:id) { upload_file_to_wiki(project, user, file_name) } + + before do + subject + end + + context 'when file is an image' do + let(:file_name) { 'dk.png' } + + it 'delivers the image' do + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end - ProjectWiki::NESTINGS.each do |nesting| - context "the user explicitly passes show_children = #{nesting}" do - let(:nesting_params) { { show_children: nesting } } + context 'when file is a svg' do + let(:file_name) { 'unsanitized.svg' } - it 'sets nesting to the provided value' do - expect(assigns :nesting).to eq nesting - end + it 'delivers the image' do + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end end + end - context 'the user wants children hidden' do - let(:nesting_params) { { show_children: 'hidden' } } + context 'when file is a pdf' do + let(:file_name) { 'git-cheat-sheet.pdf' } - it 'hides children' do - expect(assigns :show_children).to be false - end + it 'sets the content type to sets the content response headers' do + expect(response.headers['Content-Disposition']).to match(/^inline/) + expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" end 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(project_wiki_path(project, project_wiki.list_pages.first)) + end + end - include_examples 'sorting-and-nesting', ProjectWiki::CREATED_AT_ORDER, ProjectWiki::NESTING_FLAT - include_examples 'sorting-and-nesting', ProjectWiki::TITLE_ORDER, ProjectWiki::NESTING_CLOSED + context 'when page content encoding is valid' do + render_views + + it 'shows the edit page' do + subject + + expect(response).to have_http_status(:ok) + expect(response.body).to include('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(project_wiki_path(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) diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb index 3ac8fe4ae43cb2f670299080ee0e744e58bd5044..761ba58edb219a5adedd6611760c046c7713a3be 100644 --- a/spec/factories/wiki_pages.rb +++ b/spec/factories/wiki_pages.rb @@ -18,12 +18,12 @@ FactoryBot.define do association :wiki, factory: :project_wiki, strategy: :build initialize_with { new(wiki, page, true) } - before(:create) do |wiki_page, evaluator| - wiki_page.attributes = evaluator.attrs.with_indifferent_access + before(:create) do |page, evaluator| + page.attributes = evaluator.attrs end - to_create do |wiki_page| - wiki_page.create + to_create do |page| + page.create end end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 8b7fef84dd73dc69e5e431c1045c7687b1744fb3..9ec61743a119cb7d5f7fccaddf6b926242c86e4a 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -3,9 +3,8 @@ require 'spec_helper' describe 'Edit Project Settings' do - set(:project) { create(:project, :public, :repository) } - let(:member) { create(:user) } + let!(:project) { create(:project, :public, :repository) } let!(:issue) { create(:issue, project: project) } let(:non_member) { create(:user) } @@ -82,88 +81,85 @@ describe 'Edit Project Settings' do end describe 'project features visibility pages' do - set(:pipeline) { create(:ci_empty_pipeline, project: project) } - set(:job) { create(:ci_build, pipeline: pipeline) } - - where(:method_name, :build_url) do - [ - [:builds, -> { project_job_path(project, job) }], - [:issues, -> { project_issues_path(project) }], - [:wiki, -> { project_wiki_path(project, :home) }], - [:snippets, -> { project_snippets_path(project) }], - [:merge_requests, -> { project_merge_requests_path(project) }] - ] + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let(:job) { create(:ci_build, pipeline: pipeline) } + + let(:tools) do + { + builds: project_job_path(project, job), + issues: project_issues_path(project), + wiki: project_wiki_path(project, :home), + snippets: project_snippets_path(project), + merge_requests: project_merge_requests_path(project) + } end - with_them do - let(:url) { build_url.call } - let(:attr_name) { "#{method_name}_access_level" } - - context 'normal user' do - before do - project.team.truncate - sign_in(member) - end - - it 'renders 200 if tool is enabled' do - project.project_feature.update_attribute(attr_name, ProjectFeature::ENABLED) + context 'normal user' do + before do + sign_in(member) + end + it 'renders 200 if tool is enabled' do + tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::ENABLED) visit url - expect(page.status_code).to eq(200) end + end - it 'renders 404 if feature is disabled' do - project.project_feature.update_attribute(attr_name, ProjectFeature::DISABLED) - + it 'renders 404 if feature is disabled' do + tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED) visit url - expect(page.status_code).to eq(404) end + end - it 'renders 404 if feature is enabled only for team members' do - project.project_feature.update_attribute(attr_name, ProjectFeature::PRIVATE) + it 'renders 404 if feature is enabled only for team members' do + project.team.truncate + tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) visit url - expect(page.status_code).to eq(404) end + end - it 'renders 200 if user is member of group' do - group = create(:group) - project.group = group - project.save - - group.add_owner(member) + it 'renders 200 if user is member of group' do + group = create(:group) + project.group = group + project.save - project.project_feature.update_attribute(attr_name, ProjectFeature::PRIVATE) + group.add_owner(member) + tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) visit url - expect(page.status_code).to eq(200) end end + end - context 'admin user' do - before do - non_member.update_attribute(:admin, true) - project.team.truncate - sign_in(non_member) - end + context 'admin user' do + before do + non_member.update_attribute(:admin, true) + sign_in(non_member) + end - it 'renders 404 if feature is disabled' do + it 'renders 404 if feature is disabled' do + tools.each do |method_name, url| project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED) - visit url - expect(page.status_code).to eq(404) end + end - it 'renders 200 if feature is enabled only for team members' do - project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) + it 'renders 200 if feature is enabled only for team members' do + project.team.truncate + tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) visit url - expect(page.status_code).to eq(200) end end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index a56b3e4955ac1f45ccaf7bcf8822d60734519624..5c6b04a7141687dcde41522d70154268a5669597 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -4,54 +4,164 @@ require 'spec_helper' describe 'Projects > Wiki > User previews markdown changes', :js do set(:user) { create(:user) } - set(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - let(:project_wiki) { ProjectWiki.new(project, user) } + let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) } + let(:wiki_content) do + <<-HEREDOC +[regular link](regular) +[relative link 1](../relative) +[relative link 2](./relative) +[relative link 3](./e/f/relative) +[spaced link](title with spaces) + HEREDOC + end before do project.add_maintainer(user) + sign_in(user) - init_home! end - def init_home! - create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) - end + context "while creating a new wiki page" do + context "when there are no spaces or hyphens in the page name" do + it "rewrites relative links as expected" do + create_wiki_page('a/b/c/d', content: wiki_content) - def fill_in_content! - page.within '.wiki-form' do - fill_in :wiki_page_content, with: wiki_content + expect(page).to have_content("regular link") + + expect(page.html).to include("regular link") + expect(page.html).to include("relative link 1") + expect(page.html).to include("relative link 2") + expect(page.html).to include("relative link 3") + expect(page.html).to include("spaced link") + end end - end - def show_preview! - page.within '.wiki-form' do - click_on 'Preview' + context "when there are spaces in the page name" do + it "rewrites relative links as expected" do + create_wiki_page('a page/b page/c page/d page', content: wiki_content) + + expect(page).to have_content("regular link") + + expect(page.html).to include("regular link") + expect(page.html).to include("relative link 1") + expect(page.html).to include("relative link 2") + expect(page.html).to include("relative link 3") + expect(page.html).to include("spaced link") + end end - end - context 'when writing a new page' do - let(:new_wiki_path) { 'a/b/c/d' } - let(:wiki_content) { 'Some [awesome wiki](content)' } + context "when there are hyphens in the page name" do + it "rewrites relative links as expected" do + create_wiki_page('a-page/b-page/c-page/d-page', content: wiki_content) - it 'can show a preview of markdown content' do - visit project_wiki_pages_new_path(project, id: new_wiki_path) - fill_in_content! - show_preview! + expect(page).to have_content("regular link") - expect(page).to have_link('awesome wiki') + expect(page.html).to include("regular link") + expect(page.html).to include("relative link 1") + expect(page.html).to include("relative link 2") + expect(page.html).to include("relative link 3") + expect(page.html).to include("spaced link") + end end end - context 'when editing an existing page' do - let(:wiki_content) { 'Some [bemusing](content)' } - let(:wiki_page) { create(:wiki_page, wiki: project_wiki) } + context "while editing a wiki page" do + context "when there are no spaces or hyphens in the page name" do + it "rewrites relative links as expected" do + create_wiki_page('a/b/c/d') + click_link 'Edit' + + fill_in :wiki_content, with: wiki_content + click_on "Preview" + + expect(page).to have_content("regular link") + + expect(page.html).to include("regular link") + expect(page.html).to include("relative link 1") + expect(page.html).to include("relative link 2") + expect(page.html).to include("relative link 3") + expect(page.html).to include("spaced link") + end + end + + context "when there are spaces in the page name" do + it "rewrites relative links as expected" do + create_wiki_page('a page/b page/c page/d page') + click_link 'Edit' + + fill_in :wiki_content, with: wiki_content + click_on "Preview" + + expect(page).to have_content("regular link") + + expect(page.html).to include("regular link") + expect(page.html).to include("relative link 1") + expect(page.html).to include("relative link 2") + expect(page.html).to include("relative link 3") + expect(page.html).to include("spaced link") + end + end + + context "when there are hyphens in the page name" do + it "rewrites relative links as expected" do + create_wiki_page('a-page/b-page/c-page/d-page') + click_link 'Edit' + + fill_in :wiki_content, with: wiki_content + click_on "Preview" - it 'can show a preview of markdown content, when writing' do - visit project_wiki_edit_path(project, wiki_page) - fill_in_content! - show_preview! + expect(page).to have_content("regular link") - expect(page).to have_link('bemusing') + expect(page.html).to include("regular link") + expect(page.html).to include("relative link 1") + expect(page.html).to include("relative link 2") + expect(page.html).to include("relative link 3") + expect(page.html).to include("spaced link") + end end + + context 'when rendering the preview' do + it 'renders content with CommonMark' do + create_wiki_page('a-page/b-page/c-page/common-mark') + click_link 'Edit' + + fill_in :wiki_content, with: "1. one\n - sublist\n" + click_on "Preview" + + # the above generates two separate lists (not embedded) in CommonMark + expect(page).to have_content("sublist") + expect(page).not_to have_xpath("//ol//li//ul") + end + end + end + + it "does not linkify double brackets inside code blocks as expected" do + wiki_content = <<-HEREDOC + `[[do_not_linkify]]` + ``` + [[also_do_not_linkify]] + ``` + HEREDOC + + create_wiki_page('linkify_test', wiki_content) + + expect(page).to have_content("do_not_linkify") + + expect(page.html).to include('[[do_not_linkify]]') + expect(page.html).to include('[[also_do_not_linkify]]') + end + + private + + def create_wiki_page(path, content = 'content') + visit project_wiki_path(project, wiki_page) + + click_link 'New page' + + fill_in :wiki_title, with: path + fill_in :wiki_content, with: content + + click_button 'Create page' end end diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index 6b651ccb67ada9f0ecdcdce88057c5d29869232d..56d0518015d41d89c9360cbfcab5a586d0d13677 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -3,15 +3,9 @@ require "spec_helper" describe "User creates wiki page" do - include CapybaraHelpers - include WikiHelpers - - set(:user) { create(:user) } - - let(:project) { create(:project) } + let(:user) { create(:user) } let(:wiki) { ProjectWiki.new(project, user) } - let(:new_page) { WikiPage.new(wiki) } - let(:message_field) { form_field_name(new_page, :message) } + let(:project) { create(:project) } before do project.add_maintainer(user) @@ -19,76 +13,36 @@ describe "User creates wiki page" do sign_in(user) end - def start_writing(page_path) - click_link("New page") - fill_in(:wiki_page_title, with: page_path) - end - - def create_page(attrs = {}) - page.within(".wiki-form") do - attrs.each do |k, v| - fill_in("wiki_page_#{k}".to_sym, with: v) - end - end - click_on("Create page") - end - - shared_examples 'updates commit message' do - describe 'commit message', :js do - it "has `Create home` as a commit message" do - wait_for_requests - - expect(page).to have_field(message_field, with: "Create home") - end - end - end - context "when wiki is empty" do before do visit(project_wikis_path(project)) click_link "Create your first page" - find('.wiki-form') end context "in a user namespace" do let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - let(:wiki_page_content) { '' } it "shows validation error message" do - create_page - - expect(page) - .to have_content("The form contains the following error:") - .and have_content("Content can't be blank") - .and have_css('.wiki-form') - .and have_css('.qa-create-page-button') - end - - it 'offers to create pages that do not yet exist' do - create_page(content: "[link test](test)") + page.within(".wiki-form") do + fill_in(:wiki_content, with: "") - expect(page) - .to have_content("Home") - .and have_content("link test") + click_on("Create page") + end - click_link("link test") + expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank") - expect(page).to have_content("Create New Page") - end + page.within(".wiki-form") do + fill_in(:wiki_content, with: "[link test](test)") - it "has a link to the parent directory in the pages sidebar" do - wiki_full_path = "one/two/three-test" - create_page(title: wiki_full_path, content: 'wiki content') + click_on("Create page") + end - wiki_page = wiki.find_page(wiki_full_path) - expect(wiki_page).to be_present - dir = wiki.find_dir(wiki_page.directory) - expect(dir).to be_present + expect(page).to have_content("Home").and have_content("link test") - expect(current_path).to include(wiki_full_path) + click_link("link test") - expect(page).to have_link(dir.slug, href: project_wiki_dir_path(project, dir)) + expect(page).to have_content("Create New Page") end it "shows non-escaped link in the pages list", :quarantine do @@ -104,17 +58,19 @@ describe "User creates wiki page" do expect(page).to have_xpath("//a[@href='/#{project.full_path}/wikis/one/two/three-test']") end - it_behaves_like 'updates commit message' + it "has `Create home` as a commit message", :js do + wait_for_requests + + expect(page).to have_field("wiki[message]", with: "Create home") + end it "creates a page from the home page" do - page_content = <<~WIKI_CONTENT - [test](test) - [GitLab API doc](api) - [Rake tasks](raketasks) - # Wiki header - WIKI_CONTENT + fill_in(:wiki_content, with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n# Wiki header\n") + fill_in(:wiki_message, with: "Adding links to wiki") - create_page(content: page_content, message: "Adding links to wiki") + page.within(".wiki-form") do + click_button("Create page") + end expect(current_path).to eq(project_wiki_path(project, "home")) expect(page).to have_content("test GitLab API doc Rake tasks Wiki header") @@ -155,7 +111,7 @@ describe "User creates wiki page" do end end - it "creates ASCIIdoc wiki with LaTeX blocks", :js do + it "creates ASCII wiki with LaTeX blocks", :js do stub_application_setting(plantuml_url: "http://localhost", plantuml_enabled: true) ascii_content = <<~MD @@ -176,25 +132,37 @@ describe "User creates wiki page" do stem:[2+2] is 4 MD - find("#wiki_page_format option[value=asciidoc]").select_option + find("#wiki_format option[value=asciidoc]").select_option + + fill_in(:wiki_content, with: ascii_content) - create_page(content: ascii_content) + page.within(".wiki-form") do + click_button("Create page") + end page.within ".md" do expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4") end end - it_behaves_like 'wiki file attachments' + it_behaves_like 'wiki file attachments', :quarantine end - context "in a group namespace" do + context "in a group namespace", :js do let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) } - it_behaves_like 'updates commit message' + it "has `Create home` as a commit message" do + wait_for_requests - it "creates a page from the home page" do - create_page(content: "My awesome wiki!") + expect(page).to have_field("wiki[message]", with: "Create home") + end + + it "creates a page from the home page", :quarantine do + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") + + click_button("Create page") + end expect(page).to have_content("Home") .and have_content("Last edited by #{user.name}") @@ -210,37 +178,76 @@ describe "User creates wiki page" do visit(project_wikis_path(project)) end - shared_examples 'creates page by slug' do |slug, unslug| - it "creates #{slug}" do - start_writing(slug) + context "in a user namespace" do + let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } - # Commit message field should have correct value. - expect(page).to have_field(message_field, with: "Create #{unslug}") + context "via the `new wiki page` page" do + it "creates a page with a single word" do + click_link("New page") - create_page(content: "My awesome wiki!") + page.within(".wiki-form") do + fill_in(:wiki_title, with: "foo") + fill_in(:wiki_content, with: "My awesome wiki!") + end - expect(page).to have_content(unslug) - .and have_content("Last edited by #{user.name}") - .and have_content("My awesome wiki!") - end - end + # Commit message field should have correct value. + expect(page).to have_field("wiki[message]", with: "Create foo") - context "in a user namespace" do - let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } + click_button("Create page") - context "via the `new wiki page` page" do - include_examples 'creates page by slug', 'foo', 'foo' - include_examples 'creates page by slug', 'Spaces in the name', 'Spaces in the name' - include_examples 'creates page by slug', 'Hyphens-in-the-name', 'Hyphens in the name' + expect(page).to have_content("foo") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") + end + + it "creates a page with spaces in the name" do + click_link("New page") + + page.within(".wiki-form") do + fill_in(:wiki_title, with: "Spaces in the name") + fill_in(:wiki_content, with: "My awesome wiki!") + end + + # Commit message field should have correct value. + expect(page).to have_field("wiki[message]", with: "Create Spaces in the name") + + click_button("Create page") + + expect(page).to have_content("Spaces in the name") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") + end + + it "creates a page with hyphens in the name" do + click_link("New page") + + page.within(".wiki-form") do + fill_in(:wiki_title, with: "hyphens-in-the-name") + fill_in(:wiki_content, with: "My awesome wiki!") + end + + # Commit message field should have correct value. + expect(page).to have_field("wiki[message]", with: "Create hyphens in the name") + + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") + + click_button("Create page") + end + + expect(page).to have_content("hyphens in the name") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") + end end it "shows the emoji autocompletion dropdown" do - start_writing('text-autocomplete') + click_link("New page") page.within(".wiki-form") do - find("#wiki_page_content").native.send_keys("") + find("#wiki_content").native.send_keys("") - fill_in(:wiki_page_content, with: ":") + fill_in(:wiki_content, with: ":") end expect(page).to have_selector(".atwho-view") @@ -251,9 +258,23 @@ describe "User creates wiki page" do let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) } context "via the `new wiki page` page" do - include_examples 'creates page by slug', 'foo', 'foo' - include_examples 'creates page by slug', 'Spaces in the name', 'Spaces in the name' - include_examples 'creates page by slug', 'Hyphens-in-the-name', 'Hyphens in the name' + it "creates a page" do + click_link("New page") + + page.within(".wiki-form") do + fill_in(:wiki_title, with: "foo") + fill_in(:wiki_content, with: "My awesome wiki!") + end + + # Commit message field should have correct value. + expect(page).to have_field("wiki[message]", with: "Create foo") + + click_button("Create page") + + expect(page).to have_content("foo") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") + end end end end diff --git a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb index 9b9d3100bf5999fd0c6254b77a3c5db899c3456b..38e5e292064a6d3b9075ed339cdce98ad95cfc37 100644 --- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb @@ -6,7 +6,6 @@ describe 'User deletes wiki page', :js do let(:user) { create(:user) } let(:project) { create(:project, :wiki_repo, namespace: user.namespace) } let(:wiki_page) { create(:wiki_page, wiki: project.wiki) } - let(:project_wiki) { ProjectWiki.new(project, user) } before do sign_in(user) @@ -19,6 +18,5 @@ describe 'User deletes wiki page', :js do find('.modal-footer .btn-danger').click expect(page).to have_content('Page was successfully deleted') - expect(project_wiki.find_page(wiki_page.slug)).to be nil end end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index 5ab44e83f73291045ae4f22e776908aa8b71e04e..3f3711f9eb832624838c73a3aec05491fe436a00 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -10,13 +10,6 @@ describe 'User updates wiki page' do sign_in(user) end - def create_page(attrs = {}) - page.within('.wiki-form') do - attrs.each { |k, v| fill_in("wiki_page_#{k}".to_sym, with: v) } - click_on('Create page') - end - end - context 'when wiki is empty' do before do visit(project_wikis_path(project)) @@ -35,7 +28,12 @@ describe 'User updates wiki page' do end it 'updates a page that has a path', :js do - create_page(title: 'one/two/three-test', content: 'wiki content') + fill_in(:wiki_title, with: 'one/two/three-test') + + page.within '.wiki-form' do + fill_in(:wiki_content, with: 'wiki content') + click_on('Create page') + end expect(current_path).to include('one/two/three-test') expect(find('.wiki-pages')).to have_content('three') @@ -74,9 +72,9 @@ describe 'User updates wiki page' do it 'updates a page', :js do # Commit message field should have correct value. - expect(page).to have_field('wiki_page[message]', with: 'Update home') + expect(page).to have_field('wiki[message]', with: 'Update home') - fill_in(:wiki_page_content, with: 'My awesome wiki!') + fill_in(:wiki_content, with: 'My awesome wiki!') click_button('Save changes') expect(page).to have_content('Home') @@ -85,31 +83,31 @@ describe 'User updates wiki page' do end it 'updates the commit message as the title is changed', :js do - fill_in(:wiki_page_title, with: 'Wiki title') + fill_in(:wiki_title, with: 'Wiki title') - expect(page).to have_field('wiki_page[message]', with: 'Update Wiki title') + expect(page).to have_field('wiki[message]', with: 'Update Wiki title') end it 'does not allow XSS', :js do - fill_in(:wiki_page_title, with: '