Commit c83e8c06 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 2a10454f 7e14fc25
...@@ -164,7 +164,7 @@ ...@@ -164,7 +164,7 @@
= dropdown_content = dropdown_content
= dropdown_loading = dropdown_loading
= dropdown_footer add_content_class: true do = dropdown_footer add_content_class: true do
%button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ disabled: true } %button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ type: 'button', disabled: true }
= _('Move') = _('Move')
= icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon') = icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
......
---
title: Remove unintended error message shown when moving issues
merge_request: 28317
author:
type: fixed
# Importing Issues from CSV # Importing issues from CSV
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23532) in GitLab 11.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23532) in GitLab 11.7.
Issues can be imported to a project by uploading a CSV file. Supported fields are Issues can be imported to a project by uploading a CSV file with the columns
`title` and `description`. `title` and `description`, in that order.
The user uploading the CSV file will be set as the author of the imported issues. The user uploading the CSV file will be set as the author of the imported issues.
> **Note:** A permission level of `Developer` or higher is required to import issues. > **Note:** A permission level of `Developer` or higher is required to import issues.
## Prepare for the import
- Consider importing a test file containing only a few issues. There is no way to undo a large import without using the GitLab API.
- Ensure your CSV file meets the [file format](#csv-file-format) requirements.
## Import the file
To import issues: To import issues:
1. Ensure your CSV file meets the [file format](#csv-file-format) requirements.
1. Navigate to a project's Issues list page. 1. Navigate to a project's Issues list page.
1. If existing issues are present, click the import icon at the top right, next to the **Edit issues** button. 1. If existing issues are present, click the import icon at the top right, next to the **Edit issues** button.
1. For a project without any issues, click the button labeled **Import CSV** in the middle of the page. 1. For a project without any issues, click the button labeled **Import CSV** in the middle of the page.
...@@ -20,11 +26,11 @@ To import issues: ...@@ -20,11 +26,11 @@ To import issues:
The file is processed in the background and a notification email is sent The file is processed in the background and a notification email is sent
to you once the import is completed. to you once the import is completed.
## CSV File Format ## CSV file format
### Header row ### Header row
CSV files must contain a header row beginning with at least two columns, `title` and `description`, in that order. CSV files must contain a header row where the first column header is `title` and the second is `description`.
If additional columns are present, they will be ignored. If additional columns are present, they will be ignored.
### Column separator ### Column separator
...@@ -53,7 +59,7 @@ The limit depends on the configuration value of Max Attachment Size for the GitL ...@@ -53,7 +59,7 @@ The limit depends on the configuration value of Max Attachment Size for the GitL
For GitLab.com, it is set to 10 MB. For GitLab.com, it is set to 10 MB.
## Sample Data ## Sample data
```csv ```csv
title,description title,description
......
import functionRowComponent from '~/serverless/components/function_row.vue'; import functionRowComponent from '~/serverless/components/function_row.vue';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockServerlessFunction } from '../mock_data'; import { mockServerlessFunction } from '../mock_data';
const createComponent = func =>
shallowMount(functionRowComponent, { propsData: { func }, sync: false }).vm;
describe('functionRowComponent', () => { describe('functionRowComponent', () => {
it('Parses the function details correctly', () => { let wrapper;
const vm = createComponent(mockServerlessFunction);
expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); const createComponent = func => {
expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); wrapper = shallowMount(functionRowComponent, { propsData: { func }, sync: false });
expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); };
vm.$destroy(); afterEach(() => {
wrapper.destroy();
});
it('Parses the function details correctly', () => {
createComponent(mockServerlessFunction);
expect(wrapper.find('b').text()).toBe(mockServerlessFunction.name);
expect(wrapper.find('span').text()).toBe(mockServerlessFunction.image);
expect(wrapper.find(Timeago).attributes('time')).not.toBe(null);
}); });
it('handles clicks correctly', () => { it('handles clicks correctly', () => {
const vm = createComponent(mockServerlessFunction); createComponent(mockServerlessFunction);
const { vm } = wrapper;
expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row
vm.$destroy();
}); });
}); });
import Vuex from 'vuex'; import Vuex from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import AxiosMockAdapter from 'axios-mock-adapter'; import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import functionsComponent from '~/serverless/components/functions.vue'; import functionsComponent from '~/serverless/components/functions.vue';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { createStore } from '~/serverless/store'; import { createStore } from '~/serverless/store';
import EmptyState from '~/serverless/components/empty_state.vue';
import EnvironmentRow from '~/serverless/components/environment_row.vue';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { mockServerlessFunctions } from '../mock_data'; import { mockServerlessFunctions } from '../mock_data';
...@@ -43,7 +46,7 @@ describe('functionsComponent', () => { ...@@ -43,7 +46,7 @@ describe('functionsComponent', () => {
sync: false, sync: false,
}); });
expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); expect(component.find(EmptyState).exists()).toBe(true);
}); });
it('should render a loading component', () => { it('should render a loading component', () => {
...@@ -60,7 +63,7 @@ describe('functionsComponent', () => { ...@@ -60,7 +63,7 @@ describe('functionsComponent', () => {
sync: false, sync: false,
}); });
expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); expect(component.find(GlLoadingIcon).exists()).toBe(true);
}); });
it('should render empty state when there is no function data', () => { it('should render empty state when there is no function data', () => {
...@@ -104,7 +107,7 @@ describe('functionsComponent', () => { ...@@ -104,7 +107,7 @@ describe('functionsComponent', () => {
component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions);
return component.vm.$nextTick().then(() => { return component.vm.$nextTick().then(() => {
expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); expect(component.find(EnvironmentRow).exists()).toBe(true);
}); });
}); });
}); });
import { GlButton } from '@gitlab/ui';
import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
...@@ -9,27 +10,29 @@ const createComponent = missingData => ...@@ -9,27 +10,29 @@ const createComponent = missingData =>
missingData, missingData,
}, },
sync: false, sync: false,
}).vm; });
describe('missingPrometheusComponent', () => { describe('missingPrometheusComponent', () => {
let vm; let wrapper;
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
it('should render missing prometheus message', () => { it('should render missing prometheus message', () => {
vm = createComponent(false); wrapper = createComponent(false);
const { vm } = wrapper;
expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
'Function invocation metrics require Prometheus to be installed first.', 'Function invocation metrics require Prometheus to be installed first.',
); );
expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); expect(wrapper.find(GlButton).attributes('variant')).toBe('success');
}); });
it('should render no prometheus data message', () => { it('should render no prometheus data message', () => {
vm = createComponent(true); wrapper = createComponent(true);
const { vm } = wrapper;
expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
'Invocation metrics loading or not available at this time.', 'Invocation metrics loading or not available at this time.',
......
import Vue from 'vue'; import Vue from 'vue';
import urlComponent from '~/serverless/components/url.vue'; import urlComponent from '~/serverless/components/url.vue';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
const createComponent = uri => const createComponent = uri =>
shallowMount(Vue.extend(urlComponent), { shallowMount(Vue.extend(urlComponent), {
...@@ -8,15 +9,16 @@ const createComponent = uri => ...@@ -8,15 +9,16 @@ const createComponent = uri =>
uri, uri,
}, },
sync: false, sync: false,
}).vm; });
describe('urlComponent', () => { describe('urlComponent', () => {
it('should render correctly', () => { it('should render correctly', () => {
const uri = 'http://testfunc.apps.example.com'; const uri = 'http://testfunc.apps.example.com';
const vm = createComponent(uri); const wrapper = createComponent(uri);
const { vm } = wrapper;
expect(vm.$el.classList.contains('clipboard-group')).toBe(true); expect(vm.$el.classList.contains('clipboard-group')).toBe(true);
expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); expect(wrapper.find(ClipboardButton).attributes('text')).toEqual(uri);
expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri);
......
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