Commit 42b933ef authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 3d67f14e
......@@ -8,7 +8,6 @@ globals:
IS_EE: false
plugins:
- import
- html
- "@gitlab/i18n"
- "@gitlab/vue-i18n"
settings:
......
<script>
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
export default {
components: {
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
},
props: {
fieldName: {
type: String,
required: false,
default: '',
},
placeholder: {
type: String,
required: false,
default: '',
},
defaultValue: {
type: String,
required: false,
default: '',
},
value: {
type: Object,
required: false,
default: () => null,
},
labelProperty: {
type: String,
required: false,
default: 'name',
},
valueProperty: {
type: String,
required: false,
default: 'value',
},
items: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
disabled: {
type: Boolean,
required: false,
default: false,
},
loadingText: {
type: String,
required: false,
default: '',
},
disabledText: {
type: String,
required: false,
default: '',
},
hasErrors: {
type: Boolean,
required: false,
default: false,
},
errorMessage: {
type: String,
required: false,
default: '',
},
searchFieldPlaceholder: {
type: String,
required: false,
default: '',
},
emptyText: {
type: String,
required: false,
default: '',
},
searchFn: {
type: Function,
required: false,
default: searchQuery => item => item.name.toLowerCase().indexOf(searchQuery) > -1,
},
},
data() {
return {
searchQuery: '',
selectedItem: null,
};
},
computed: {
toggleText() {
if (this.loading && this.loadingText) {
return this.loadingText;
}
if (this.disabled && this.disabledText) {
return this.disabledText;
}
if (!this.selectedItem) {
return this.placeholder;
}
return this.selectedItemLabel;
},
results() {
if (!this.items) {
return [];
}
return this.items.filter(this.searchFn(this.searchQuery));
},
selectedItemLabel() {
return this.selectedItem && this.selectedItem[this.labelProperty];
},
selectedItemValue() {
return (this.selectedItem && this.selectedItem[this.valueProperty]) || '';
},
},
methods: {
select(item) {
this.selectedItem = item;
this.$emit('input', item);
},
},
};
</script>
<template>
<div>
<div class="js-gcp-machine-type-dropdown dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemValue" />
<dropdown-button
:class="{ 'border-danger': hasErrors }"
:is-disabled="disabled"
:is-loading="loading"
:toggle-text="toggleText"
/>
<div class="dropdown-menu dropdown-select">
<dropdown-search-input v-model="searchQuery" :placeholder-text="searchFieldPlaceholder" />
<div class="dropdown-content">
<ul>
<li v-if="!results.length">
<span class="js-empty-text menu-item">
{{ emptyText }}
</span>
</li>
<li v-for="item in results" :key="item.id">
<button class="js-dropdown-item" type="button" @click.prevent="select(item)">
<slot name="item" :item="item">
{{ item.name }}
</slot>
</button>
</li>
</ul>
</div>
</div>
</div>
<span
v-if="hasErrors && errorMessage"
:class="[
'form-text js-eks-dropdown-error-message',
{
'text-danger': hasErrors,
'text-muted': !hasErrors,
},
]"
>
{{ errorMessage }}
</span>
</div>
</template>
......@@ -14,5 +14,12 @@ export default {
};
</script>
<template>
<form name="eks-cluster-configuration-form"></form>
<form name="eks-cluster-configuration-form">
<div class="form-group">
<label class="label-bold" name="role" for="eks-role">
{{ s__('ClusterIntegration|Role name') }}
</label>
<role-name-dropdown />
</div>
</form>
</template>
<script>
import { sprintf, s__ } from '~/locale';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
export default {
components: {
ClusterFormDropdown,
},
props: {
roles: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
helpText() {
return sprintf(
s__(
'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
endLink: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div>
<cluster-form-dropdown
field-id="eks-role-name"
field-name="eks-role-name"
:items="roles"
:loading="loading"
:loading-text="s__('ClusterIntegration|Loading IAM Roles')"
:placeholder="s__('ClusterIntergation|Select role name')"
:search-field-placeholder="s__('ClusterIntegration|Search IAM Roles')"
:empty-text="s__('ClusterIntegration|No IAM Roles found')"
/>
<p class="form-text text-muted" v-html="helpText"></p>
</div>
</template>
......@@ -49,12 +49,12 @@
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- next if mirror.new_record?
%tr.qa-mirrored-repository-row.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
%td.qa-mirror-repository-url= mirror.safe_url || _('Invalid URL')
%tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row' } }
%td{ data: { qa_selector: 'mirror_repository_url_cell' } }= mirror.safe_url || _('Invalid URL')
%td= _('Push')
%td
= mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
%td.qa-mirror-last-update-at= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
%td{ data: { qa_selector: 'mirror_last_update_at_cell' } }= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
%td
- if mirror.disabled?
= render 'projects/mirrors/disabled_mirror_badge'
......
---
title: Create a project for self-monitoring the GitLab instance
merge_request: 31389
author:
type: added
# frozen_string_literal: true
::Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
# frozen_string_literal: true
::Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
# frozen_string_literal: true
class AddGitlabInstanceAdministrationProject < ActiveRecord::Migration[5.2]
DOWNTIME = false
disable_ddl_transaction!
def up
BackgroundMigrationWorker.perform_async('AddGitlabInstanceAdministrationProject', [])
end
def down
ApplicationSetting.current_without_cache
&.instance_administration_project
&.owner
&.destroy!
end
end
......@@ -174,7 +174,7 @@ If we take a deeper look into the basic workflow, we can see
the features available in GitLab at each stage of the DevOps
lifecycle, as shown on the illustration below.
![Deeper look into the basic CI/CD workflow](img/gitlab_workflow_example_extended_11_11.png)
![Deeper look into the basic CI/CD workflow](img/gitlab_workflow_example_extended_v12_3.png)
If you look at the image from the left to the right,
you'll see some of the features available in GitLab
......
......@@ -3514,6 +3514,9 @@ msgstr ""
msgid "ClusterIntegration|Let's Encrypt"
msgstr ""
msgid "ClusterIntegration|Loading IAM Roles"
msgstr ""
msgid "ClusterIntegration|Machine type"
msgstr ""
......@@ -3523,6 +3526,9 @@ msgstr ""
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
msgid "ClusterIntegration|No IAM Roles found"
msgstr ""
msgid "ClusterIntegration|No machine types matched your search"
msgstr ""
......@@ -3589,9 +3595,15 @@ msgstr ""
msgid "ClusterIntegration|Request to begin uninstalling failed"
msgstr ""
msgid "ClusterIntegration|Role name"
msgstr ""
msgid "ClusterIntegration|Save changes"
msgstr ""
msgid "ClusterIntegration|Search IAM Roles"
msgstr ""
msgid "ClusterIntegration|Search machine types"
msgstr ""
......@@ -3616,6 +3628,9 @@ msgstr ""
msgid "ClusterIntegration|Select project to choose zone"
msgstr ""
msgid "ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}."
msgstr ""
msgid "ClusterIntegration|Select zone"
msgstr ""
......@@ -3739,6 +3754,9 @@ msgstr ""
msgid "ClusterIntegration|sign up"
msgstr ""
msgid "ClusterIntergation|Select role name"
msgstr ""
msgid "Code"
msgstr ""
......
......@@ -13,8 +13,8 @@ module QA
view 'app/views/projects/mirrors/_mirror_repos.html.haml' do
element :mirror_repository_url_input
element :mirror_repository_button
element :mirror_repository_url
element :mirror_last_update_at
element :mirror_repository_url_cell
element :mirror_last_update_at_cell
element :mirrored_repository_row
end
......@@ -64,21 +64,21 @@ module QA
wait(interval: 1) do
within_element_by_index(:mirrored_repository_row, row_index) do
last_update = find_element(:mirror_last_update_at, wait: 0)
last_update = find_element(:mirror_last_update_at_cell, wait: 0)
last_update.has_text?('just now') || last_update.has_text?('seconds')
end
end
# Fail early if the page still shows that there has been no update
within_element_by_index(:mirrored_repository_row, row_index) do
find_element(:mirror_last_update_at, wait: 0).assert_no_text('Never')
find_element(:mirror_last_update_at_cell, wait: 0).assert_no_text('Never')
end
end
private
def find_repository_row_index(target_url)
all_elements(:mirror_repository_url).index do |url|
all_elements(:mirror_repository_url_cell).index do |url|
# The url might be a sanitized url but the target_url won't be so
# we compare just the paths instead of the full url
URI.parse(url.text).path == target_url.path
......
......@@ -4,11 +4,12 @@ module QA
module Resource
module Repository
class ProjectPush < Repository::Push
attr_accessor :project_name
attr_writer :wait_for_push
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-with-code'
resource.name = project_name
resource.description = 'Project with repository'
end
end
......@@ -19,6 +20,7 @@ module QA
@commit_message = "This is a test commit"
@branch_name = 'master'
@new_branch = true
@project_name = 'project-with-code'
@wait_for_push = true
end
......
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
describe('ClusterFormDropdown', () => {
let vm;
beforeEach(() => {
vm = shallowMount(ClusterFormDropdown);
});
afterEach(() => vm.destroy());
describe('when no item is selected', () => {
it('displays placeholder text', () => {
const placeholder = 'placeholder';
vm.setProps({ placeholder });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(placeholder);
});
});
describe('when an item is selected', () => {
const selectedItem = { name: 'Name', value: 'value' };
beforeEach(() => {
vm.setData({ selectedItem });
});
it('displays selected item label', () => {
expect(vm.find(DropdownButton).props('toggleText')).toEqual(selectedItem.name);
});
it('sets selected value to dropdown hidden input', () => {
expect(vm.find(DropdownHiddenInput).props('value')).toEqual(selectedItem.value);
});
});
describe('when an item is selected and has a custom label property', () => {
it('displays selected item custom label', () => {
const labelProperty = 'customLabel';
const selectedItem = { [labelProperty]: 'Name' };
vm.setProps({ labelProperty });
vm.setData({ selectedItem });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(selectedItem[labelProperty]);
});
});
describe('when loading', () => {
it('dropdown button isLoading', () => {
vm.setProps({ loading: true });
expect(vm.find(DropdownButton).props('isLoading')).toBe(true);
});
});
describe('when loading and loadingText is provided', () => {
it('uses loading text as toggle button text', () => {
const loadingText = 'loading text';
vm.setProps({ loading: true, loadingText });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(loadingText);
});
});
describe('when disabled', () => {
it('dropdown button isDisabled', () => {
vm.setProps({ disabled: true });
expect(vm.find(DropdownButton).props('isDisabled')).toBe(true);
});
});
describe('when disabled and disabledText is provided', () => {
it('uses disabled text as toggle button text', () => {
const disabledText = 'disabled text';
vm.setProps({ disabled: true, disabledText });
expect(vm.find(DropdownButton).props('toggleText')).toBe(disabledText);
});
});
describe('when has errors', () => {
it('sets border-danger class selector to dropdown toggle', () => {
vm.setProps({ hasErrors: true });
expect(vm.find(DropdownButton).classes('border-danger')).toBe(true);
});
});
describe('when has errors and an error message', () => {
it('displays error message', () => {
const errorMessage = 'error message';
vm.setProps({ hasErrors: true, errorMessage });
expect(vm.find('.js-eks-dropdown-error-message').text()).toEqual(errorMessage);
});
});
describe('when no results are available', () => {
it('displays empty text', () => {
const emptyText = 'error message';
vm.setProps({ items: [], emptyText });
expect(vm.find('.js-empty-text').text()).toEqual(emptyText);
});
});
it('displays search field placeholder', () => {
const searchFieldPlaceholder = 'Placeholder';
vm.setProps({ searchFieldPlaceholder });
expect(vm.find(DropdownSearchInput).props('placeholderText')).toEqual(searchFieldPlaceholder);
});
it('it filters results by search query', () => {
const secondItem = { name: 'second item' };
const items = [{ name: 'first item' }, secondItem];
const searchQuery = 'second';
vm.setProps({ items });
vm.setData({ searchQuery });
expect(vm.findAll('.js-dropdown-item').length).toEqual(1);
expect(vm.find('.js-dropdown-item').text()).toEqual(secondItem.name);
});
});
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import RoleNameDropdown from '~/create_cluster/eks_cluster/components/role_name_dropdown.vue';
describe('RoleNameDropdown', () => {
let vm;
beforeEach(() => {
vm = shallowMount(RoleNameDropdown);
});
afterEach(() => vm.destroy());
it('renders a cluster-form-dropdown', () => {
expect(vm.find(ClusterFormDropdown).exists()).toBe(true);
});
it('sets roles to cluster-form-dropdown items property', () => {
const roles = [{ name: 'basic' }];
vm.setProps({ roles });
expect(vm.find(ClusterFormDropdown).props('items')).toEqual(roles);
});
it('sets a loading text', () => {
expect(vm.find(ClusterFormDropdown).props('loadingText')).toEqual('Loading IAM Roles');
});
it('sets a placeholder', () => {
expect(vm.find(ClusterFormDropdown).props('placeholder')).toEqual('Select role name');
});
it('sets an empty results text', () => {
expect(vm.find(ClusterFormDropdown).props('emptyText')).toEqual('No IAM Roles found');
});
it('sets a search field placeholder', () => {
expect(vm.find(ClusterFormDropdown).props('searchFieldPlaceholder')).toEqual(
'Search IAM Roles',
);
});
});
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190801072937_add_gitlab_instance_administration_project.rb')
describe AddGitlabInstanceAdministrationProject, :migration do
let(:application_settings) { table(:application_settings) }
let(:users) { table(:users) }
let(:prometheus_settings) do
{
enable: true,
listen_address: 'localhost:9090'
}
end
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
stub_config(prometheus: prometheus_settings)
end
describe 'down' do
let!(:application_setting) { application_settings.create! }
let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
it 'deletes group and project' do
migrate!
expect(Project.count).to eq(1)
expect(Group.count).to eq(1)
schema_migrate_down!
expect(Project.count).to eq(0)
expect(Group.count).to eq(0)
end
end
end
......@@ -4630,13 +4630,6 @@ eslint-plugin-filenames@^1.3.2:
lodash.snakecase "4.1.1"
lodash.upperfirst "4.3.1"
eslint-plugin-html@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-5.0.0.tgz#396e30a60dedee0122fe08f11d13c5ab22f20d32"
integrity sha512-f7p/7YQdgQUFVAX3nB4dnMQbrDeTalcA01PDhuvTLk0ZadCwM4Pb+639SRuqEf1zMkIxckLY+ckCr0hVP5zl6A==
dependencies:
htmlparser2 "^3.10.0"
eslint-plugin-import@^2.14.0, eslint-plugin-import@^2.16.0:
version "2.16.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f"
......
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