Commit 652bd073 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 2af90cef
......@@ -3,8 +3,10 @@ import { throttle } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import {
GlSprintf,
GlIcon,
GlAlert,
GlDropdown,
GlDropdownHeader,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
......@@ -22,8 +24,10 @@ import { formatDate } from '../utils';
export default {
components: {
GlSprintf,
GlIcon,
GlAlert,
GlDropdown,
GlDropdownHeader,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
......@@ -124,6 +128,12 @@ export default {
'fetchMoreLogsPrepend',
]),
isCurrentEnvironment(envName) {
return envName === this.environments.current;
},
isCurrentPod(podName) {
return podName === this.pods.current;
},
topReached() {
if (!this.logs.isLoading) {
this.fetchMoreLogsPrepend();
......@@ -161,7 +171,6 @@ export default {
<div class="row mx-n1">
<gl-form-group
id="environments-dropdown-fg"
:label="s__('Environments|Environment')"
label-size="sm"
label-for="environments-dropdown"
class="col-3 px-1"
......@@ -173,18 +182,27 @@ export default {
class="d-flex gl-h-32 js-environments-dropdown"
toggle-class="dropdown-menu-toggle"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Select environment') }}
</gl-dropdown-header>
<gl-dropdown-item
v-for="env in environments.options"
:key="env.id"
@click="showEnvironment(env.name)"
>
{{ env.name }}
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentEnvironment(env.name) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ env.name }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>
<gl-form-group
id="pods-dropdown-fg"
:label="s__('Environments|Logs from')"
label-size="sm"
label-for="pods-dropdown"
class="col-3 px-1"
......@@ -196,24 +214,58 @@ export default {
class="d-flex gl-h-32 js-pods-dropdown"
toggle-class="dropdown-menu-toggle"
>
<gl-dropdown-header class="text-center">
{{ s__('Environments|Filter by pod') }}
</gl-dropdown-header>
<template v-if="advancedFeaturesEnabled">
<gl-dropdown-item key="all-pods" @click="showPodLogs(null)">
{{ s__('Environments|All pods') }}
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentPod(null) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ s__('Environments|All pods') }}</div>
</div>
</gl-dropdown-item>
<gl-dropdown-divider />
</template>
<gl-dropdown-item v-if="!pods.options.length" :disabled="true">
<span class="text-muted">
{{ s__('Environments|No pods to display') }}
</span>
</gl-dropdown-item>
<gl-dropdown-item
v-for="podName in pods.options"
:key="podName"
class="text-nowrap"
@click="showPodLogs(podName)"
>
{{ podName }}
<div class="d-flex">
<gl-icon
:class="{ invisible: !isCurrentPod(podName) }"
name="status_success_borderless"
/>
<div class="flex-grow-1">{{ podName }}</div>
</div>
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>
<gl-form-group id="search-fg" label-size="sm" label-for="search" class="col-3 px-1">
<gl-search-box-by-click
v-model.trim="searchQuery"
:disabled="disableAdvancedControls"
:placeholder="s__('Environments|Search')"
class="js-logs-search"
type="search"
autofocus
@submit="setSearch(searchQuery)"
/>
</gl-form-group>
<gl-form-group
id="dates-fg"
:label="s__('Environments|Show last')"
label-size="sm"
label-for="time-window-dropdown"
class="col-3 px-1"
......@@ -222,32 +274,16 @@ export default {
ref="dateTimePicker"
v-model="timeRangeModel"
class="w-100 gl-h-32"
right
:disabled="disableAdvancedControls"
:options="timeRanges"
/>
</gl-form-group>
<gl-form-group
id="search-fg"
:label="s__('Environments|Search')"
label-size="sm"
label-for="search"
class="col-3 px-1"
>
<gl-search-box-by-click
v-model.trim="searchQuery"
:disabled="disableAdvancedControls"
:placeholder="s__('Environments|Search')"
class="js-logs-search"
type="search"
autofocus
@submit="setSearch(searchQuery)"
/>
</gl-form-group>
</div>
<log-control-buttons
ref="scrollButtons"
class="controllers align-self-end mb-1"
class="controllers"
:scroll-down-button-disabled="scrollDownButtonDisabled"
@refresh="showPodLogs(pods.current)"
@scrollDown="scrollDown"
......
......@@ -379,7 +379,7 @@
}
.top-bar {
@include build-trace-top-bar($gl-line-height * 5);
@include build-trace-top-bar($gl-line-height * 3);
position: relative;
top: 0;
......
......@@ -6,6 +6,7 @@ class Event < ApplicationRecord
include Presentable
include DeleteWithLimit
include CreatedAtFilterable
include Gitlab::Utils::StrongMemoize
default_scope { reorder(nil) }
......@@ -42,7 +43,8 @@ class Event < ApplicationRecord
note: Note,
project: Project,
snippet: Snippet,
user: User
user: User,
wiki: WikiPage::Meta
).freeze
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
......@@ -79,6 +81,7 @@ class Event < ApplicationRecord
scope :recent, -> { reorder(id: :desc) }
scope :code_push, -> { where(action: PUSHED) }
scope :merged, -> { where(action: MERGED) }
scope :for_wiki_page, -> { where(target_type: WikiPage::Meta.name) }
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
......@@ -197,6 +200,14 @@ class Event < ApplicationRecord
created_action? && !target && target_type.nil?
end
def created_wiki_page?
wiki_page? && action == CREATED
end
def updated_wiki_page?
wiki_page? && action == UPDATED
end
def created_target?
created_action? && target
end
......@@ -217,6 +228,10 @@ class Event < ApplicationRecord
target_type == "MergeRequest"
end
def wiki_page?
target_type == WikiPage::Meta.name
end
def milestone
target if milestone?
end
......@@ -229,6 +244,14 @@ class Event < ApplicationRecord
target if merge_request?
end
def wiki_page
strong_memoize(:wiki_page) do
next unless wiki_page?
ProjectWiki.new(project, author).find_page(target.canonical_slug)
end
end
def note
target if note?
end
......@@ -250,6 +273,10 @@ class Event < ApplicationRecord
'destroyed'
elsif commented_action?
"commented on"
elsif created_wiki_page?
'created'
elsif updated_wiki_page?
'updated'
elsif created_project_action?
created_project_action_name
else
......@@ -362,6 +389,8 @@ class Event < ApplicationRecord
:read_snippet
elsif milestone?
:read_milestone
elsif wiki_page?
:read_wiki
end
end
end
......
......@@ -19,10 +19,11 @@ class ProjectWiki
DIRECTION_DESC = 'desc'
DIRECTION_ASC = 'asc'
attr_reader :project, :user
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
attr_reader :project
def initialize(project, user = nil)
@project = project
......@@ -196,9 +197,9 @@ class ProjectWiki
def commit_details(action, message = nil, title = nil)
commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(@user)
git_user = Gitlab::Git::User.from_gitlab(user)
Gitlab::Git::Wiki::CommitDetails.new(@user.id,
Gitlab::Git::Wiki::CommitDetails.new(user.id,
git_user.username,
git_user.name,
git_user.email,
......@@ -206,7 +207,7 @@ class ProjectWiki
end
def default_message(action, title)
"#{@user.username} #{action} page: #{title}"
"#{user.username} #{action} page: #{title}"
end
def update_project_activity
......
......@@ -21,6 +21,14 @@ class WikiPage
ActiveModel::Name.new(self, nil, 'wiki')
end
def eql?(other)
return false unless other.present? && other.is_a?(self.class)
slug == other.slug && wiki.project == other.wiki.project
end
alias_method :==, :eql?
# Sorts and groups pages by directory.
#
# pages - an array of WikiPage objects.
......@@ -58,6 +66,7 @@ class WikiPage
# The GitLab ProjectWiki instance.
attr_reader :wiki
delegate :project, to: :wiki
# The raw Gitlab::Git::WikiPage instance.
attr_reader :page
......@@ -70,6 +79,10 @@ class WikiPage
Gitlab::HookData::WikiPageBuilder.new(self).build
end
# Construct a new WikiPage
#
# @param [ProjectWiki] wiki
# @param [Gitlab::Git::WikiPage] page
def initialize(wiki, page = nil)
@wiki = wiki
@page = page
......
# frozen_string_literal: true
class WikiPage
class Meta < ApplicationRecord
include Gitlab::Utils::StrongMemoize
CanonicalSlugConflictError = Class.new(ActiveRecord::RecordInvalid)
self.table_name = 'wiki_page_meta'
belongs_to :project
has_many :slugs, class_name: 'WikiPage::Slug', foreign_key: 'wiki_page_meta_id', inverse_of: :wiki_page_meta
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
validates :title, presence: true
validates :project_id, presence: true
validate :no_two_metarecords_in_same_project_can_have_same_canonical_slug
scope :with_canonical_slug, ->(slug) do
joins(:slugs).where(wiki_page_slugs: { canonical: true, slug: slug })
end
alias_method :resource_parent, :project
# Return the (updated) WikiPage::Meta record for a given wiki page
#
# If none is found, then a new record is created, and its fields are set
# to reflect the wiki_page passed.
#
# @param [String] last_known_slug
# @param [WikiPage] wiki_page
#
# As with all `find_or_create` methods, this one raises errors on
# validation issues.
def self.find_or_create(last_known_slug, wiki_page)
project = wiki_page.wiki.project
known_slugs = [last_known_slug, wiki_page.slug].compact.uniq
raise 'no slugs!' if known_slugs.empty?
transaction do
found = find_by_canonical_slug(known_slugs, project)
meta = found || create(title: wiki_page.title, project_id: project.id)
meta.update_state(found.nil?, known_slugs, wiki_page)
# We don't need to run validations here, since find_by_canonical_slug
# guarantees that there is no conflict in canonical_slug, and DB
# constraints on title and project_id enforce our other invariants
# This saves us a query.
meta
end
end
def self.find_by_canonical_slug(canonical_slug, project)
meta, conflict = with_canonical_slug(canonical_slug)
.where(project_id: project.id)
.limit(2)
if conflict.present?
meta.errors.add(:canonical_slug, 'Duplicate value found')
raise CanonicalSlugConflictError.new(meta)
end
meta
end
def canonical_slug
strong_memoize(:canonical_slug) { slugs.canonical.first&.slug }
end
def canonical_slug=(slug)
return if @canonical_slug == slug
if persisted?
transaction do
slugs.canonical.update_all(canonical: false)
page_slug = slugs.create_with(canonical: true).find_or_create_by(slug: slug)
page_slug.update_columns(canonical: true) unless page_slug.canonical?
end
else
slugs.new(slug: slug, canonical: true)
end
@canonical_slug = slug
end
def update_state(created, known_slugs, wiki_page)
update_wiki_page_attributes(wiki_page)
insert_slugs(known_slugs, created, wiki_page.slug)
self.canonical_slug = wiki_page.slug
end
def update_columns(attrs = {})
super(attrs.reverse_merge(updated_at: Time.now.utc))
end
def self.update_all(attrs = {})
super(attrs.reverse_merge(updated_at: Time.now.utc))
end
private
def update_wiki_page_attributes(page)
update_columns(title: page.title) unless page.title == title
end
def insert_slugs(strings, is_new, canonical_slug)
creation = Time.now.utc
slug_attrs = strings.map do |slug|
{
wiki_page_meta_id: id,
slug: slug,
canonical: (is_new && slug == canonical_slug),
created_at: creation,
updated_at: creation
}
end
slugs.insert_all(slug_attrs) unless !is_new && slug_attrs.size == 1
@canonical_slug = canonical_slug if is_new || strings.size == 1
end
def no_two_metarecords_in_same_project_can_have_same_canonical_slug
return unless project_id.present? && canonical_slug.present?
offending = self.class.with_canonical_slug(canonical_slug).where(project_id: project_id)
offending = offending.where.not(id: id) if persisted?
if offending.exists?
errors.add(:canonical_slug, 'each page in a wiki must have a distinct canonical slug')
end
end
end
end
# frozen_string_literal: true
class WikiPage
class Slug < ApplicationRecord
self.table_name = 'wiki_page_slugs'
belongs_to :wiki_page_meta, class_name: 'WikiPage::Meta', inverse_of: :slugs
validates :slug, presence: true, uniqueness: { scope: :wiki_page_meta_id }
validates :canonical, uniqueness: {
scope: :wiki_page_meta_id,
if: :canonical?,
message: 'Only one slug can be canonical per wiki metadata record'
}
scope :canonical, -> { where(canonical: true) }
def update_columns(attrs = {})
super(attrs.reverse_merge(updated_at: Time.now.utc))
end
def self.update_all(attrs = {})
super(attrs.reverse_merge(updated_at: Time.now.utc))
end
end
end
# frozen_string_literal: true
class WikiPage::MetaPolicy < BasePolicy
delegate { @subject.project }
end
......@@ -16,7 +16,7 @@ module Metrics
def sequences
@sequences ||= {
::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH => [::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
::Gitlab::Metrics::Dashboard::Stages::ProjectMetricsInserter,
::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
::Gitlab::Metrics::Dashboard::Stages::Sorter].freeze
}.freeze
end
......
......@@ -9,7 +9,7 @@ module Metrics
DASHBOARD_NAME = 'Default'
SEQUENCE = [
STAGES::ProjectMetricsInserter,
STAGES::CustomMetricsInserter,
STAGES::EndpointInserter,
STAGES::Sorter
].freeze
......
......@@ -10,8 +10,8 @@ module Metrics
SEQUENCE = [
STAGES::CommonMetricsInserter,
STAGES::ProjectMetricsInserter,
STAGES::ProjectMetricsDetailsInserter,
STAGES::CustomMetricsInserter,
STAGES::CustomMetricsDetailsInserter,
STAGES::EndpointInserter,
STAGES::Sorter
].freeze
......
---
title: Add limit metric to lists
merge_request: 25532
author:
type: added
---
title: Improve logs dropdown with more clear labels
merge_request: 26635
author:
type: added
---
title: Optimize ldap keys counters query performance in usage data
merge_request: 27309
author:
type: performance
---
title: Adds wiki metadata models
merge_request: 26529
author:
type: added
......@@ -234,7 +234,7 @@
- 2
- - service_desk_email_receiver
- 1
- - status_page_publish_incident
- - status_page_publish
- 1
- - sync_seat_link_request
- 1
......
# frozen_string_literal: true
class AddLimitMetricTypeToList < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :lists, :limit_metric, :string, limit: 20
end
end
# frozen_string_literal: true
class AddWikiSlug < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
create_table :wiki_page_meta, id: :serial do |t|
t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false
t.timestamps_with_timezone null: false
t.string :title, null: false, limit: 255
end
create_table :wiki_page_slugs, id: :serial do |t|
t.boolean :canonical, default: false, null: false
t.references :wiki_page_meta, index: true, foreign_key: { on_delete: :cascade }, null: false
t.timestamps_with_timezone null: false
t.string :slug, null: false, limit: 2048
t.index [:slug, :wiki_page_meta_id], unique: true
t.index [:wiki_page_meta_id], name: 'one_canonical_wiki_page_slug_per_metadata', unique: true, where: "(canonical = true)"
end
end
end
# frozen_string_literal: true
class AddIndexOnIdAndLdapKeyToKeys < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_keys_on_id_and_ldap_key_type'
disable_ddl_transaction!
def up
add_concurrent_index :keys, [:id], where: "type = 'LDAPKey'", name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :keys, INDEX_NAME
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_03_13_123934) do
ActiveRecord::Schema.define(version: 2020_03_16_111759) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -2311,6 +2311,7 @@ ActiveRecord::Schema.define(version: 2020_03_13_123934) do
t.index ["fingerprint"], name: "index_keys_on_fingerprint", unique: true
t.index ["fingerprint_sha256"], name: "index_keys_on_fingerprint_sha256"
t.index ["id", "type"], name: "index_on_deploy_keys_id_and_type_and_public", unique: true, where: "(public = true)"
t.index ["id"], name: "index_keys_on_id_and_ldap_key_type", where: "((type)::text = 'LDAPKey'::text)"
t.index ["last_used_at"], name: "index_keys_on_last_used_at", order: "DESC NULLS LAST"
t.index ["user_id"], name: "index_keys_on_user_id"
end
......@@ -2425,6 +2426,7 @@ ActiveRecord::Schema.define(version: 2020_03_13_123934) do
t.integer "milestone_id"
t.integer "max_issue_count", default: 0, null: false
t.integer "max_issue_weight", default: 0, null: false
t.string "limit_metric", limit: 20
t.index ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true
t.index ["label_id"], name: "index_lists_on_label_id"
t.index ["list_type"], name: "index_lists_on_list_type"
......@@ -4668,6 +4670,25 @@ ActiveRecord::Schema.define(version: 2020_03_13_123934) do
t.index ["type"], name: "index_web_hooks_on_type"
end
create_table "wiki_page_meta", id: :serial, force: :cascade do |t|
t.bigint "project_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "title", limit: 255, null: false
t.index ["project_id"], name: "index_wiki_page_meta_on_project_id"
end
create_table "wiki_page_slugs", id: :serial, force: :cascade do |t|
t.boolean "canonical", default: false, null: false
t.bigint "wiki_page_meta_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "slug", limit: 2048, null: false
t.index ["slug", "wiki_page_meta_id"], name: "index_wiki_page_slugs_on_slug_and_wiki_page_meta_id", unique: true
t.index ["wiki_page_meta_id"], name: "index_wiki_page_slugs_on_wiki_page_meta_id"
t.index ["wiki_page_meta_id"], name: "one_canonical_wiki_page_slug_per_metadata", unique: true, where: "(canonical = true)"
end
create_table "x509_certificates", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
......@@ -5212,6 +5233,8 @@ ActiveRecord::Schema.define(version: 2020_03_13_123934) do
add_foreign_key "vulnerability_scanners", "projects", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
add_foreign_key "wiki_page_meta", "projects", on_delete: :cascade
add_foreign_key "wiki_page_slugs", "wiki_page_meta", column: "wiki_page_meta_id", on_delete: :cascade
add_foreign_key "x509_certificates", "x509_issuers", on_delete: :cascade
add_foreign_key "x509_commit_signatures", "projects", on_delete: :cascade
add_foreign_key "x509_commit_signatures", "x509_certificates", on_delete: :cascade
......
......@@ -494,7 +494,9 @@ Below we describe the different pathing that HTTP vs. SSH Git requests will take
### Web Request (80/443)
TODO
When you make a Git request over HTTP, the request first takes the same steps as a web HTTP request
through NGINX and GitLab Workhorse. However, the GitLab Workhorse then diverts the request towards
Gitaly, which processes it directly.
### SSH Request (22)
......
......@@ -44,8 +44,9 @@ the `author` field. GitLab team members **should not**.
a changelog entry regardless of these guidelines if the contributor wants one.
Example: "Fixed a typo on the search results page."
- Any docs-only changes **should not** have a changelog entry.
- Any change behind a feature flag **should not** have a changelog entry. The
entry should be added [in the merge request removing the feature flags](feature_flags/development.md).
- Any change behind a feature flag **should not** have a changelog entry - unless
the feature flag has been defaulted to true. The entry should be added
[in the merge request removing the feature flags](feature_flags/development.md).
If the change includes a database migration (regular, post, or data migration),
there should be a changelog entry for the migration change.
- A fix for a regression introduced and then fixed in the same release (i.e.,
......
......@@ -12,6 +12,10 @@ module Gitlab
'content' => absolute_image_urls(wiki_page.content)
)
end
def uploads_prefix
''
end
end
end
end
......@@ -4,7 +4,7 @@ module Gitlab
module Metrics
module Dashboard
module Stages
class ProjectMetricsDetailsInserter < BaseStage
class CustomMetricsDetailsInserter < BaseStage
def transform!
dashboard[:panel_groups].each do |panel_group|
next unless panel_group
......
......@@ -4,7 +4,7 @@ module Gitlab
module Metrics
module Dashboard
module Stages
class ProjectMetricsInserter < BaseStage
class CustomMetricsInserter < BaseStage
# Inserts project-specific metrics into the dashboard
# config. If there are no project-specific metrics,
# this will have no effect.
......
......@@ -7723,6 +7723,9 @@ msgstr ""
msgid "Environments|Environments are places where code gets deployed, such as staging or production."
msgstr ""
msgid "Environments|Filter by pod"
msgstr ""
msgid "Environments|Install Elastic Stack on your cluster to enable advanced querying capabilities such as full text search."
msgstr ""
......@@ -7735,9 +7738,6 @@ msgstr ""
msgid "Environments|Learn more about stopping environments"
msgstr ""
msgid "Environments|Logs from"
msgstr ""
msgid "Environments|Logs from %{start} to %{end}."
msgstr ""
......@@ -7753,6 +7753,9 @@ msgstr ""
msgid "Environments|No pod selected"
msgstr ""
msgid "Environments|No pods to display"
msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
msgstr ""
......@@ -7792,10 +7795,10 @@ msgstr ""
msgid "Environments|Search"
msgstr ""
msgid "Environments|Show all"
msgid "Environments|Select environment"
msgstr ""
msgid "Environments|Show last"
msgid "Environments|Show all"
msgstr ""
msgid "Environments|Stop"
......
......@@ -22,6 +22,16 @@ FactoryBot.define do
action { Event::CLOSED }
target factory: :closed_issue
end
factory :wiki_page_event do
action { Event::CREATED }
transient do
wiki_page { create(:wiki_page, project: project) }
end
target { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
end
end
factory :push_event, class: 'PushEvent' do
......
......@@ -7,6 +7,7 @@ FactoryBot.define do
list_type { :label }
max_issue_count { 0 }
max_issue_weight { 0 }
limit_metric { nil }
sequence(:position)
end
......
......@@ -5,17 +5,22 @@ require 'ostruct'
FactoryBot.define do
factory :wiki_page do
transient do
title { generate(:wiki_page_title) }
content { 'Content for wiki page' }
format { 'markdown' }
project { create(:project) }
attrs do
{
title: 'Title.with.dot',
content: 'Content for wiki page',
format: 'markdown'
title: title,
content: content,
format: format
}
end
end
page { OpenStruct.new(url_path: 'some-name') }
association :wiki, factory: :project_wiki, strategy: :build
wiki { build(:project_wiki, project: project) }
initialize_with { new(wiki, page) }
before(:create) do |page, evaluator|
......@@ -25,5 +30,48 @@ FactoryBot.define do
to_create do |page|
page.create
end
trait :with_real_page do
project { create(:project, :repository) }
page do
wiki.create_page(title, content)
page_title, page_dir = wiki.page_title_and_dir(title)
wiki.wiki.page(title: page_title, dir: page_dir, version: nil)
end
end
end
factory :wiki_page_meta, class: 'WikiPage::Meta' do
title { generate(:wiki_page_title) }
project { create(:project) }
trait :for_wiki_page do
transient do
wiki_page { create(:wiki_page, project: project) }
end
project { @overrides[:wiki_page]&.project || create(:project) }
title { wiki_page.title }
initialize_with do
raise 'Metadata only available for valid pages' unless wiki_page.valid?
WikiPage::Meta.find_or_create(wiki_page.slug, wiki_page)
end
end
end
factory :wiki_page_slug, class: 'WikiPage::Slug' do
wiki_page_meta { create(:wiki_page_meta) }
slug { generate(:sluggified_title) }
canonical { false }
trait :canonical do
canonical { true }
end
end
sequence(:wiki_page_title) { |n| "Page #{n}" }
sequence(:sluggified_title) { |n| "slug-#{n}" }
end
......@@ -37,7 +37,8 @@
"title": { "type": "string" },
"position": { "type": ["integer", "null"] },
"max_issue_count": { "type": "integer" },
"max_issue_weight": { "type": "integer" }
"max_issue_weight": { "type": "integer" },
"limit_metric": { "type": ["string", "null"] }
},
"additionalProperties": true
}
import Vue from 'vue';
import { GlSprintf, GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui';
import { GlSprintf, GlIcon, GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import EnvironmentLogs from '~/logs/components/environment_logs.vue';
......@@ -45,6 +45,10 @@ describe('EnvironmentLogs', () => {
const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown');
const findPodsDropdown = () => wrapper.find('.js-pods-dropdown');
const findPodsDropdownItems = () =>
findPodsDropdown()
.findAll(GlDropdownItem)
.filter(itm => !itm.attributes('disabled'));
const findSearchBar = () => wrapper.find('.js-logs-search');
const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' });
const findInfoAlert = () => wrapper.find('.js-elasticsearch-alert');
......@@ -179,7 +183,7 @@ describe('EnvironmentLogs', () => {
it('displays a disabled pods dropdown', () => {
expect(findPodsDropdown().attributes('disabled')).toBe('true');
expect(findPodsDropdown().findAll(GlDropdownItem).length).toBe(0);
expect(findPodsDropdownItems()).toHaveLength(0);
});
it('displays a disabled search bar', () => {
......@@ -296,8 +300,22 @@ describe('EnvironmentLogs', () => {
});
});
it('dropdown has one environment selected', () => {
const items = findEnvironmentsDropdown().findAll(GlDropdownItem);
mockEnvironments.forEach((env, i) => {
const item = items.at(i);
if (item.text() !== mockEnvName) {
expect(item.find(GlIcon).classes()).toContain('invisible');
} else {
// selected
expect(item.find(GlIcon).classes()).not.toContain('invisible');
}
});
});
it('populates pods dropdown', () => {
const items = findPodsDropdown().findAll(GlDropdownItem);
const items = findPodsDropdownItems();
expect(findPodsDropdown().props('text')).toBe(mockPodName);
expect(items.length).toBe(mockPods.length + 1);
......@@ -313,6 +331,19 @@ describe('EnvironmentLogs', () => {
expect(getInfiniteScrollAttr('fetched-items')).toBe(mockTrace.length);
});
it('dropdown has one pod selected', () => {
const items = findPodsDropdownItems();
mockPods.forEach((pod, i) => {
const item = items.at(i);
if (item.text() !== mockPodName) {
expect(item.find(GlIcon).classes()).toContain('invisible');
} else {
// selected
expect(item.find(GlIcon).classes()).not.toContain('invisible');
}
});
});
it('populates logs trace', () => {
const trace = findLogTrace();
expect(trace.text().split('\n').length).toBe(mockTrace.length);
......@@ -341,7 +372,7 @@ describe('EnvironmentLogs', () => {
});
it('pod name, trace is refreshed', () => {
const items = findPodsDropdown().findAll(GlDropdownItem);
const items = findPodsDropdownItems();
const index = 2; // any pod
expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
......
......@@ -167,30 +167,30 @@ describe Types::BaseField do
end
end
end
end
describe '#description' do
context 'feature flag given' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description') }
let(:flag) { :test_flag }
describe '#description' do
context 'feature flag given' do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description') }
let(:flag) { :test_flag }
it 'prepends the description' do
expect(field.description). to eq 'Test description. Available only when feature flag `test_flag` is enabled.'
end
it 'prepends the description' do
expect(field.description). to eq 'Test description. Available only when feature flag `test_flag` is enabled.'
end
context 'falsey feature_flag values' do
using RSpec::Parameterized::TableSyntax
context 'falsey feature_flag values' do
using RSpec::Parameterized::TableSyntax
where(:flag, :feature_value) do
'' | false
'' | true
nil | false
nil | true
end
where(:flag, :feature_value) do
'' | false
'' | true
nil | false
nil | true
end
with_them do
it 'returns the correct description' do
expect(field.description).to eq('Test description')
end
with_them do
it 'returns the correct description' do
expect(field.description).to eq('Test description')
end
end
end
......
......@@ -40,6 +40,14 @@ Event:
- updated_at
- action
- author_id
WikiPage::Meta:
- id
- title
- project_id
WikiPage::Slug:
- id
- wiki_page_meta_id
- slug
PushEventPayload:
- commit_count
- action
......@@ -756,6 +764,7 @@ List:
- user_id
- max_issue_count
- max_issue_weight
- limit_metric
ExternalPullRequest:
- id
- created_at
......
......@@ -11,8 +11,8 @@ describe Gitlab::Metrics::Dashboard::Processor do
let(:sequence) do
[
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::ProjectMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::ProjectMetricsDetailsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
]
......
This diff is collapsed.
This diff is collapsed.
# frozen_string_literal: true
require 'spec_helper'
describe WikiPage::Slug do
let_it_be(:meta) { create(:wiki_page_meta) }
describe 'Associations' do
it { is_expected.to belong_to(:wiki_page_meta) }
it 'refers correctly to the wiki_page_meta' do
created = create(:wiki_page_slug, wiki_page_meta: meta)
expect(created.reload.wiki_page_meta).to eq(meta)
end
end
describe 'scopes' do
describe 'canonical' do
subject { described_class.canonical }
context 'there are no slugs' do
it { is_expected.to be_empty }
end
context 'there are some non-canonical slugs' do
before do
create(:wiki_page_slug)
end
it { is_expected.to be_empty }
end
context 'there is at least one canonical slugs' do
before do
create(:wiki_page_slug, :canonical)
end
it { is_expected.not_to be_empty }
end
end
end
describe 'Validations' do
let(:canonical) { false }
subject do
build(:wiki_page_slug, canonical: canonical, wiki_page_meta: meta)
end
it { is_expected.to validate_presence_of(:slug) }
it { is_expected.to validate_uniqueness_of(:slug).scoped_to(:wiki_page_meta_id) }
describe 'only_one_slug_can_be_canonical_per_meta_record' do
context 'there are no other slugs' do
it { is_expected.to be_valid }
context 'the current slug is canonical' do
let(:canonical) { true }
it { is_expected.to be_valid }
end
end
context 'there are other slugs, but they are not canonical' do
before do
create(:wiki_page_slug, wiki_page_meta: meta)
end
it { is_expected.to be_valid }
context 'the current slug is canonical' do
let(:canonical) { true }
it { is_expected.to be_valid }
end
end
context 'there is already a canonical slug' do
before do
create(:wiki_page_slug, canonical: true, wiki_page_meta: meta)
end
it { is_expected.to be_valid }
context 'the current slug is canonical' do
let(:canonical) { true }
it { is_expected.not_to be_valid }
end
end
end
end
end
......@@ -606,12 +606,36 @@ describe WikiPage do
expect(subject).to eq(subject)
end
it 'returns false for updated wiki page' do
it 'returns true for updated wiki page' do
subject.update(content: "Updated content")
updated_page = wiki.find_page('test page')
updated_page = wiki.find_page(existing_page.slug)
expect(updated_page).not_to be_nil
expect(updated_page).not_to eq(subject)
expect(updated_page).to eq(subject)
end
it 'returns false for a completely different wiki page' do
other_page = create(:wiki_page)
expect(subject.slug).not_to eq(other_page.slug)
expect(subject.project).not_to eq(other_page.project)
expect(subject).not_to eq(other_page)
end
it 'returns false for page with different slug on same project' do
other_page = create(:wiki_page, project: subject.project)
expect(subject.slug).not_to eq(other_page.slug)
expect(subject.project).to eq(other_page.project)
expect(subject).not_to eq(other_page)
end
it 'returns false for page with the same slug on a different project' do
other_page = create(:wiki_page, title: existing_page.slug)
expect(subject.slug).to eq(other_page.slug)
expect(subject.project).not_to eq(other_page.project)
expect(subject).not_to eq(other_page)
end
end
......
......@@ -83,7 +83,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash))
end
it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH, [STAGES::CommonMetricsInserter, STAGES::ProjectMetricsInserter, STAGES::Sorter]
it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH, [STAGES::CommonMetricsInserter, STAGES::CustomMetricsInserter, STAGES::Sorter]
context 'selected branch already exists' do
let(:branch) { 'existing_branch' }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment