Commit 228d752f authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent b539ac1d
...@@ -617,7 +617,7 @@ GitLabDropdown = (function() { ...@@ -617,7 +617,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.hidden = function(e) { GitLabDropdown.prototype.hidden = function(e) {
var $input; var $input;
this.resetRows(); this.resetRows();
this.removeArrayKeyEvent(); this.removeArrowKeyEvent();
$input = this.dropdown.find('.dropdown-input-field'); $input = this.dropdown.find('.dropdown-input-field');
if (this.options.filterable) { if (this.options.filterable) {
$input.blur(); $input.blur();
...@@ -900,7 +900,7 @@ GitLabDropdown = (function() { ...@@ -900,7 +900,7 @@ GitLabDropdown = (function() {
); );
}; };
GitLabDropdown.prototype.removeArrayKeyEvent = function() { GitLabDropdown.prototype.removeArrowKeyEvent = function() {
return $('body').off('keydown'); return $('body').off('keydown');
}; };
......
<script>
import { GlLink } from '@gitlab/ui';
export default {
components: {
GlLink,
},
props: {
currentPath: {
type: String,
required: false,
default: null,
},
links: {
type: Array,
required: true,
},
},
computed: {
normalizedLinks() {
return this.links.map(link => ({
text: link.text,
path: `${link.path}?path=${this.currentPath}`,
}));
},
},
};
</script>
<template>
<section class="border-top pt-1 mt-1">
<h5 class="m-0 dropdown-bold-header">{{ __('Download this directory') }}</h5>
<div class="dropdown-menu-content">
<div class="btn-group ml-0 w-100">
<gl-link
v-for="(link, index) in normalizedLinks"
:key="index"
:href="link.path"
:class="{ 'btn-primary': index === 0 }"
class="btn btn-xs"
>
{{ link.text }}
</gl-link>
</div>
</div>
</section>
</template>
<script>
import { GlLink } from '@gitlab/ui';
export default {
components: {
GlLink,
},
props: {
path: {
type: String,
required: true,
},
text: {
type: String,
required: true,
},
cssClass: {
type: String,
required: false,
default: null,
},
},
};
</script>
<template>
<gl-link :href="path" :class="cssClass" class="btn">{{ text }}</gl-link>
</template>
...@@ -3,9 +3,13 @@ import createRouter from './router'; ...@@ -3,9 +3,13 @@ import createRouter from './router';
import App from './components/app.vue'; import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue'; import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue'; import LastCommit from './components/last_commit.vue';
import TreeActionLink from './components/tree_action_link.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import { setTitle } from './utils/title'; import { setTitle } from './utils/title';
import { parseBoolean } from '../lib/utils/common_utils'; import { parseBoolean } from '../lib/utils/common_utils';
import { webIDEUrl } from '../lib/utils/url_utility';
import { __ } from '../locale';
export default function setupVueRepositoryList() { export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list'); const el = document.getElementById('js-tree-list');
...@@ -91,6 +95,66 @@ export default function setupVueRepositoryList() { ...@@ -91,6 +95,66 @@ export default function setupVueRepositoryList() {
}, },
}); });
const treeHistoryLinkEl = document.getElementById('js-tree-history-link');
const { historyLink } = treeHistoryLinkEl.dataset;
// eslint-disable-next-line no-new
new Vue({
el: treeHistoryLinkEl,
router,
render(h) {
return h(TreeActionLink, {
props: {
path: historyLink + (this.$route.params.pathMatch || '/'),
text: __('History'),
},
});
},
});
const webIdeLinkEl = document.getElementById('js-tree-web-ide-link');
if (webIdeLinkEl) {
// eslint-disable-next-line no-new
new Vue({
el: webIdeLinkEl,
router,
render(h) {
return h(TreeActionLink, {
props: {
path: webIDEUrl(`/${projectPath}/edit/${ref}/-${this.$route.params.pathMatch || '/'}`),
text: __('Web IDE'),
cssClass: 'qa-web-ide-button',
},
});
},
});
}
const directoryDownloadLinks = document.getElementById('js-directory-downloads');
if (directoryDownloadLinks) {
// eslint-disable-next-line no-new
new Vue({
el: directoryDownloadLinks,
router,
render(h) {
const currentPath = this.$route.params.pathMatch || '/';
if (currentPath !== '/') {
return h(DirectoryDownloadLinks, {
props: {
currentPath: currentPath.replace(/^\//, ''),
links: JSON.parse(directoryDownloadLinks.dataset.links),
},
});
}
return null;
},
});
}
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el, el,
......
/* eslint-disable no-return-assign, one-var, no-var, consistent-return, class-methods-use-this, no-lonely-if, vars-on-top */ /* eslint-disable no-return-assign, one-var, no-var, consistent-return, class-methods-use-this, vars-on-top */
import $ from 'jquery'; import $ from 'jquery';
import { escape, throttle } from 'underscore'; import { escape, throttle } from 'underscore';
...@@ -95,7 +95,6 @@ export class SearchAutocomplete { ...@@ -95,7 +95,6 @@ export class SearchAutocomplete {
this.createAutocomplete(); this.createAutocomplete();
} }
this.saveTextLength();
this.bindEvents(); this.bindEvents();
this.dropdownToggle.dropdown(); this.dropdownToggle.dropdown();
this.searchInput.addClass('js-autocomplete-disabled'); this.searchInput.addClass('js-autocomplete-disabled');
...@@ -107,7 +106,7 @@ export class SearchAutocomplete { ...@@ -107,7 +106,7 @@ export class SearchAutocomplete {
this.onClearInputClick = this.onClearInputClick.bind(this); this.onClearInputClick = this.onClearInputClick.bind(this);
this.onSearchInputFocus = this.onSearchInputFocus.bind(this); this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); this.onSearchInputChange = this.onSearchInputChange.bind(this);
this.setScrollFade = this.setScrollFade.bind(this); this.setScrollFade = this.setScrollFade.bind(this);
} }
getElement(selector) { getElement(selector) {
...@@ -118,10 +117,6 @@ export class SearchAutocomplete { ...@@ -118,10 +117,6 @@ export class SearchAutocomplete {
return (this.originalState = this.serializeState()); return (this.originalState = this.serializeState());
} }
saveTextLength() {
return (this.lastTextLength = this.searchInput.val().length);
}
createAutocomplete() { createAutocomplete() {
return this.searchInput.glDropdown({ return this.searchInput.glDropdown({
filterInputBlur: false, filterInputBlur: false,
...@@ -318,12 +313,16 @@ export class SearchAutocomplete { ...@@ -318,12 +313,16 @@ export class SearchAutocomplete {
} }
bindEvents() { bindEvents() {
this.searchInput.on('keydown', this.onSearchInputKeyDown); this.searchInput.on('input', this.onSearchInputChange);
this.searchInput.on('keyup', this.onSearchInputKeyUp); this.searchInput.on('keyup', this.onSearchInputKeyUp);
this.searchInput.on('focus', this.onSearchInputFocus); this.searchInput.on('focus', this.onSearchInputFocus);
this.searchInput.on('blur', this.onSearchInputBlur); this.searchInput.on('blur', this.onSearchInputBlur);
this.clearInput.on('click', this.onClearInputClick); this.clearInput.on('click', this.onClearInputClick);
this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250)); this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250));
this.searchInput.on('click', e => {
e.stopPropagation();
});
} }
enableAutocomplete() { enableAutocomplete() {
...@@ -342,43 +341,19 @@ export class SearchAutocomplete { ...@@ -342,43 +341,19 @@ export class SearchAutocomplete {
} }
} }
// Saves last length of the entered text onSearchInputChange() {
onSearchInputKeyDown() { this.enableAutocomplete();
return this.saveTextLength();
} }
onSearchInputKeyUp(e) { onSearchInputKeyUp(e) {
switch (e.keyCode) { switch (e.keyCode) {
case KEYCODE.BACKSPACE:
// When removing the last character and no badge is present
if (this.lastTextLength === 1) {
this.disableAutocomplete();
}
// When removing any character from existin value
if (this.lastTextLength > 1) {
this.enableAutocomplete();
}
break;
case KEYCODE.ESCAPE: case KEYCODE.ESCAPE:
this.restoreOriginalState(); this.restoreOriginalState();
break; break;
case KEYCODE.ENTER: case KEYCODE.ENTER:
this.disableAutocomplete(); this.disableAutocomplete();
break; break;
case KEYCODE.UP:
case KEYCODE.DOWN:
return;
default: default:
// Handle the case when deleting the input value other than backspace
// e.g. Pressing ctrl + backspace or ctrl + x
if (this.searchInput.val() === '') {
this.disableAutocomplete();
} else {
// We should display the menu only when input is not empty
if (e.keyCode !== KEYCODE.ENTER) {
this.enableAutocomplete();
}
}
} }
this.wrap.toggleClass('has-value', Boolean(e.target.value)); this.wrap.toggleClass('has-value', Boolean(e.target.value));
} }
...@@ -434,7 +409,7 @@ export class SearchAutocomplete { ...@@ -434,7 +409,7 @@ export class SearchAutocomplete {
disableAutocomplete() { disableAutocomplete() {
if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) { if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
this.searchInput.addClass('js-autocomplete-disabled'); this.searchInput.addClass('js-autocomplete-disabled');
this.dropdown.removeClass('show').trigger('hidden.bs.dropdown'); this.dropdown.dropdown('toggle');
this.restoreMenu(); this.restoreMenu();
} }
} }
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
module Types module Types
class TodoTargetEnum < BaseEnum class TodoTargetEnum < BaseEnum
value 'Issue' value 'COMMIT', value: 'Commit', description: 'A Commit'
value 'MergeRequest' value 'ISSUE', value: 'Issue', description: 'An Issue'
value 'Epic' value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
end end
end end
Types::TodoTargetEnum.prepend_if_ee('::EE::Types::TodoTargetEnum')
...@@ -40,7 +40,8 @@ module Types ...@@ -40,7 +40,8 @@ module Types
field :body, GraphQL::STRING_TYPE, field :body, GraphQL::STRING_TYPE,
description: 'Body of the todo', description: 'Body of the todo',
null: false null: false,
calls_gitaly: true # TODO This is only true when `target_type` is `Commit`. See https://gitlab.com/gitlab-org/gitlab/issues/34757#note_234752665
field :state, Types::TodoStateEnum, field :state, Types::TodoStateEnum,
description: 'State of the todo', description: 'State of the todo',
......
...@@ -195,6 +195,17 @@ module TreeHelper ...@@ -195,6 +195,17 @@ module TreeHelper
full_name: project.name_with_namespace full_name: project.name_with_namespace
} }
end end
def directory_download_links(project, ref, archive_prefix)
formats = ['zip', 'tar.gz', 'tar.bz2', 'tar']
formats.map do |fmt|
{
text: fmt,
path: project_archive_path(project, id: tree_join(ref, archive_prefix), format: fmt)
}
end
end
end end
TreeHelper.prepend_if_ee('::EE::TreeHelper') TreeHelper.prepend_if_ee('::EE::TreeHelper')
...@@ -23,7 +23,7 @@ module Clusters ...@@ -23,7 +23,7 @@ module Clusters
end end
def validate_params(cluster) def validate_params(cluster)
if params[:management_project_id] if params[:management_project_id].present?
management_project = management_project_scope(cluster).find_by_id(params[:management_project_id]) management_project = management_project_scope(cluster).find_by_id(params[:management_project_id])
unless management_project unless management_project
......
...@@ -12,11 +12,14 @@ ...@@ -12,11 +12,14 @@
%h5.m-0.dropdown-bold-header= _('Download source code') %h5.m-0.dropdown-bold-header= _('Download source code')
.dropdown-menu-content .dropdown-menu-content
= render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil
- if directory? && Feature.enabled?(:git_archive_path, default_enabled: true) - if Feature.enabled?(:git_archive_path, default_enabled: true)
%section.border-top.pt-1.mt-1 - if vue_file_list_enabled?
%h5.m-0.dropdown-bold-header= _('Download this directory') #js-directory-downloads{ data: { links: directory_download_links(project, ref, archive_prefix).to_json } }
.dropdown-menu-content - elsif directory?
= render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path %section.border-top.pt-1.mt-1
%h5.m-0.dropdown-bold-header= _('Download this directory')
.dropdown-menu-content
= render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
- if pipeline && pipeline.latest_builds_with_artifacts.any? - if pipeline && pipeline.latest_builds_with_artifacts.any?
%section.border-top.pt-1.mt-1 %section.border-top.pt-1.mt-1
%h5.m-0.dropdown-bold-header= _('Download artifacts') %h5.m-0.dropdown-bold-header= _('Download artifacts')
......
...@@ -77,15 +77,21 @@ ...@@ -77,15 +77,21 @@
.tree-controls .tree-controls
= render_if_exists 'projects/tree/lock_link' = render_if_exists 'projects/tree/lock_link'
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' - if vue_file_list_enabled?
#js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
- else
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link' = render 'projects/find_file_link'
- if can_create_mr_from_fork - if can_create_mr_from_fork
= succeed " " do = succeed " " do
- if can_collaborate || current_user&.already_forked?(@project) - if can_collaborate || current_user&.already_forked?(@project)
= link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do - if vue_file_list_enabled?
= _('Web IDE') #js-tree-web-ide-link.d-inline-block
- else
= link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
= _('Web IDE')
- else - else
= link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do = link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
= _('Web IDE') = _('Web IDE')
......
---
title: 'Puma only: database connection pool now always >= number of worker threads'
merge_request: 19286
author:
type: performance
---
title: Fix keyboard shortcuts in header search autocomplete
merge_request: 18685
author:
type: fixed
---
title: Update expired trial status copy
merge_request: 18962
author:
type: changed
---
title: Fix errors in GraphQL Todos API due to missing TargetTypeEnum values
merge_request: 19052
author:
type: fixed
---
title: Add endpoint for a group's vulnerable projects
merge_request: 15317
author:
type: added
---
title: Remove empty Github service templates from database
merge_request: 18868
author:
type: fixed
---
title: Enforce default, global project and snippet visibilities
merge_request: 19188
author:
type: fixed
# frozen_string_literal: true
# when running on puma, scale connection pool size with the number
# of threads per worker process
if defined?(::Puma)
db_config = Gitlab::Database.config ||
Rails.application.config.database_configuration[Rails.env]
puma_options = Puma.cli_config.options
# We use either the maximum number of threads per worker process, or
# the user specified value, whichever is larger.
desired_pool_size = [db_config['pool'].to_i, puma_options[:max_threads]].max
db_config['pool'] = desired_pool_size
# recreate the connection pool from the new config
ActiveRecord::Base.establish_connection(db_config)
end
# frozen_string_literal: true
class SetApplicationSettingsDefaultProjectAndSnippetVisibility < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
change_column_null :application_settings, :default_project_visibility, false, 0
change_column_default :application_settings, :default_project_visibility, from: nil, to: 0
change_column_null :application_settings, :default_snippet_visibility, false, 0
change_column_default :application_settings, :default_snippet_visibility, from: nil, to: 0
end
end
# frozen_string_literal: true
## It's expected to delete one record on GitLab.com
#
class RemoveEmptyGithubServiceTemplates < ActiveRecord::Migration[5.2]
DOWNTIME = false
class Service < ActiveRecord::Base
self.table_name = 'services'
self.inheritance_column = :_type_disabled
serialize :properties, JSON
end
def up
relationship.where(properties: {}).delete_all
end
def down
relationship.find_or_create_by!(properties: {})
end
private
def relationship
RemoveEmptyGithubServiceTemplates::Service.where(template: true, type: 'GithubService')
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_10_26_041447) do ActiveRecord::Schema.define(version: 2019_10_26_124116) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -158,8 +158,8 @@ ActiveRecord::Schema.define(version: 2019_10_26_041447) do ...@@ -158,8 +158,8 @@ ActiveRecord::Schema.define(version: 2019_10_26_041447) do
t.text "restricted_visibility_levels" t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility" t.integer "default_project_visibility", default: 0, null: false
t.integer "default_snippet_visibility" t.integer "default_snippet_visibility", default: 0, null: false
t.text "domain_whitelist" t.text "domain_whitelist"
t.boolean "user_oauth_applications", default: true t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path" t.string "after_sign_out_path"
......
...@@ -5,7 +5,7 @@ require 'rails/generators' ...@@ -5,7 +5,7 @@ require 'rails/generators'
module Rails module Rails
class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
def create_migration_file def create_migration_file
timestamp = Time.now.strftime('%Y%m%d%H%M%S') timestamp = Time.now.utc.strftime('%Y%m%d%H%M%S')
template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb" template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
end end
......
...@@ -2421,9 +2421,6 @@ msgstr "" ...@@ -2421,9 +2421,6 @@ msgstr ""
msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold." msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}, or start a free 30-day trial of GitLab.com Gold."
msgstr "" msgstr ""
msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
msgstr ""
msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}." msgid "BillingPlans|Learn more about each plan by visiting our %{pricing_page_link}."
msgstr "" msgstr ""
...@@ -2442,18 +2439,15 @@ msgstr "" ...@@ -2442,18 +2439,15 @@ msgstr ""
msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
msgstr "" msgstr ""
msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. %{learn_more_text}" msgid "BillingPlans|Your GitLab.com Gold trial expired on %{expiration_date}. You can restore access to the Gold features at any time by upgrading below."
msgstr "" msgstr ""
msgid "BillingPlans|Your GitLab.com trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}." msgid "BillingPlans|Your GitLab.com Gold trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the Gold features by upgrading below."
msgstr "" msgstr ""
msgid "BillingPlans|billed annually at %{price_per_year}" msgid "BillingPlans|billed annually at %{price_per_year}"
msgstr "" msgstr ""
msgid "BillingPlans|features"
msgstr ""
msgid "BillingPlans|frequently asked questions" msgid "BillingPlans|frequently asked questions"
msgstr "" msgstr ""
......
...@@ -26,6 +26,16 @@ describe 'User uses header search field', :js do ...@@ -26,6 +26,16 @@ describe 'User uses header search field', :js do
end end
end end
context 'when using the keyboard shortcut' do
before do
find('body').native.send_keys('s')
end
it 'shows the category search dropdown' do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
end
context 'when clicking the search field' do context 'when clicking the search field' do
before do before do
page.find('#search.js-autocomplete-disabled').click page.find('#search.js-autocomplete-disabled').click
...@@ -77,15 +87,21 @@ describe 'User uses header search field', :js do ...@@ -77,15 +87,21 @@ describe 'User uses header search field', :js do
end end
context 'when entering text into the search field' do context 'when entering text into the search field' do
before do it 'does not display the category search dropdown' do
page.within('.search-input-wrap') do page.within('.search-input-wrap') do
fill_in('search', with: scope_name.first(4)) fill_in('search', with: scope_name.first(4))
end end
end
it 'does not display the category search dropdown' do
expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i) expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i)
end end
it 'hides the dropdown when there are no results' do
page.within('.search-input-wrap') do
fill_in('search', with: 'a_search_term_with_no_results')
end
expect(page).not_to have_selector('.dropdown-menu')
end
end end
end end
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Repository directory download links component renders downloads links for path app 1`] = `
<section
class="border-top pt-1 mt-1"
>
<h5
class="m-0 dropdown-bold-header"
>
Download this directory
</h5>
<div
class="dropdown-menu-content"
>
<div
class="btn-group ml-0 w-100"
>
<gllink-stub
class="btn btn-xs btn-primary"
href="http://test.com/?path=app"
>
zip
</gllink-stub>
<gllink-stub
class="btn btn-xs"
href="http://test.com/?path=app"
>
tar
</gllink-stub>
</div>
</div>
</section>
`;
exports[`Repository directory download links component renders downloads links for path app/assets 1`] = `
<section
class="border-top pt-1 mt-1"
>
<h5
class="m-0 dropdown-bold-header"
>
Download this directory
</h5>
<div
class="dropdown-menu-content"
>
<div
class="btn-group ml-0 w-100"
>
<gllink-stub
class="btn btn-xs btn-primary"
href="http://test.com/?path=app/assets"
>
zip
</gllink-stub>
<gllink-stub
class="btn btn-xs"
href="http://test.com/?path=app/assets"
>
tar
</gllink-stub>
</div>
</div>
</section>
`;
import { shallowMount } from '@vue/test-utils';
import DirectoryDownloadLinks from '~/repository/components/directory_download_links.vue';
let vm;
function factory(currentPath) {
vm = shallowMount(DirectoryDownloadLinks, {
propsData: {
currentPath,
links: [{ text: 'zip', path: 'http://test.com/' }, { text: 'tar', path: 'http://test.com/' }],
},
});
}
describe('Repository directory download links component', () => {
afterEach(() => {
vm.destroy();
});
it.each`
path
${'app'}
${'app/assets'}
`('renders downloads links for path $path', ({ path }) => {
factory(path);
expect(vm.element).toMatchSnapshot();
});
});
import Vue from 'vue'; import Vue from 'vue';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import { PathIdSeparator } from 'ee/related_issues/constants';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import { import {
defaultAssignees, defaultAssignees,
...@@ -12,7 +13,7 @@ describe('RelatedIssuableItem', () => { ...@@ -12,7 +13,7 @@ describe('RelatedIssuableItem', () => {
const props = { const props = {
idKey: 1, idKey: 1,
displayReference: 'gitlab-org/gitlab-test#1', displayReference: 'gitlab-org/gitlab-test#1',
pathIdSeparator: '#', pathIdSeparator: PathIdSeparator.Issue,
path: `${gl.TEST_HOST}/path`, path: `${gl.TEST_HOST}/path`,
title: 'title', title: 'title',
confidential: true, confidential: true,
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Database config initializer' do
subject do
load Rails.root.join('config/initializers/database_config.rb')
end
before do
allow(ActiveRecord::Base).to receive(:establish_connection)
end
context "when using Puma" do
let(:puma) { double('puma') }
let(:puma_options) { { max_threads: 8 } }
before do
stub_const("Puma", puma)
allow(puma).to receive_message_chain(:cli_config, :options).and_return(puma_options)
end
context "and no existing pool size is set" do
before do
stub_database_config(pool_size: nil)
end
it "sets it to the max number of worker threads" do
expect { subject }.to change { Gitlab::Database.config['pool'] }.from(nil).to(8)
end
end
context "and the existing pool size is smaller than the max number of worker threads" do
before do
stub_database_config(pool_size: 7)
end
it "sets it to the max number of worker threads" do
expect { subject }.to change { Gitlab::Database.config['pool'] }.from(7).to(8)
end
end
context "and the existing pool size is larger than the max number of worker threads" do
before do
stub_database_config(pool_size: 9)
end
it "keeps the configured pool size" do
expect { subject }.not_to change { Gitlab::Database.config['pool'] }
end
end
end
context "when not using Puma" do
before do
stub_database_config(pool_size: 7)
end
it "does nothing" do
expect { subject }.not_to change { Gitlab::Database.config['pool'] }
end
end
def stub_database_config(pool_size:)
config = {
'adapter' => 'postgresql',
'host' => 'db.host.com',
'pool' => pool_size
}.compact
allow(Gitlab::Database).to receive(:config).and_return(config)
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191021101942_remove_empty_github_service_templates.rb')
describe RemoveEmptyGithubServiceTemplates, :migration do
subject(:migration) { described_class.new }
let(:services) do
table(:services).tap do |klass|
klass.class_eval do
serialize :properties, JSON
end
end
end
before do
services.delete_all
create_service(properties: nil)
create_service(properties: {})
create_service(properties: { some: :value })
create_service(properties: {}, template: false)
create_service(properties: {}, type: 'SomeType')
end
def all_service_properties
services.where(template: true, type: 'GithubService').pluck(:properties)
end
it 'correctly migrates up and down service templates' do
reversible_migration do |migration|
migration.before -> do
expect(services.count).to eq(5)
expect(all_service_properties)
.to match(a_collection_containing_exactly(nil, {}, { 'some' => 'value' }))
end
migration.after -> do
expect(services.count).to eq(4)
expect(all_service_properties)
.to match(a_collection_containing_exactly(nil, { 'some' => 'value' }))
end
end
end
def create_service(params)
data = { template: true, type: 'GithubService' }
data.merge!(params)
services.create!(data)
end
end
...@@ -13,7 +13,7 @@ describe 'Query current user todos' do ...@@ -13,7 +13,7 @@ describe 'Query current user todos' do
let(:fields) do let(:fields) do
<<~QUERY <<~QUERY
nodes { nodes {
id #{all_graphql_fields_for('todos'.classify)}
} }
QUERY QUERY
end end
...@@ -28,6 +28,8 @@ describe 'Query current user todos' do ...@@ -28,6 +28,8 @@ describe 'Query current user todos' do
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
it_behaves_like 'a working graphql query'
it 'contains the expected ids' do it 'contains the expected ids' do
is_expected.to include( is_expected.to include(
a_hash_including('id' => commit_todo.to_global_id.to_s), a_hash_including('id' => commit_todo.to_global_id.to_s),
...@@ -35,4 +37,12 @@ describe 'Query current user todos' do ...@@ -35,4 +37,12 @@ describe 'Query current user todos' do
a_hash_including('id' => merge_request_todo.to_global_id.to_s) a_hash_including('id' => merge_request_todo.to_global_id.to_s)
) )
end end
it 'returns Todos for all target types' do
is_expected.to include(
a_hash_including('targetType' => 'COMMIT'),
a_hash_including('targetType' => 'ISSUE'),
a_hash_including('targetType' => 'MERGEREQUEST')
)
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe 'getting project information' do
include GraphqlHelpers
let(:query) do
graphql_query_for('currentUser', {}, 'name')
end
subject { graphql_data['currentUser'] }
before do
post_graphql(query, current_user: current_user)
end
context 'when there is a current_user' do
set(:current_user) { create(:user) }
it_behaves_like 'a working graphql query'
it { is_expected.to include('name' => current_user.name) }
end
context 'when there is no current_user' do
let(:current_user) { nil }
it_behaves_like 'a working graphql query'
it { is_expected.to be_nil }
end
end
...@@ -138,6 +138,23 @@ describe Clusters::UpdateService do ...@@ -138,6 +138,23 @@ describe Clusters::UpdateService do
expect(cluster.management_project_id).to be_nil expect(cluster.management_project_id).to be_nil
end end
end end
context 'cluster already has a management project set' do
before do
cluster.update!(management_project: create(:project))
end
let(:params) do
{ management_project_id: '' }
end
it 'unsets management_project_id' do
is_expected.to eq(true)
cluster.reload
expect(cluster.management_project_id).to be_nil
end
end
end end
context 'project cluster' do context 'project cluster' do
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
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