summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Bug.md6
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js6
-rw-r--r--app/assets/javascripts/ide/components/ide_context_bar.vue4
-rw-r--r--app/assets/javascripts/ide/components/ide_project_branches_tree.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_project_tree.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_repo_tree.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/new_branch_form.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue6
-rw-r--r--app/assets/javascripts/ide/components/repo_edit_button.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_loading_file.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_preview.vue4
-rw-r--r--app/assets/javascripts/ide/stores/actions.js4
-rw-r--r--app/assets/javascripts/labels_select.js40
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js6
-rw-r--r--app/assets/javascripts/pages/projects/cycle_analytics/show/index.js3
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees.js224
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees.vue232
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss7
-rw-r--r--app/controllers/projects/pages_domains_controller.rb29
-rw-r--r--app/models/ci/runner.rb4
-rw-r--r--app/services/projects/update_service.rb15
-rw-r--r--app/views/admin/runners/_runner.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml1
-rw-r--r--app/views/projects/pages_domains/_form.html.haml56
-rw-r--r--app/views/projects/pages_domains/edit.html.haml11
-rw-r--r--app/views/projects/pages_domains/new.html.haml6
-rw-r--r--app/views/projects/pages_domains/show.html.haml4
-rw-r--r--app/views/projects/runners/_form.html.haml5
-rw-r--r--app/views/projects/runners/show.html.haml3
-rw-r--r--changelogs/unreleased/43489-display-runner-ip.yml5
-rw-r--r--changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml5
-rw-r--r--changelogs/unreleased/43643-fix-mr-label-filtering.yml5
-rw-r--r--changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml5
-rw-r--r--changelogs/unreleased/assignees-vue-component-missing-data-container.yml5
-rw-r--r--changelogs/unreleased/feature-edit_pages_domain.yml5
-rw-r--r--changelogs/unreleased/refactor-move-assignees-vue-component.yml5
-rw-r--r--config/initializers/doorkeeper.rb2
-rw-r--r--config/initializers/lograge.rb7
-rw-r--r--config/routes/project.rb2
-rw-r--r--config/webpack.config.js29
-rw-r--r--db/migrate/20180222043024_add_ip_address_to_runner.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--doc/README.md54
-rw-r--r--doc/ci/variables/README.md3
-rw-r--r--doc/development/fe_guide/vue.md2
-rw-r--r--doc/integration/saml.md3
-rw-r--r--doc/user/markdown.md8
-rw-r--r--doc/user/project/merge_requests/index.md2
-rw-r--r--lib/api/helpers/runner.rb18
-rw-r--r--lib/api/runner.rb4
-rw-r--r--lib/gitlab/git/wiki.rb5
-rw-r--r--lib/gitlab/search_results.rb4
-rwxr-xr-xscripts/security-harness2
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb60
-rw-r--r--spec/features/projects/pages_spec.rb31
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb17
-rw-r--r--spec/javascripts/labels_select_spec.js43
-rw-r--r--spec/javascripts/sidebar/assignees_spec.js2
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb27
-rw-r--r--spec/requests/api/runner_spec.rb18
-rw-r--r--spec/services/projects/update_service_spec.rb34
68 files changed, 774 insertions, 367 deletions
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index aec734870d6..3e58d2a867e 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -1,3 +1,4 @@
+<!---
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
@@ -14,10 +15,7 @@ For the Enterprise Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=bug
and verify the issue you're about to submit isn't a duplicate.
-
-Please remove this notice if you're confident your issue isn't a duplicate.
-
-------
+--->
### Summary
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index add24303e7b..9501e35b178 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -6,7 +6,7 @@ import { __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
-import assignees from '../../sidebar/components/assignees/assignees';
+import assignees from '../../sidebar/components/assignees/assignees.vue';
import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue';
import IssuableContext from '../../issuable_context';
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 034f2923b3b..46d89c825f9 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -14,10 +14,10 @@ import CycleAnalyticsStore from './cycle_analytics_store';
Vue.use(Translate);
-$(() => {
+export default () => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
- gl.cycleAnalyticsApp = new Vue({
+ new Vue({ // eslint-disable-line no-new
el: '#cycle-analytics',
name: 'CycleAnalytics',
components: {
@@ -132,4 +132,4 @@ $(() => {
},
},
});
-});
+};
diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue
index dd947f66969..9d933b8891d 100644
--- a/app/assets/javascripts/ide/components/ide_context_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_context_bar.vue
@@ -1,8 +1,8 @@
<script>
import { mapGetters, mapState, mapActions } from 'vuex';
+ import icon from '~/vue_shared/components/icon.vue';
+ import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import repoCommitSection from './repo_commit_section.vue';
- import icon from '../../vue_shared/components/icon.vue';
- import panelResizer from '../../vue_shared/components/panel_resizer.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
index af2f7341a91..2fbff2bd789 100644
--- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue
@@ -1,6 +1,6 @@
<script>
+import icon from '~/vue_shared/components/icon.vue';
import repoTree from './ide_repo_tree.vue';
-import icon from '../../vue_shared/components/icon.vue';
import newDropdown from './new_dropdown/index.vue';
export default {
diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue
index ed49a0e72a2..32bf7175c88 100644
--- a/app/assets/javascripts/ide/components/ide_project_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_project_tree.vue
@@ -1,6 +1,6 @@
<script>
+import projectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
import branchesTree from './ide_project_branches_tree.vue';
-import projectAvatarImage from '../../vue_shared/components/project_avatar/image.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue
index 4651e345d75..4a324264992 100644
--- a/app/assets/javascripts/ide/components/ide_repo_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_repo_tree.vue
@@ -1,8 +1,8 @@
<script>
import { mapState } from 'vuex';
+import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import repoPreviousDirectory from './repo_prev_directory.vue';
import repoFile from './repo_file.vue';
-import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
import { treeList } from '../stores/utils';
export default {
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index a68f8ce0169..18b5059a17f 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -1,9 +1,9 @@
<script>
import { mapState, mapActions } from 'vuex';
+ import icon from '~/vue_shared/components/icon.vue';
+ import panelResizer from '~/vue_shared/components/panel_resizer.vue';
+ import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import projectTree from './ide_project_tree.vue';
- import icon from '../../vue_shared/components/icon.vue';
- import panelResizer from '../../vue_shared/components/panel_resizer.vue';
- import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index e48c446c4a4..97ae64b206d 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -1,8 +1,8 @@
<script>
import { mapState } from 'vuex';
- import icon from '../../vue_shared/components/icon.vue';
- import tooltip from '../../vue_shared/directives/tooltip';
- import timeAgoMixin from '../../vue_shared/mixins/timeago';
+ import icon from '~/vue_shared/components/icon.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
+ import timeAgoMixin from '~/vue_shared/mixins/timeago';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue
index 56e31256132..1e8d5bb6453 100644
--- a/app/assets/javascripts/ide/components/new_branch_form.vue
+++ b/app/assets/javascripts/ide/components/new_branch_form.vue
@@ -1,7 +1,7 @@
<script>
import { mapState, mapActions } from 'vuex';
- import flash, { hideFlash } from '../../flash';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import flash, { hideFlash } from '~/flash';
+ import loadingIcon from '~/vue_shared/components/loading_icon.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 96b1bb78c1d..37f2cf30a29 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -1,8 +1,8 @@
<script>
import { mapGetters, mapState, mapActions } from 'vuex';
-import tooltip from '../../vue_shared/directives/tooltip';
-import icon from '../../vue_shared/components/icon.vue';
-import modal from '../../vue_shared/components/modal.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import icon from '~/vue_shared/components/icon.vue';
+import modal from '~/vue_shared/components/modal.vue';
import commitFilesList from './commit_sidebar/list.vue';
export default {
diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue
index c43e9163340..fe4320731d9 100644
--- a/app/assets/javascripts/ide/components/repo_edit_button.vue
+++ b/app/assets/javascripts/ide/components/repo_edit_button.vue
@@ -1,6 +1,6 @@
<script>
import { mapGetters, mapActions, mapState } from 'vuex';
-import modal from '../../vue_shared/components/modal.vue';
+import modal from '~/vue_shared/components/modal.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f99228012f4..f31cc12339b 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -1,7 +1,7 @@
<script>
/* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex';
-import flash from '../../flash';
+import flash from '~/flash';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
diff --git a/app/assets/javascripts/ide/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue
index 3aeb6f0b28f..79af8c0b0c7 100644
--- a/app/assets/javascripts/ide/components/repo_loading_file.vue
+++ b/app/assets/javascripts/ide/components/repo_loading_file.vue
@@ -1,6 +1,6 @@
<script>
import { mapState } from 'vuex';
- import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
+ import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
export default {
components: {
diff --git a/app/assets/javascripts/ide/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue
index e47270a9855..a216269e292 100644
--- a/app/assets/javascripts/ide/components/repo_preview.vue
+++ b/app/assets/javascripts/ide/components/repo_preview.vue
@@ -1,7 +1,7 @@
<script>
import { mapGetters } from 'vuex';
- import LineHighlighter from '../../line_highlighter';
- import syntaxHighlight from '../../syntax_highlight';
+ import LineHighlighter from '~/line_highlighter';
+ import syntaxHighlight from '~/syntax_highlight';
export default {
computed: {
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index d007d0ae78f..2c690b1f635 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { visitUrl } from '../../lib/utils/url_utility';
-import flash from '../../flash';
+import { visitUrl } from '~/lib/utils/url_utility';
+import flash from '~/flash';
import service from '../services';
import * as types from './mutation_types';
import { stripHtml } from '../../lib/utils/text_utility';
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 89a246f56cf..9b46bbf83da 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -21,7 +21,7 @@ export default class LabelsSelect {
}
$els.each(function(i, dropdown) {
- var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer;
+ var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer;
$dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter');
$toggleText = $dropdown.find('.dropdown-toggle-text');
@@ -53,13 +53,6 @@ export default class LabelsSelect {
.map(function () {
return this.value;
}).get();
- if (issueUpdateURL != null) {
- issueURLSplit = issueUpdateURL.split('/');
- }
- if (issueUpdateURL) {
- labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
- labelNoneHTMLTemplate = '<span class="no-value">None</span>';
- }
const handleClick = options.handleClick;
$sidebarLabelTooltip.tooltip();
@@ -91,14 +84,17 @@ export default class LabelsSelect {
$loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide();
- data.issueURLSplit = issueURLSplit;
+ data.issueUpdateURL = issueUpdateURL;
labelCount = 0;
- if (data.labels.length) {
- template = labelHTMLTemplate(data);
+ if (data.labels.length && issueUpdateURL) {
+ template = LabelsSelect.getLabelTemplate({
+ labels: data.labels,
+ issueUpdateURL,
+ });
labelCount = data.labels.length;
}
else {
- template = labelNoneHTMLTemplate;
+ template = '<span class="no-value">None</span>';
}
$value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount);
@@ -418,6 +414,26 @@ export default class LabelsSelect {
this.bindEvents();
}
+ static getLabelTemplate(tplData) {
+ // We could use ES6 template string here
+ // and properly indent markup for readability
+ // but that also introduces unintended white-space
+ // so best approach is to use traditional way of
+ // concatenation
+ // see: http://2ality.com/2016/05/template-literal-whitespace.html#joining-arrays
+ const tpl = _.template([
+ '<% _.each(labels, function(label){ %>',
+ '<a href="<%- issueUpdateURL.slice(0, issueUpdateURL.lastIndexOf("/")) %>?label_name[]=<%- encodeURIComponent(label.title) %>">',
+ '<span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">',
+ '<%- label.title %>',
+ '</span>',
+ '</a>',
+ '<% }); %>',
+ ].join(''));
+
+ return tpl(tplData);
+ }
+
bindEvents() {
return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue);
}
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index b3f6a72fdcb..42f7460ad55 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -2,9 +2,9 @@
import { visitUrl } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import { isMetaClick } from '~/lib/utils/common_utils';
-import { __ } from '../../../../locale';
-import flash from '../../../../flash';
-import axios from '../../../../lib/utils/axios_utils';
+import { __ } from '~/locale';
+import flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
export default class Todos {
constructor() {
diff --git a/app/assets/javascripts/pages/projects/cycle_analytics/show/index.js b/app/assets/javascripts/pages/projects/cycle_analytics/show/index.js
new file mode 100644
index 00000000000..df58e9dd072
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/cycle_analytics/show/index.js
@@ -0,0 +1,3 @@
+import initCycleAnalytics from '~/cycle_analytics/cycle_analytics_bundle';
+
+document.addEventListener('DOMContentLoaded', initCycleAnalytics);
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.js b/app/assets/javascripts/sidebar/components/assignees/assignees.js
deleted file mode 100644
index 643877b9d47..00000000000
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.js
+++ /dev/null
@@ -1,224 +0,0 @@
-export default {
- name: 'Assignees',
- data() {
- return {
- defaultRenderCount: 5,
- defaultMaxCounter: 99,
- showLess: true,
- };
- },
- props: {
- rootPath: {
- type: String,
- required: true,
- },
- users: {
- type: Array,
- required: true,
- },
- editable: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- firstUser() {
- return this.users[0];
- },
- hasMoreThanTwoAssignees() {
- return this.users.length > 2;
- },
- hasMoreThanOneAssignee() {
- return this.users.length > 1;
- },
- hasAssignees() {
- return this.users.length > 0;
- },
- hasNoUsers() {
- return !this.users.length;
- },
- hasOneUser() {
- return this.users.length === 1;
- },
- renderShowMoreSection() {
- return this.users.length > this.defaultRenderCount;
- },
- numberOfHiddenAssignees() {
- return this.users.length - this.defaultRenderCount;
- },
- isHiddenAssignees() {
- return this.numberOfHiddenAssignees > 0;
- },
- hiddenAssigneesLabel() {
- return `+ ${this.numberOfHiddenAssignees} more`;
- },
- collapsedTooltipTitle() {
- const maxRender = Math.min(this.defaultRenderCount, this.users.length);
- const renderUsers = this.users.slice(0, maxRender);
- const names = renderUsers.map(u => u.name);
-
- if (this.users.length > maxRender) {
- names.push(`+ ${this.users.length - maxRender} more`);
- }
-
- return names.join(', ');
- },
- sidebarAvatarCounter() {
- let counter = `+${this.users.length - 1}`;
-
- if (this.users.length > this.defaultMaxCounter) {
- counter = `${this.defaultMaxCounter}+`;
- }
-
- return counter;
- },
- },
- methods: {
- assignSelf() {
- this.$emit('assign-self');
- },
- toggleShowLess() {
- this.showLess = !this.showLess;
- },
- renderAssignee(index) {
- return !this.showLess || (index < this.defaultRenderCount && this.showLess);
- },
- avatarUrl(user) {
- return user.avatar || user.avatar_url || gon.default_avatar_url;
- },
- assigneeUrl(user) {
- return `${this.rootPath}${user.username}`;
- },
- assigneeAlt(user) {
- return `${user.name}'s avatar`;
- },
- assigneeUsername(user) {
- return `@${user.username}`;
- },
- shouldRenderCollapsedAssignee(index) {
- const firstTwo = this.users.length <= 2 && index <= 2;
-
- return index === 0 || firstTwo;
- },
- },
- template: `
- <div>
- <div
- class="sidebar-collapsed-icon sidebar-collapsed-user"
- :class="{ 'multiple-users': hasMoreThanOneAssignee, 'has-tooltip': hasAssignees }"
- data-container="body"
- data-placement="left"
- :title="collapsedTooltipTitle"
- >
- <i
- v-if="hasNoUsers"
- aria-label="No Assignee"
- class="fa fa-user"
- />
- <button
- type="button"
- class="btn-link"
- v-for="(user, index) in users"
- v-if="shouldRenderCollapsedAssignee(index)"
- >
- <img
- width="24"
- class="avatar avatar-inline s24"
- :alt="assigneeAlt(user)"
- :src="avatarUrl(user)"
- />
- <span class="author">
- {{ user.name }}
- </span>
- </button>
- <button
- v-if="hasMoreThanTwoAssignees"
- class="btn-link"
- type="button"
- >
- <span
- class="avatar-counter sidebar-avatar-counter"
- >
- {{ sidebarAvatarCounter }}
- </span>
- </button>
- </div>
- <div class="value hide-collapsed">
- <template v-if="hasNoUsers">
- <span class="assign-yourself no-value">
- No assignee
- <template v-if="editable">
- -
- <button
- type="button"
- class="btn-link"
- @click="assignSelf"
- >
- assign yourself
- </button>
- </template>
- </span>
- </template>
- <template v-else-if="hasOneUser">
- <a
- class="author_link bold"
- :href="assigneeUrl(firstUser)"
- >
- <img
- width="32"
- class="avatar avatar-inline s32"
- :alt="assigneeAlt(firstUser)"
- :src="avatarUrl(firstUser)"
- />
- <span class="author">
- {{ firstUser.name }}
- </span>
- <span class="username">
- {{ assigneeUsername(firstUser) }}
- </span>
- </a>
- </template>
- <template v-else>
- <div class="user-list">
- <div
- class="user-item"
- v-for="(user, index) in users"
- v-if="renderAssignee(index)"
- >
- <a
- class="user-link has-tooltip"
- data-placement="bottom"
- :href="assigneeUrl(user)"
- :data-title="user.name"
- >
- <img
- width="32"
- class="avatar avatar-inline s32"
- :alt="assigneeAlt(user)"
- :src="avatarUrl(user)"
- />
- </a>
- </div>
- </div>
- <div
- v-if="renderShowMoreSection"
- class="user-list-more"
- >
- <button
- type="button"
- class="btn-link"
- @click="toggleShowLess"
- >
- <template v-if="showLess">
- {{ hiddenAssigneesLabel }}
- </template>
- <template v-else>
- - show less
- </template>
- </button>
- </div>
- </template>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
new file mode 100644
index 00000000000..1e7f46454bf
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -0,0 +1,232 @@
+<script>
+export default {
+ name: 'Assignees',
+ props: {
+ rootPath: {
+ type: String,
+ required: true,
+ },
+ users: {
+ type: Array,
+ required: true,
+ },
+ editable: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ defaultRenderCount: 5,
+ defaultMaxCounter: 99,
+ showLess: true,
+ };
+ },
+ computed: {
+ firstUser() {
+ return this.users[0];
+ },
+ hasMoreThanTwoAssignees() {
+ return this.users.length > 2;
+ },
+ hasMoreThanOneAssignee() {
+ return this.users.length > 1;
+ },
+ hasAssignees() {
+ return this.users.length > 0;
+ },
+ hasNoUsers() {
+ return !this.users.length;
+ },
+ hasOneUser() {
+ return this.users.length === 1;
+ },
+ renderShowMoreSection() {
+ return this.users.length > this.defaultRenderCount;
+ },
+ numberOfHiddenAssignees() {
+ return this.users.length - this.defaultRenderCount;
+ },
+ isHiddenAssignees() {
+ return this.numberOfHiddenAssignees > 0;
+ },
+ hiddenAssigneesLabel() {
+ return `+ ${this.numberOfHiddenAssignees} more`;
+ },
+ collapsedTooltipTitle() {
+ const maxRender = Math.min(this.defaultRenderCount, this.users.length);
+ const renderUsers = this.users.slice(0, maxRender);
+ const names = renderUsers.map(u => u.name);
+
+ if (this.users.length > maxRender) {
+ names.push(`+ ${this.users.length - maxRender} more`);
+ }
+
+ return names.join(', ');
+ },
+ sidebarAvatarCounter() {
+ let counter = `+${this.users.length - 1}`;
+
+ if (this.users.length > this.defaultMaxCounter) {
+ counter = `${this.defaultMaxCounter}+`;
+ }
+
+ return counter;
+ },
+ },
+ methods: {
+ assignSelf() {
+ this.$emit('assign-self');
+ },
+ toggleShowLess() {
+ this.showLess = !this.showLess;
+ },
+ renderAssignee(index) {
+ return !this.showLess || (index < this.defaultRenderCount && this.showLess);
+ },
+ avatarUrl(user) {
+ return user.avatar || user.avatar_url || gon.default_avatar_url;
+ },
+ assigneeUrl(user) {
+ return `${this.rootPath}${user.username}`;
+ },
+ assigneeAlt(user) {
+ return `${user.name}'s avatar`;
+ },
+ assigneeUsername(user) {
+ return `@${user.username}`;
+ },
+ shouldRenderCollapsedAssignee(index) {
+ const firstTwo = this.users.length <= 2 && index <= 2;
+
+ return index === 0 || firstTwo;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div
+ class="sidebar-collapsed-icon sidebar-collapsed-user"
+ :class="{ 'multiple-users': hasMoreThanOneAssignee, 'has-tooltip': hasAssignees }"
+ data-container="body"
+ data-placement="left"
+ :title="collapsedTooltipTitle"
+ >
+ <i
+ v-if="hasNoUsers"
+ aria-label="No Assignee"
+ class="fa fa-user"
+ >
+ </i>
+ <button
+ type="button"
+ class="btn-link"
+ v-for="(user, index) in users"
+ v-if="shouldRenderCollapsedAssignee(index)"
+ :key="user.id"
+ >
+ <img
+ width="24"
+ class="avatar avatar-inline s24"
+ :alt="assigneeAlt(user)"
+ :src="avatarUrl(user)"
+ />
+ <span class="author">
+ {{ user.name }}
+ </span>
+ </button>
+ <button
+ v-if="hasMoreThanTwoAssignees"
+ class="btn-link"
+ type="button"
+ >
+ <span
+ class="avatar-counter sidebar-avatar-counter"
+ >
+ {{ sidebarAvatarCounter }}
+ </span>
+ </button>
+ </div>
+ <div class="value hide-collapsed">
+ <template v-if="hasNoUsers">
+ <span class="assign-yourself no-value">
+ No assignee
+ <template v-if="editable">
+ -
+ <button
+ type="button"
+ class="btn-link"
+ @click="assignSelf"
+ >
+ assign yourself
+ </button>
+ </template>
+ </span>
+ </template>
+ <template v-else-if="hasOneUser">
+ <a
+ class="author_link bold"
+ :href="assigneeUrl(firstUser)"
+ >
+ <img
+ width="32"
+ class="avatar avatar-inline s32"
+ :alt="assigneeAlt(firstUser)"
+ :src="avatarUrl(firstUser)"
+ />
+ <span class="author">
+ {{ firstUser.name }}
+ </span>
+ <span class="username">
+ {{ assigneeUsername(firstUser) }}
+ </span>
+ </a>
+ </template>
+ <template v-else>
+ <div class="user-list">
+ <div
+ class="user-item"
+ v-for="(user, index) in users"
+ v-if="renderAssignee(index)"
+ :key="user.id"
+ >
+ <a
+ class="user-link has-tooltip"
+ data-container="body"
+ data-placement="bottom"
+ :href="assigneeUrl(user)"
+ :data-title="user.name"
+ >
+ <img
+ width="32"
+ class="avatar avatar-inline s32"
+ :alt="assigneeAlt(user)"
+ :src="avatarUrl(user)"
+ />
+ </a>
+ </div>
+ </div>
+ <div
+ v-if="renderShowMoreSection"
+ class="user-list-more"
+ >
+ <button
+ type="button"
+ class="btn-link"
+ @click="toggleShowLess"
+ >
+ <template v-if="showLess">
+ {{ hiddenAssigneesLabel }}
+ </template>
+ <template v-else>
+ - show less
+ </template>
+ </button>
+ </div>
+ </template>
+ </div>
+ </div>
+</template>
+
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
index 9e47039d920..8269fe1281d 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
@@ -1,6 +1,6 @@
import Flash from '../../../flash';
import AssigneeTitle from './assignee_title';
-import Assignees from './assignees';
+import Assignees from './assignees.vue';
import Store from '../../stores/sidebar_store';
import eventHub from '../../event_hub';
@@ -28,8 +28,8 @@ export default {
},
},
components: {
- 'assignee-title': AssigneeTitle,
- assignees: Assignees,
+ AssigneeTitle,
+ Assignees,
},
methods: {
assignSelf() {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 0cf67734237..4c9732c26d9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -103,6 +103,7 @@
.issuable-show-labels {
a {
margin-bottom: 5px;
+ margin-right: 5px;
display: inline-block;
.color-label {
@@ -116,6 +117,12 @@
}
&.has-labels {
+ // this font size is a fix to
+ // prevent unintended spacing between labels
+ // which shows up when rendering markup has white-space
+ // characters present.
+ // see: https://css-tricks.com/fighting-the-space-between-inline-block-elements/#article-header-id-3
+ font-size: 0;
margin-bottom: -5px;
}
}
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index b71f1e5fef4..4856be61e88 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -3,7 +3,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
before_action :require_pages_enabled!
before_action :authorize_update_pages!, except: [:show]
- before_action :domain, only: [:show, :destroy, :verify]
+ before_action :domain, except: [:new, :create]
def show
end
@@ -24,8 +24,11 @@ class Projects::PagesDomainsController < Projects::ApplicationController
redirect_to project_pages_domain_path(@project, @domain)
end
+ def edit
+ end
+
def create
- @domain = @project.pages_domains.create(pages_domain_params)
+ @domain = @project.pages_domains.create(create_params)
if @domain.valid?
redirect_to project_pages_domain_path(@project, @domain)
@@ -34,6 +37,16 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end
end
+ def update
+ if @domain.update(update_params)
+ redirect_to project_pages_path(@project),
+ status: 302,
+ notice: 'Domain was updated'
+ else
+ render 'edit'
+ end
+ end
+
def destroy
@domain.destroy
@@ -49,12 +62,12 @@ class Projects::PagesDomainsController < Projects::ApplicationController
private
- def pages_domain_params
- params.require(:pages_domain).permit(
- :certificate,
- :key,
- :domain
- )
+ def create_params
+ params.require(:pages_domain).permit(:key, :certificate, :domain)
+ end
+
+ def update_params
+ params.require(:pages_domain).permit(:key, :certificate)
end
def domain
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 13c784bea0d..609620a62bb 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -49,7 +49,7 @@ module Ci
ref_protected: 1
}
- cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at
+ cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address
# Searches for runners matching the given query.
#
@@ -157,7 +157,7 @@ module Ci
end
def update_cached_info(values)
- values = values&.slice(:version, :revision, :platform, :architecture) || {}
+ values = values&.slice(:version, :revision, :platform, :architecture, :ip_address) || {}
values[:contacted_at] = Time.now
cache_attributes(values)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 0e235a6d2a0..379a8068023 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -15,6 +15,8 @@ module Projects
return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end
+ ensure_wiki_exists if enabling_wiki?
+
if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path')
project.rename_repo
@@ -52,5 +54,18 @@ module Projects
project.repository.exists? &&
new_branch && new_branch != project.default_branch
end
+
+ def enabling_wiki?
+ return false if @project.wiki_enabled?
+
+ params[:project_feature_attributes][:wiki_access_level].to_i > ProjectFeature::DISABLED
+ end
+
+ def ensure_wiki_exists
+ ProjectWiki.new(project, project.owner).wiki
+ rescue ProjectWiki::CouldNotCreateWikiError
+ log_error("Could not create wiki for #{project.full_name}")
+ Gitlab::Metrics.counter(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki')
+ end
end
end
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 140688b52d3..e1cee584929 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -17,6 +17,8 @@
%td
= runner.version
%td
+ = runner.ip_address
+ %td
- if runner.shared?
n/a
- else
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index abec3607cab..9f13dbbbd82 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -60,6 +60,7 @@
%th Runner token
%th Description
%th Version
+ %th IP Address
%th Projects
%th Jobs
%th Tags
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index d98e0564da4..02395b6eb9b 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -2,7 +2,6 @@
- page_title "Cycle Analytics"
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
- = webpack_bundle_tag('cycle_analytics')
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index ca1b41b140a..d81b07832bb 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -1,34 +1,30 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f|
- - if @domain.errors.any?
- #error_explanation
- .alert.alert-danger
- - @domain.errors.full_messages.each do |msg|
- %p= msg
+- if @domain.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ - @domain.errors.full_messages.each do |msg|
+ %p= msg
+.form-group
+ = f.label :domain, class: 'control-label' do
+ Domain
+ .col-sm-10
+ = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control', disabled: @domain.persisted?
+
+- if Gitlab.config.pages.external_https
.form-group
- = f.label :domain, class: 'control-label' do
- Domain
+ = f.label :certificate, class: 'control-label' do
+ Certificate (PEM)
.col-sm-10
- = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control'
-
- - if Gitlab.config.pages.external_https
- .form-group
- = f.label :certificate, class: 'control-label' do
- Certificate (PEM)
- .col-sm-10
- = f.text_area :certificate, rows: 5, class: 'form-control'
- %span.help-inline Upload a certificate for your domain with all intermediates
-
- .form-group
- = f.label :key, class: 'control-label' do
- Key (PEM)
- .col-sm-10
- = f.text_area :key, rows: 5, class: 'form-control'
- %span.help-inline Upload a private key for your certificate
- - else
- .nothing-here-block
- Support for custom certificates is disabled.
- Ask your system's administrator to enable it.
+ = f.text_area :certificate, rows: 5, class: 'form-control'
+ %span.help-inline Upload a certificate for your domain with all intermediates
- .form-actions
- = f.submit 'Create New Domain', class: "btn btn-save"
+ .form-group
+ = f.label :key, class: 'control-label' do
+ Key (PEM)
+ .col-sm-10
+ = f.text_area :key, rows: 5, class: 'form-control'
+ %span.help-inline Upload a private key for your certificate
+- else
+ .nothing-here-block
+ Support for custom certificates is disabled.
+ Ask your system's administrator to enable it.
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
new file mode 100644
index 00000000000..5645a4604bf
--- /dev/null
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -0,0 +1,11 @@
+- add_to_breadcrumbs "Pages", project_pages_path(@project)
+- breadcrumb_title @domain.domain
+- page_title @domain.domain
+%h3.page_title
+ = @domain.domain
+%hr.clearfix
+%div
+ = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f|
+ = render 'form', { f: f }
+ .form-actions
+ = f.submit 'Save Changes', class: "btn btn-save"
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index e1477c71d06..5a397c9d3c7 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -1,6 +1,10 @@
+- add_to_breadcrumbs "Pages", project_pages_path(@project)
- page_title 'New Pages Domain'
%h3.page_title
New Pages Domain
%hr.clearfix
%div
- = render 'form'
+ = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f|
+ = render 'form', { f: f }
+ .form-actions
+ = f.submit 'Create New Domain', class: "btn btn-save"
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index 72e9203bdb0..ba0713daee9 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -1,4 +1,7 @@
+- add_to_breadcrumbs "Pages", project_pages_path(@project)
+- breadcrumb_title @domain.domain
- page_title "#{@domain.domain}", 'Pages Domains'
+
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
- if verification_enabled && @domain.unverified?
%p.alert.alert-warning
@@ -8,6 +11,7 @@
%h3.page-title
Pages Domain
+ = link_to 'Edit', edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success pull-right'
.table-holder
%table.table
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index e660fce652f..49c90869146 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -30,6 +30,11 @@
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group
+ = label_tag :ip_address, class: 'control-label' do
+ IP Address
+ .col-sm-10
+ = f.text_field :ip_address, class: 'form-control', readonly: true
+ .form-group
= label_tag :description, class: 'control-label' do
Description
.col-sm-10
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index dfab04aa1fb..4e57f5f844d 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -41,6 +41,9 @@
%td Version
%td= @runner.version
%tr
+ %td IP Address
+ %td= @runner.ip_address
+ %tr
%td Revision
%td= @runner.revision
%tr
diff --git a/changelogs/unreleased/43489-display-runner-ip.yml b/changelogs/unreleased/43489-display-runner-ip.yml
new file mode 100644
index 00000000000..621c2ec709a
--- /dev/null
+++ b/changelogs/unreleased/43489-display-runner-ip.yml
@@ -0,0 +1,5 @@
+---
+title: Display Runner IP Address
+merge_request: 17286
+author:
+type: added
diff --git a/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml b/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml
new file mode 100644
index 00000000000..173710412a5
--- /dev/null
+++ b/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml
@@ -0,0 +1,5 @@
+---
+title: Fix code and wiki search results pages when non-ASCII text is displayed
+merge_request: 17413
+author:
+type: fixed
diff --git a/changelogs/unreleased/43643-fix-mr-label-filtering.yml b/changelogs/unreleased/43643-fix-mr-label-filtering.yml
new file mode 100644
index 00000000000..32a44aef243
--- /dev/null
+++ b/changelogs/unreleased/43643-fix-mr-label-filtering.yml
@@ -0,0 +1,5 @@
+---
+title: Enable filtering MR list based on clicked label in MR sidebar
+merge_request: 17390
+author:
+type: fixed
diff --git a/changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml b/changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml
new file mode 100644
index 00000000000..c0fa8e2e377
--- /dev/null
+++ b/changelogs/unreleased/4826-create-empty-wiki-when-it-s-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Make sure wiki exists when it's enabled
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/assignees-vue-component-missing-data-container.yml b/changelogs/unreleased/assignees-vue-component-missing-data-container.yml
new file mode 100644
index 00000000000..233d983b415
--- /dev/null
+++ b/changelogs/unreleased/assignees-vue-component-missing-data-container.yml
@@ -0,0 +1,5 @@
+---
+title: Add Assignees vue component missing data container
+merge_request: 17426
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/feature-edit_pages_domain.yml b/changelogs/unreleased/feature-edit_pages_domain.yml
new file mode 100644
index 00000000000..bd0af53296c
--- /dev/null
+++ b/changelogs/unreleased/feature-edit_pages_domain.yml
@@ -0,0 +1,5 @@
+---
+title: 'Pages custom domain: allow update of key/certificate'
+merge_request: 17376
+author: rfwatson
+type: changed
diff --git a/changelogs/unreleased/refactor-move-assignees-vue-component.yml b/changelogs/unreleased/refactor-move-assignees-vue-component.yml
new file mode 100644
index 00000000000..98cfa6b4c81
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-assignees-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move Assignees vue component
+merge_request: 16952
+author: George Tsiolis
+type: performance
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index b89f0419b91..2079d3acb72 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -103,4 +103,6 @@ Doorkeeper.configure do
# Some applications require dynamic query parameters on their request_uri
# set to true if you want this to be allowed
# wildcard_redirect_uri false
+
+ base_controller 'ApplicationController'
end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 8560d24526f..114c1cb512f 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -12,9 +12,14 @@ unless Sidekiq.server?
config.lograge.logger = ActiveSupport::Logger.new(filename)
# Add request parameters to log output
config.lograge.custom_options = lambda do |event|
+ params = event.payload[:params]
+ .except(*%w(controller action format))
+ .each_pair
+ .map { |k, v| { key: k, value: v } }
+
payload = {
time: event.time.utc.iso8601(3),
- params: event.payload[:params].except(*%w(controller action format)),
+ params: params,
remote_ip: event.payload[:remote_ip],
user_id: event.payload[:user_id],
username: event.payload[:username]
diff --git a/config/routes/project.rb b/config/routes/project.rb
index dd1a9c3be3b..34636285c51 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -55,7 +55,7 @@ constraints(ProjectUrlConstrainer.new) do
end
resource :pages, only: [:show, :destroy] do
- resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: %r{[^/]+} } do
+ resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do
member do
post :verify
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 65785385554..d8ba3c06d3a 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -44,7 +44,6 @@ function generateEntries() {
const manualEntries = {
balsamiq_viewer: './blob/balsamiq_viewer.js',
- cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
environments: './environments/environments_bundle.js',
monitoring: './monitoring/monitoring_bundle.js',
mr_notes: './mr_notes/index.js',
@@ -59,7 +58,6 @@ function generateEntries() {
terminal: './terminal/terminal_bundle.js',
two_factor_auth: './two_factor_auth.js',
-
common: './commons/index.js',
common_vue: './vue_shared/vue_resource_interceptor.js',
locale: './locale/index.js',
@@ -225,6 +223,33 @@ const config = {
return `${moduleNames[0]}-${hash.substr(0, 6)}`;
}),
+ // create cacheable common library bundle for all vue chunks
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'common_vue',
+ chunks: [
+ 'boards',
+ 'deploy_keys',
+ 'environments',
+ 'filtered_search',
+ 'groups',
+ 'monitoring',
+ 'mr_notes',
+ 'notebook_viewer',
+ 'pdf_viewer',
+ 'pipelines',
+ 'pipelines_details',
+ 'registry_list',
+ 'ide',
+ 'schedule_form',
+ 'schedules_index',
+ 'sidebar',
+ 'vue_merge_request_widget',
+ ],
+ minChunks: function(module, count) {
+ return module.resource && (/vue_shared/).test(module.resource);
+ },
+ }),
+
// create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'common', 'webpack_runtime'],
diff --git a/db/migrate/20180222043024_add_ip_address_to_runner.rb b/db/migrate/20180222043024_add_ip_address_to_runner.rb
new file mode 100644
index 00000000000..bf00560b5a8
--- /dev/null
+++ b/db/migrate/20180222043024_add_ip_address_to_runner.rb
@@ -0,0 +1,9 @@
+class AddIpAddressToRunner < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_runners, :ip_address, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5bb461169f1..3fb80065ff5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180216121030) do
+ActiveRecord::Schema.define(version: 20180222043024) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -437,6 +437,7 @@ ActiveRecord::Schema.define(version: 20180216121030) do
t.boolean "run_untagged", default: true, null: false
t.boolean "locked", default: false, null: false
t.integer "access_level", default: 0, null: false
+ t.string "ip_address"
end
add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 46fcb7c6baf..fb7a23e2750 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,5 +1,4 @@
---
-toc: false
comments: false
---
@@ -8,15 +7,9 @@ comments: false
Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
platform for software development!
-GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans.
-
-With GitLab self-hosted, you deploy your own GitLab instance on-premises or on a private cloud of your choice. GitLab self-hosted is available for [free and with paid subscriptions](https://about.gitlab.com/products/): Libre, Starter, Premium, and Ultimate.
-
-Every feature available in Libre is also available in Starter, Premium, and Ultimate.
-Starter features are also available in Premium and Ultimate, and Premium features are also
-available in Ultimate.
-
-GitLab.com is our SaaS offering. It's hosted, managed, and administered by GitLab, with [free and paid plans](https://about.gitlab.com/gitlab-com/) for individuals and teams: Free, Bronze, Silver, and Gold.
+GitLab offers the most scalable Git-based fully integrated platform for
+software development, with flexible products and subscriptions.
+To understand what features you have access to, check the [GitLab subscriptions](#gitlab-subscriptions) below.
## Shortcuts to GitLab's most visited docs
@@ -124,8 +117,6 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
- [GitLab Integration](integration/README.md): Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication.
- [Trello Power-Up](integration/trello_power_up.md): Integrate with GitLab's Trello Power-Up
-----
-
## Administrator documentation
[Administration documentation](administration/index.md) applies to admin users of GitLab
@@ -143,3 +134,42 @@ Learn how to contribute to GitLab:
- [Development](development/README.md): All styleguides and explanations how to contribute.
- [Legal](legal/README.md): Contributor license agreements.
- [Writing documentation](development/writing_documentation.md): Contributing to GitLab Docs.
+
+## GitLab subscriptions
+
+You have two options to use GitLab:
+
+- GitLab self-hosted: Install, administer, and maintain your own GitLab instance.
+- GitLab.com: GitLab's SaaS offering. You don't need to install anything to use GitLab.com,
+you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab
+straight away.
+
+### GitLab self-hosted
+
+With GitLab self-hosted, you deploy your own GitLab instance on-premises or on a private cloud of your choice. GitLab self-hosted is available for [free and with paid subscriptions](https://about.gitlab.com/products/): Libre, Starter, Premium, and Ultimate.
+
+Every feature available in Libre is also available in Starter, Premium, and Ultimate.
+Starter features are also available in Premium and Ultimate, and Premium features are also
+available in Ultimate.
+
+### GitLab.com
+
+GitLab.com is hosted, managed, and administered by GitLab, Inc., with
+[free and paid subscriptions](https://about.gitlab.com/gitlab-com/) for individuals
+and teams: Free, Bronze, Silver, and Gold.
+
+GitLab.com subscriptions grants access
+to the same features available in GitLab self-hosted, **expect
+[administration](administration/index.md) tools and settings**:
+
+- GitLab.com Free includes the same features available in GitLab Libre
+- GitLab.com Bronze includes the same features available in GitLab Starter
+- GitLab.com Silver includes the same features available in GitLab Premium
+- GitLab.com Gold includes the same features available in GitLab Ultimate
+
+For supporting the open source community and encouraging the development of
+open source projects, GitLab grants access to **Gold** features
+for all GitLab.com **public** projects, regardless of the subscription.
+
+To know more about GitLab subscriptions and licensing, please refer to the
+[GitLab Product Marketing Handbook](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index f30a85b114e..23ce6a5f210 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -56,6 +56,9 @@ future GitLab releases.**
| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
+| **CI_RUNNER_VERSION** | all | 10.6 | GitLab Runner version that is executing the current job |
+| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job |
+| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
| **CI_PIPELINE_SOURCE** | 10.0 | all | The source for this pipeline, one of: push, web, trigger, schedule, api, external. Pipelines created before 9.5 will have unknown as source |
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 6c93c29124d..09957feee17 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -507,6 +507,7 @@ This is the entry point for our store. You can use the following as a guide:
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
+import * as getters from './getters';
import * as mutations from './mutations';
Vue.use(Vuex);
@@ -514,6 +515,7 @@ Vue.use(Vuex);
export default new Vuex.Store({
actions,
getters,
+ mutations,
state: {
users: [],
},
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 3ae98adc465..f8a7dd6b1dc 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -109,8 +109,7 @@ in your SAML IdP:
1. Change the value of `issuer` to a unique name, which will identify the application
to the IdP.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. For the changes to take effect, you must [reconfigure][] GitLab if you installed via Omnibus or [restart GitLab][] if you installed from source.
1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified
in `issuer`.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index ea7b1c9a0ed..650d60f1585 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -36,12 +36,16 @@ GFM honors the markdown specification in how [paragraphs and line breaks are han
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
Line-breaks, or softreturns, are rendered if you end a line with two or more spaces:
- Roses are red [followed by two or more spaces]
+[//]: # (Do *NOT* remove the two ending whitespaces in the following line.)
+[//]: # (They are needed for the Markdown text to render correctly.)
+ Roses are red [followed by two or more spaces]
Violets are blue
Sugar is sweet
-Roses are red
+[//]: # (Do *NOT* remove the two ending whitespaces in the following line.)
+[//]: # (They are needed for the Markdown text to render correctly.)
+Roses are red
Violets are blue
Sugar is sweet
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 0de89f90e21..16027744164 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -34,7 +34,7 @@ With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Premium)
- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Starter)
- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Starter)
-- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter)
+- Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Starter)
## Use cases
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index fbe30192a16..35ac0b4cbca 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -9,16 +9,22 @@ module API
Gitlab::CurrentSettings.runners_registration_token)
end
- def get_runner_version_from_params
- return unless params['info'].present?
+ def authenticate_runner!
+ forbidden! unless current_runner
- attributes_for_keys(%w(name version revision platform architecture), params['info'])
+ current_runner
+ .update_cached_info(get_runner_details_from_request)
end
- def authenticate_runner!
- forbidden! unless current_runner
+ def get_runner_details_from_request
+ return get_runner_ip unless params['info'].present?
+
+ attributes_for_keys(%w(name version revision platform architecture), params['info'])
+ .merge(get_runner_ip)
+ end
- current_runner.update_cached_info(get_runner_version_from_params)
+ def get_runner_ip
+ { ip_address: request.ip }
end
def current_runner
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 5469cba69a6..91cdc564002 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -16,7 +16,8 @@ module API
optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
end
post '/' do
- attributes = attributes_for_keys [:description, :locked, :run_untagged, :tag_list]
+ attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list])
+ .merge(get_runner_details_from_request)
runner =
if runner_registration_token_valid?
@@ -30,7 +31,6 @@ module API
return forbidden! unless runner
if runner.id
- runner.update(get_runner_version_from_params)
present runner, with: Entities::RunnerRegistrationDetails
else
not_found!
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index ac12271a87e..52b44b9b3c5 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -59,7 +59,7 @@ module Gitlab
end
def pages(limit: nil)
- @repository.gitaly_migrate(:wiki_get_all_pages, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
+ @repository.gitaly_migrate(:wiki_get_all_pages) do |is_enabled|
if is_enabled
gitaly_get_all_pages
else
@@ -68,9 +68,8 @@ module Gitlab
end
end
- # Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42039
def page(title:, version: nil, dir: nil)
- @repository.gitaly_migrate(:wiki_find_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
+ @repository.gitaly_migrate(:wiki_find_page) do |is_enabled|
if is_enabled
gitaly_find_page(title: title, version: version, dir: dir)
else
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 5a5ae7f19d4..781783f4d97 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -1,6 +1,8 @@
module Gitlab
class SearchResults
class FoundBlob
+ include EncodingHelper
+
attr_reader :id, :filename, :basename, :ref, :startline, :data, :project_id
def initialize(opts = {})
@@ -9,7 +11,7 @@ module Gitlab
@basename = opts.fetch(:basename, nil)
@ref = opts.fetch(:ref, nil)
@startline = opts.fetch(:startline, nil)
- @data = opts.fetch(:data, nil)
+ @data = encode_utf8(opts.fetch(:data, nil))
@per_page = opts.fetch(:per_page, 20)
@project_id = opts.fetch(:project_id, nil)
end
diff --git a/scripts/security-harness b/scripts/security-harness
index d454f44dff7..c60b3410095 100755
--- a/scripts/security-harness
+++ b/scripts/security-harness
@@ -21,6 +21,8 @@ else
File.open(hook_path, 'w') do |file|
IO.copy_stream(DATA, file)
end
+
+ File.chmod(0755, hook_path)
end
# Toggle the harness on or off
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index 004b463e745..149b690ff70 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -34,6 +34,8 @@ describe Oauth::AuthorizationsController do
end
context 'with valid params' do
+ render_views
+
it 'returns 200 code and renders view' do
get :new, params
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 2192fd5cae2..83a3799e883 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -53,6 +53,66 @@ describe Projects::PagesDomainsController do
end
end
+ describe 'GET edit' do
+ it "displays the 'edit' page" do
+ get(:edit, request_params.merge(id: pages_domain.domain))
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to render_template('edit')
+ end
+ end
+
+ describe 'PATCH update' do
+ before do
+ controller.instance_variable_set(:@domain, pages_domain)
+ end
+
+ let(:pages_domain_params) do
+ attributes_for(:pages_domain, :with_certificate, :with_key).slice(:key, :certificate)
+ end
+
+ let(:params) do
+ request_params.merge(id: pages_domain.domain, pages_domain: pages_domain_params)
+ end
+
+ it 'updates the domain' do
+ expect(pages_domain)
+ .to receive(:update)
+ .with(pages_domain_params)
+ .and_return(true)
+
+ patch(:update, params)
+ end
+
+ it 'redirects to the project page' do
+ patch(:update, params)
+
+ expect(flash[:notice]).to eq 'Domain was updated'
+ expect(response).to redirect_to(project_pages_path(project))
+ end
+
+ context 'the domain is invalid' do
+ it 'renders the edit action' do
+ allow(pages_domain).to receive(:update).and_return(false)
+
+ patch(:update, params)
+
+ expect(response).to render_template('edit')
+ end
+ end
+
+ context 'the parameters include the domain' do
+ it 'renders 400 Bad Request' do
+ expect(pages_domain)
+ .to receive(:update)
+ .with(hash_not_including(:domain))
+ .and_return(true)
+
+ patch(:update, params.deep_merge(pages_domain: { domain: 'abc' }))
+ end
+ end
+ end
+
describe 'POST verify' do
let(:params) { request_params.merge(id: pages_domain.domain) }
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index a96f2c186a4..2a0d235ef04 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -160,6 +160,37 @@ feature 'Pages' do
expect(page).to have_content('my.test.domain.com')
end
+
+ describe 'updating the certificate for an existing domain' do
+ let!(:domain) do
+ create(:pages_domain, :with_key, :with_certificate, project: project)
+ end
+
+ it 'allows the certificate to be updated' do
+ visit project_pages_path(project)
+
+ within('#content-body') { click_link 'Details' }
+ click_link 'Edit'
+ click_button 'Save Changes'
+
+ expect(page).to have_content('Domain was updated')
+ end
+
+ context 'when the certificate is invalid' do
+ it 'tells the user what the problem is' do
+ visit project_pages_path(project)
+
+ within('#content-body') { click_link 'Details' }
+ click_link 'Edit'
+ fill_in 'Certificate (PEM)', with: 'invalid data'
+ click_button 'Save Changes'
+
+ expect(page).to have_content('Certificate must be a valid PEM certificate')
+ expect(page).to have_content('Certificate misses intermediates')
+ expect(page).to have_content("Key doesn't match the certificate")
+ end
+ end
+ end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 37a06b65481..3a8e7c05cc4 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -367,23 +367,6 @@ describe 'Pipelines', :js do
expect(build.reload).to be_canceled
end
end
-
- context 'dropdown jobs list' do
- it 'should keep the dropdown open when the user ctr/cmd + clicks in the job name' do
- find('.js-builds-dropdown-button').click
- dropdown_item = find('.mini-pipeline-graph-dropdown-item').native
-
- %i(alt control).each do |meta_key|
- page.driver.browser.action
- .key_down(meta_key)
- .click(dropdown_item)
- .key_up(meta_key)
- .perform
- end
-
- expect(page).to have_selector('.js-ci-action-icon')
- end
- end
end
context 'with pagination' do
diff --git a/spec/javascripts/labels_select_spec.js b/spec/javascripts/labels_select_spec.js
new file mode 100644
index 00000000000..b8f7b1dc855
--- /dev/null
+++ b/spec/javascripts/labels_select_spec.js
@@ -0,0 +1,43 @@
+import LabelsSelect from '~/labels_select';
+
+const mockUrl = '/foo/bar/url';
+
+const mockLabels = [
+ {
+ id: 26,
+ title: 'Foo Label',
+ description: 'Foobar',
+ color: '#BADA55',
+ text_color: '#FFFFFF',
+ },
+];
+
+describe('LabelsSelect', () => {
+ describe('getLabelTemplate', () => {
+ const label = mockLabels[0];
+ let $labelEl;
+
+ beforeEach(() => {
+ $labelEl = $(LabelsSelect.getLabelTemplate({
+ labels: mockLabels,
+ issueUpdateURL: mockUrl,
+ }));
+ });
+
+ it('generated label item template has correct label URL', () => {
+ expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label');
+ });
+
+ it('generated label item template has correct label title', () => {
+ expect($labelEl.find('span.label').text()).toBe(label.title);
+ });
+
+ it('generated label item template has label description as title attribute', () => {
+ expect($labelEl.find('span.label').attr('title')).toBe(label.description);
+ });
+
+ it('generated label item template has correct label styles', () => {
+ expect($labelEl.find('span.label').attr('style')).toBe(`background-color: ${label.color}; color: ${label.text_color};`);
+ });
+ });
+});
diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js
index c9453a21189..4e4343812bd 100644
--- a/spec/javascripts/sidebar/assignees_spec.js
+++ b/spec/javascripts/sidebar/assignees_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import Assignee from '~/sidebar/components/assignees/assignees';
+import Assignee from '~/sidebar/components/assignees/assignees.vue';
import UsersMock from './mock_data';
import UsersMockHelper from '../helpers/user_mock_data_helper';
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 1ebb0105cf5..d8250e4b4c6 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require 'spec_helper'
describe Gitlab::ProjectSearchResults do
@@ -105,6 +106,32 @@ describe Gitlab::ProjectSearchResults do
end
end
+ context 'when the search returns non-ASCII data' do
+ context 'with UTF-8' do
+ let(:results) { project.repository.search_files_by_content("файл", 'master') }
+
+ it 'returns results as UTF-8' do
+ expect(subject.filename).to eq('encoding/russian.rb')
+ expect(subject.basename).to eq('encoding/russian')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("Хороший файл")
+ end
+ end
+
+ context 'with ISO-8859-1' do
+ let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) }
+
+ it 'returns results as UTF-8' do
+ expect(subject.filename).to eq('encoding/iso8859.txt')
+ expect(subject.basename).to eq('encoding/iso8859')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("Äü\n\nfoo")
+ end
+ end
+ end
+
context "when filename has extension" do
let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index f10b6e43d09..4021e537efc 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -122,6 +122,15 @@ describe API::Runner do
end
end
end
+
+ it "sets the runner's ip_address" do
+ post api('/runners'),
+ { token: registration_token },
+ { 'REMOTE_ADDR' => '123.111.123.111' }
+
+ expect(response).to have_gitlab_http_status 201
+ expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
+ end
end
describe 'DELETE /api/v4/runners' do
@@ -422,6 +431,15 @@ describe API::Runner do
end
end
+ it "sets the runner's ip_address" do
+ post api('/jobs/request'),
+ { token: runner.token },
+ { 'User-Agent' => user_agent, 'REMOTE_ADDR' => '123.222.123.222' }
+
+ expect(response).to have_gitlab_http_status 201
+ expect(runner.reload.ip_address).to eq('123.222.123.222')
+ end
+
context 'when concurrently updating a job' do
before do
expect_any_instance_of(Ci::Build).to receive(:run!)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index a0b97ceead9..ad5a289290c 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -123,6 +123,40 @@ describe Projects::UpdateService do
end
end
+ context 'when we update project but not enabling a wiki' do
+ it 'does not try to create an empty wiki' do
+ FileUtils.rm_rf(project.wiki.repository.path)
+
+ result = update_project(project, user, { name: 'test1' })
+
+ expect(result).to eq({ status: :success })
+ expect(project.wiki_repository_exists?).to be false
+ end
+ end
+
+ context 'when enabling a wiki' do
+ it 'creates a wiki' do
+ project.project_feature.update(wiki_access_level: ProjectFeature::DISABLED)
+ FileUtils.rm_rf(project.wiki.repository.path)
+
+ result = update_project(project, user, project_feature_attributes: { wiki_access_level: ProjectFeature::ENABLED })
+
+ expect(result).to eq({ status: :success })
+ expect(project.wiki_repository_exists?).to be true
+ expect(project.wiki_enabled?).to be true
+ end
+
+ it 'logs an error and creates a metric when wiki can not be created' do
+ project.project_feature.update(wiki_access_level: ProjectFeature::DISABLED)
+
+ expect_any_instance_of(ProjectWiki).to receive(:wiki).and_raise(ProjectWiki::CouldNotCreateWikiError)
+ expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}")
+ expect(Gitlab::Metrics).to receive(:counter)
+
+ update_project(project, user, project_feature_attributes: { wiki_access_level: ProjectFeature::ENABLED })
+ end
+ end
+
context 'when updating a project that contains container images' do
before do
stub_container_registry_config(enabled: true)