summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md17
-rw-r--r--app/assets/javascripts/environments/components/environment.js29
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js30
-rw-r--r--app/assets/javascripts/environments/components/environments_table.js37
-rw-r--r--app/assets/javascripts/environments/services/environments_service.js5
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js70
-rw-r--r--app/assets/stylesheets/pages/projects.scss11
-rw-r--r--app/services/users/create_service.rb2
-rw-r--r--app/views/projects/new.html.haml24
-rw-r--r--changelogs/unreleased/26595-fix-issue-preselected-template.yml4
-rw-r--r--changelogs/unreleased/28732-expandable-folders.yml4
-rw-r--r--changelogs/unreleased/29034-fix-github-importer.yml4
-rw-r--r--changelogs/unreleased/29432-prevent-click-disabled-btn.yml4
-rw-r--r--changelogs/unreleased/29541-fix-github-importer-deleted-fork.yml4
-rw-r--r--changelogs/unreleased/29871-api-remove-merge-requests-comments-endpoints.yml4
-rw-r--r--changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml4
-rw-r--r--changelogs/unreleased/30264-fix-vue-pagination.yml5
-rw-r--r--changelogs/unreleased/30276-move-top-badges.yml4
-rw-r--r--changelogs/unreleased/30289-allow-users-to-import-github-projects-to-subgroups.yml4
-rw-r--r--changelogs/unreleased/backport-sticking-api-helper-changes.yml4
-rw-r--r--changelogs/unreleased/fix-gb-environments-folders-route.yml4
-rw-r--r--changelogs/unreleased/forked-subquery-order.yml4
-rw-r--r--changelogs/unreleased/make-ci-build-to-lock-on-status-change.yml4
-rw-r--r--changelogs/unreleased/sh-fix-destroy-user-race.yml5
-rw-r--r--changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml4
-rw-r--r--changelogs/unreleased/todo-update-order.yml4
-rw-r--r--config/webpack.config.js3
-rw-r--r--lib/gitlab/shell.rb2
-rw-r--r--spec/features/admin/admin_health_check_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb20
-rw-r--r--spec/features/projects/environments/environments_spec.rb40
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb8
-rw-r--r--spec/javascripts/environments/environment_spec.js99
-rw-r--r--spec/javascripts/environments/environments_store_spec.js110
-rw-r--r--spec/javascripts/environments/mock_data.js16
-rw-r--r--spec/lib/gitlab/backend/shell_spec.rb9
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb9
-rw-r--r--spec/services/users/create_service_spec.rb26
38 files changed, 512 insertions, 131 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 734c72f5dd2..3e5475a2296 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,23 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.0.3 (2017-04-05)
+
+- Fix name colision when importing GitHub pull requests from forked repositories. !9719
+- Fix GitHub Importer for PRs of deleted forked repositories. !9992
+- Fix environment folder route when special chars present in environment name. !10250
+- Improve Markdown rendering when a lot of merge requests are referenced. !10252
+- Allow users to import GitHub projects to subgroups.
+- Backport API changes needed to fix sticking in EE.
+- Remove unnecessary ORDER BY clause from `forked_to_project_id` subquery. (mhasbini)
+- Make CI build to use optimistic locking only on status change.
+- Fix race condition where a namespace would be deleted before a project was deleted.
+- Fix linking to new issue with selected template via url parameter.
+- Remove unnecessary ORDER BY clause when updating todos. (mhasbini)
+- API: Make the /notes endpoint work with noteable iid instead of id.
+- Fixes method not replacing URL parameters correctly and breaking pipelines pagination.
+- Move issue, mr, todos next to profile dropdown in top nav.
+
## 9.0.2 (2017-03-29)
- Correctly update paths when changing a child group.
diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.js
index 51aab8460f6..0518422e475 100644
--- a/app/assets/javascripts/environments/components/environment.js
+++ b/app/assets/javascripts/environments/components/environment.js
@@ -24,6 +24,7 @@ export default Vue.component('environment-component', {
state: store.state,
visibility: 'available',
isLoading: false,
+ isLoadingFolderContent: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
@@ -68,15 +69,21 @@ export default Vue.component('environment-component', {
this.fetchEnvironments();
eventHub.$on('refreshEnvironments', this.fetchEnvironments);
+ eventHub.$on('toggleFolder', this.toggleFolder);
},
beforeDestroyed() {
eventHub.$off('refreshEnvironments');
+ eventHub.$off('toggleFolder');
},
methods: {
- toggleRow(model) {
- return this.store.toggleFolder(model.name);
+ toggleFolder(folder, folderUrl) {
+ this.store.toggleFolder(folder);
+
+ if (!folder.isOpen) {
+ this.fetchChildEnvironments(folder, folderUrl);
+ }
},
/**
@@ -117,6 +124,21 @@ export default Vue.component('environment-component', {
new Flash('An error occurred while fetching the environments.');
});
},
+
+ fetchChildEnvironments(folder, folderUrl) {
+ this.isLoadingFolderContent = true;
+
+ this.service.getFolderContent(folderUrl)
+ .then(resp => resp.json())
+ .then((response) => {
+ this.store.setfolderContent(folder, response.environments);
+ this.isLoadingFolderContent = false;
+ })
+ .catch(() => {
+ this.isLoadingFolderContent = false;
+ new Flash('An error occurred while fetching the environments.');
+ });
+ },
},
template: `
@@ -179,7 +201,8 @@ export default Vue.component('environment-component', {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
- :service="service"/>
+ :service="service"
+ :is-loading-folder-content="isLoadingFolderContent" />
</div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
diff --git a/app/assets/javascripts/environments/components/environment_item.js b/app/assets/javascripts/environments/components/environment_item.js
index 9c196562c6c..e44d93a30c7 100644
--- a/app/assets/javascripts/environments/components/environment_item.js
+++ b/app/assets/javascripts/environments/components/environment_item.js
@@ -7,6 +7,7 @@ import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import MonitoringButtonComponent from './environment_monitoring';
import CommitComponent from '../../vue_shared/components/commit';
+import eventHub from '../event_hub';
/**
* Envrionment Item Component
@@ -410,7 +411,6 @@ export default {
folderUrl() {
return `${window.location.pathname}/folders/${this.model.folderName}`;
},
-
},
/**
@@ -428,15 +428,37 @@ export default {
return true;
},
+ methods: {
+ onClickFolder() {
+ eventHub.$emit('toggleFolder', this.model, this.folderUrl);
+ },
+ },
+
template: `
- <tr>
+ <tr :class="{ 'js-child-row': model.isChildren }">
<td>
<a v-if="!model.isFolder"
class="environment-name"
+ :class="{ 'prepend-left-default': model.isChildren }"
:href="environmentPath">
{{model.name}}
</a>
- <a v-else class="folder-name" :href="folderUrl">
+ <span v-else
+ class="folder-name"
+ @click="onClickFolder"
+ role="button">
+
+ <span class="folder-icon">
+ <i
+ v-show="model.isOpen"
+ class="fa fa-caret-down"
+ aria-hidden="true" />
+ <i
+ v-show="!model.isOpen"
+ class="fa fa-caret-right"
+ aria-hidden="true"/>
+ </span>
+
<span class="folder-icon">
<i class="fa fa-folder" aria-hidden="true"></i>
</span>
@@ -448,7 +470,7 @@ export default {
<span class="badge">
{{model.size}}
</span>
- </a>
+ </span>
</td>
<td class="deployment-column">
diff --git a/app/assets/javascripts/environments/components/environments_table.js b/app/assets/javascripts/environments/components/environments_table.js
index 338dff40bc9..5e6af3a1d45 100644
--- a/app/assets/javascripts/environments/components/environments_table.js
+++ b/app/assets/javascripts/environments/components/environments_table.js
@@ -31,6 +31,18 @@ export default {
type: Object,
required: true,
},
+
+ isLoadingFolderContent: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+
+ methods: {
+ folderUrl(model) {
+ return `${window.location.pathname}/folders/${model.folderName}`;
+ },
},
template: `
@@ -53,6 +65,31 @@ export default {
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"></tr>
+
+ <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
+ <tr v-if="isLoadingFolderContent">
+ <td colspan="6" class="text-center">
+ <i class="fa fa-spin fa-spinner fa-2x" aria-hidden="true"/>
+ </td>
+ </tr>
+
+ <template v-else>
+ <tr is="environment-item"
+ v-for="children in model.children"
+ :model="children"
+ :can-create-deployment="canCreateDeployment"
+ :can-read-environment="canReadEnvironment"
+ :service="service"></tr>
+
+ <tr>
+ <td colspan="6" class="text-center">
+ <a :href="folderUrl(model)" class="btn btn-default">
+ Show all
+ </a>
+ </td>
+ </tr>
+ </template>
+ </template>
</template>
</tbody>
</table>
diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js
index 07040bf0d73..8adb53ea86d 100644
--- a/app/assets/javascripts/environments/services/environments_service.js
+++ b/app/assets/javascripts/environments/services/environments_service.js
@@ -7,6 +7,7 @@ Vue.use(VueResource);
export default class EnvironmentsService {
constructor(endpoint) {
this.environments = Vue.resource(endpoint);
+ this.folderResults = 3;
}
get(scope, page) {
@@ -16,4 +17,8 @@ export default class EnvironmentsService {
postAction(endpoint) {
return Vue.http.post(endpoint, {}, { emulateJSON: true });
}
+
+ getFolderContent(folderUrl) {
+ return Vue.http.get(`${folderUrl}.json?per_page=${this.folderResults}`);
+ }
}
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index 3c3084f3b78..158e7922e3c 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -38,7 +38,12 @@ export default class EnvironmentsStore {
let filtered = {};
if (env.size > 1) {
- filtered = Object.assign({}, env, { isFolder: true, folderName: env.name });
+ filtered = Object.assign({}, env, {
+ isFolder: true,
+ folderName: env.name,
+ isOpen: false,
+ children: [],
+ });
}
if (env.latest) {
@@ -85,4 +90,67 @@ export default class EnvironmentsStore {
this.state.stoppedCounter = count;
return count;
}
+
+ /**
+ * Toggles folder open property for the given folder.
+ *
+ * @param {Object} folder
+ * @return {Array}
+ */
+ toggleFolder(folder) {
+ return this.updateFolder(folder, 'isOpen', !folder.isOpen);
+ }
+
+ /**
+ * Updates the folder with the received environments.
+ *
+ *
+ * @param {Object} folder Folder to update
+ * @param {Array} environments Received environments
+ * @return {Object}
+ */
+ setfolderContent(folder, environments) {
+ const updatedEnvironments = environments.map((env) => {
+ let updated = env;
+
+ if (env.latest) {
+ updated = Object.assign({}, env, env.latest);
+ delete updated.latest;
+ } else {
+ updated = env;
+ }
+
+ updated.isChildren = true;
+
+ return updated;
+ });
+
+ return this.updateFolder(folder, 'children', updatedEnvironments);
+ }
+
+ /**
+ * Given a folder a prop and a new value updates the correct folder.
+ *
+ * @param {Object} folder
+ * @param {String} prop
+ * @param {String|Boolean|Object|Array} newValue
+ * @return {Array}
+ */
+ updateFolder(folder, prop, newValue) {
+ const environments = this.state.environments;
+
+ const updatedEnvironments = environments.map((env) => {
+ const updateEnv = Object.assign({}, env);
+ if (env.isFolder && env.id === folder.id) {
+ updateEnv[prop] = newValue;
+ }
+
+ return updateEnv;
+ });
+
+ this.state.environments = updatedEnvironments;
+
+ return updatedEnvironments;
+ }
+
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index c2c2f371b87..0fa1f68e034 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -459,20 +459,13 @@ a.deploy-project-label {
flex-wrap: wrap;
.btn {
- margin: 0 10px 10px 0;
padding: 8px;
+ margin-left: 10px;
}
> div {
+ margin-bottom: 10px;
padding-left: 0;
-
- &:last-child {
- margin-bottom: 0;
-
- .btn {
- margin-right: 0;
- }
- }
}
}
}
diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb
index 193fcd85896..a847a71a66a 100644
--- a/app/services/users/create_service.rb
+++ b/app/services/users/create_service.rb
@@ -62,6 +62,7 @@ module Users
:email,
:external,
:force_random_password,
+ :password_automatically_set,
:hide_no_password,
:hide_no_ssh_key,
:key_id,
@@ -85,6 +86,7 @@ module Users
[
:email,
:email_confirmation,
+ :password_automatically_set,
:name,
:password,
:username
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 09ac1fd6794..0c7b53e5a9a 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -78,7 +78,7 @@
- if git_import_enabled?
%button.btn.js-toggle-button.import_git{ type: "button" }
= icon('git', text: 'Repo by URL')
- .import_gitlab_project
+ .import_gitlab_project.has-tooltip{ data: { container: 'body' } }
- if gitlab_project_import_enabled?
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
@@ -109,6 +109,9 @@
%p Please wait a moment, this page will automatically refresh when ready.
:javascript
+ var importBtnTooltip = "Please enter a valid project name.";
+ var $importBtnWrapper = $('.import_gitlab_project');
+
$('.how_to_import_link').bind('click', function (e) {
e.preventDefault();
var import_modal = $(this).next(".modal").show();
@@ -123,15 +126,8 @@
$(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
});
- $('.btn_import_gitlab_project').attr('disabled',true)
- $('.import_gitlab_project').attr('title', 'Project path and name required.');
-
- $('.import_gitlab_project').click(function( event ) {
- if($('.btn_import_gitlab_project').attr('disabled')) {
- event.preventDefault();
- new Flash("Please enter path and name for the project to be imported to.");
- }
- });
+ $('.btn_import_gitlab_project').attr('disabled', $('#project_path').val().trim().length === 0);
+ $importBtnWrapper.attr('title', importBtnTooltip);
$('#new_project').submit(function(){
var $path = $('#project_path');
@@ -139,13 +135,13 @@
});
$('#project_path').keyup(function(){
- if($(this).val().length !=0) {
+ if($(this).val().trim().length !== 0) {
$('.btn_import_gitlab_project').attr('disabled', false);
- $('.import_gitlab_project').attr('title','');
- $(".flash-container").html("")
+ $importBtnWrapper.attr('title','');
+ $importBtnWrapper.removeClass('has-tooltip');
} else {
$('.btn_import_gitlab_project').attr('disabled',true);
- $('.import_gitlab_project').attr('title', 'Project path and name required.');
+ $importBtnWrapper.addClass('has-tooltip');
}
});
diff --git a/changelogs/unreleased/26595-fix-issue-preselected-template.yml b/changelogs/unreleased/26595-fix-issue-preselected-template.yml
deleted file mode 100644
index a94765f8f2a..00000000000
--- a/changelogs/unreleased/26595-fix-issue-preselected-template.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix linking to new issue with selected template via url parameter
-merge_request:
-author:
diff --git a/changelogs/unreleased/28732-expandable-folders.yml b/changelogs/unreleased/28732-expandable-folders.yml
new file mode 100644
index 00000000000..9ae30ba6253
--- /dev/null
+++ b/changelogs/unreleased/28732-expandable-folders.yml
@@ -0,0 +1,4 @@
+---
+title: Add back expandable folder behavior
+merge_request:
+author:
diff --git a/changelogs/unreleased/29034-fix-github-importer.yml b/changelogs/unreleased/29034-fix-github-importer.yml
deleted file mode 100644
index 6d08db3d55d..00000000000
--- a/changelogs/unreleased/29034-fix-github-importer.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix name colision when importing GitHub pull requests from forked repositories
-merge_request: 9719
-author:
diff --git a/changelogs/unreleased/29432-prevent-click-disabled-btn.yml b/changelogs/unreleased/29432-prevent-click-disabled-btn.yml
new file mode 100644
index 00000000000..f30570cf68b
--- /dev/null
+++ b/changelogs/unreleased/29432-prevent-click-disabled-btn.yml
@@ -0,0 +1,4 @@
+---
+title: Fix project title validation, prevent clicking on disabled button
+merge_request: 9931
+author:
diff --git a/changelogs/unreleased/29541-fix-github-importer-deleted-fork.yml b/changelogs/unreleased/29541-fix-github-importer-deleted-fork.yml
deleted file mode 100644
index fcb5798a619..00000000000
--- a/changelogs/unreleased/29541-fix-github-importer-deleted-fork.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix GitHub Importer for PRs of deleted forked repositories
-merge_request: 9992
-author:
diff --git a/changelogs/unreleased/29871-api-remove-merge-requests-comments-endpoints.yml b/changelogs/unreleased/29871-api-remove-merge-requests-comments-endpoints.yml
deleted file mode 100644
index e3fb62d53b6..00000000000
--- a/changelogs/unreleased/29871-api-remove-merge-requests-comments-endpoints.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'API: Make the /notes endpoint work with noteable iid instead of id'
-merge_request:
-author:
diff --git a/changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml b/changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml
deleted file mode 100644
index f3f4e065aef..00000000000
--- a/changelogs/unreleased/30098-banzai-filter-mergerequestreferencefilter-has-an-n-1-query-problem.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve Markdown rendering when a lot of merge requests are referenced
-merge_request: 10252
-author:
diff --git a/changelogs/unreleased/30264-fix-vue-pagination.yml b/changelogs/unreleased/30264-fix-vue-pagination.yml
deleted file mode 100644
index d5846e52bcf..00000000000
--- a/changelogs/unreleased/30264-fix-vue-pagination.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes method not replacing URL parameters correctly and breaking pipelines
- pagination
-merge_request:
-author:
diff --git a/changelogs/unreleased/30276-move-top-badges.yml b/changelogs/unreleased/30276-move-top-badges.yml
deleted file mode 100644
index 68a8ab359fd..00000000000
--- a/changelogs/unreleased/30276-move-top-badges.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Move issue, mr, todos next to profile dropdown in top nav
-merge_request:
-author:
diff --git a/changelogs/unreleased/30289-allow-users-to-import-github-projects-to-subgroups.yml b/changelogs/unreleased/30289-allow-users-to-import-github-projects-to-subgroups.yml
deleted file mode 100644
index a33382a85e3..00000000000
--- a/changelogs/unreleased/30289-allow-users-to-import-github-projects-to-subgroups.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow users to import GitHub projects to subgroups
-merge_request:
-author:
diff --git a/changelogs/unreleased/backport-sticking-api-helper-changes.yml b/changelogs/unreleased/backport-sticking-api-helper-changes.yml
deleted file mode 100644
index 170ef152271..00000000000
--- a/changelogs/unreleased/backport-sticking-api-helper-changes.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Backport API changes needed to fix sticking in EE
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-gb-environments-folders-route.yml b/changelogs/unreleased/fix-gb-environments-folders-route.yml
deleted file mode 100644
index fd9d9e6f168..00000000000
--- a/changelogs/unreleased/fix-gb-environments-folders-route.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix environment folder route when special chars present in environment name
-merge_request: 10250
-author:
diff --git a/changelogs/unreleased/forked-subquery-order.yml b/changelogs/unreleased/forked-subquery-order.yml
deleted file mode 100644
index 06fb8236783..00000000000
--- a/changelogs/unreleased/forked-subquery-order.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unnecessary ORDER BY clause from `forked_to_project_id` subquery
-merge_request:
-author: mhasbini
diff --git a/changelogs/unreleased/make-ci-build-to-lock-on-status-change.yml b/changelogs/unreleased/make-ci-build-to-lock-on-status-change.yml
deleted file mode 100644
index b0c5eb4ed17..00000000000
--- a/changelogs/unreleased/make-ci-build-to-lock-on-status-change.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make CI build to use optimistic locking only on status change
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-fix-destroy-user-race.yml b/changelogs/unreleased/sh-fix-destroy-user-race.yml
deleted file mode 100644
index 4d650b51ada..00000000000
--- a/changelogs/unreleased/sh-fix-destroy-user-race.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix race condition where a namespace would be deleted before a project was
- deleted
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml b/changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml
new file mode 100644
index 00000000000..fe75d7e1156
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-ssh-keys-with-spaces.yml
@@ -0,0 +1,4 @@
+---
+title: Handle SSH keys that have multiple spaces between each marker
+merge_request:
+author:
diff --git a/changelogs/unreleased/todo-update-order.yml b/changelogs/unreleased/todo-update-order.yml
deleted file mode 100644
index 2046b6df11e..00000000000
--- a/changelogs/unreleased/todo-update-order.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unnecessary ORDER BY clause when updating todos
-merge_request:
-author: mhasbini
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 728c2a64053..36c09c14d56 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -52,7 +52,7 @@ var config = {
filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js'
},
- devtool: 'inline-source-map',
+ devtool: 'cheap-module-source-map',
module: {
rules: [
@@ -163,6 +163,7 @@ if (IS_PRODUCTION) {
}
if (IS_DEV_SERVER) {
+ config.devtool = 'cheap-module-eval-source-map';
config.devServer = {
port: DEV_SERVER_PORT,
headers: { 'Access-Control-Allow-Origin': '*' },
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index b631ef11ce7..36a871e5bbc 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def strip_key(key)
- key.split(/ /)[0, 2].join(' ')
+ key.split(/[ ]+/)[0, 2].join(' ')
end
private
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index f7e49a56deb..523afa2318f 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
feature "Admin Health Check", feature: true do
include StubENV
- include WaitForAjax
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
@@ -24,11 +23,12 @@ feature "Admin Health Check", feature: true do
expect(page).to have_selector('#health-check-token', text: token)
end
- describe 'reload access token', js: true do
+ describe 'reload access token' do
it 'changes the access token' do
orig_token = current_application_settings.health_check_access_token
click_button 'Reset health check access token'
- wait_for_ajax
+
+ expect(page).to have_content('New health check access token has been generated!')
expect(find('#health-check-token').text).not_to eq orig_token
end
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 004e335dd38..2f880c926e7 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe 'Filter issues', js: true, feature: true do
+ include Devise::Test::IntegrationHelpers
include FilteredSearchHelpers
include WaitForAjax
@@ -42,16 +43,17 @@ describe 'Filter issues', js: true, feature: true do
project.team << [user2, :master]
group.add_developer(user)
group.add_developer(user2)
- login_as(user)
- create(:issue, project: project)
- create(:issue, title: "Bug report 1", project: project)
- create(:issue, title: "Bug report 2", project: project)
- create(:issue, title: "issue with 'single quotes'", project: project)
- create(:issue, title: "issue with \"double quotes\"", project: project)
- create(:issue, title: "issue with !@\#{$%^&*()-+", project: project)
- create(:issue, title: "issue by assignee", project: project, milestone: milestone, author: user, assignee: user)
- create(:issue, title: "issue by assignee with searchTerm", project: project, milestone: milestone, author: user, assignee: user)
+ sign_in(user)
+
+ create(:issue, project: project)
+ create(:issue, project: project, title: "Bug report 1")
+ create(:issue, project: project, title: "Bug report 2")
+ create(:issue, project: project, title: "issue with 'single quotes'")
+ create(:issue, project: project, title: "issue with \"double quotes\"")
+ create(:issue, project: project, title: "issue with !@\#{$%^&*()-+")
+ create(:issue, project: project, title: "issue by assignee", milestone: milestone, author: user, assignee: user)
+ create(:issue, project: project, title: "issue by assignee with searchTerm", milestone: milestone, author: user, assignee: user)
issue = create(:issue,
title: "Bug 2",
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 641e2cf7402..cf393afccbb 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -23,6 +23,46 @@ feature 'Environments page', :feature, :js do
expect(page).to have_link('Available')
expect(page).to have_link('Stopped')
end
+
+ describe 'with one available environment' do
+ given(:environment) { create(:environment, project: project, state: :available) }
+
+ describe 'in available tab page' do
+ it 'should show one environment' do
+ visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+ expect(page).to have_css('.environments-container')
+ expect(page.all('tbody > tr').length).to eq(1)
+ end
+ end
+
+ describe 'in stopped tab page' do
+ it 'should show no environments' do
+ visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+ expect(page).to have_css('.environments-container')
+ expect(page).to have_content('You don\'t have any environments right now')
+ end
+ end
+ end
+
+ describe 'with one stopped environment' do
+ given(:environment) { create(:environment, project: project, state: :stopped) }
+
+ describe 'in available tab page' do
+ it 'should show no environments' do
+ visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+ expect(page).to have_css('.environments-container')
+ expect(page).to have_content('You don\'t have any environments right now')
+ end
+ end
+
+ describe 'in stopped tab page' do
+ it 'should show one environment' do
+ visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+ expect(page).to have_css('.environments-container')
+ expect(page.all('tbody > tr').length).to eq(1)
+ end
+ end
+ end
end
context 'without environments' do
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 2d1106ea3e8..583f479ec18 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -69,12 +69,8 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
select2(namespace.id, from: '#project_namespace_id')
- # click on disabled element
- find(:link, 'GitLab export').trigger('click')
-
- page.within('.flash-container') do
- expect(page).to have_content('Please enter path and name')
- end
+ # Check for tooltip disabled import button
+ expect(find('.import_gitlab_project')['title']).to eq('Please enter a valid project name.')
end
end
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index 9bcf617fcd8..4431baa4b96 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import '~/flash';
import EnvironmentsComponent from '~/environments/components/environment';
-import { environment } from './mock_data';
+import { environment, folder } from './mock_data';
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
@@ -179,4 +179,101 @@ describe('Environment', () => {
}, 0);
});
});
+
+ describe('expandable folders', () => {
+ const environmentsResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: [folder],
+ stopped_count: 0,
+ available_count: 1,
+ }), {
+ status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsComponent({
+ el: document.querySelector('#environments-list-view'),
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsResponseInterceptor,
+ );
+ });
+
+ it('should open a closed folder', (done) => {
+ setTimeout(() => {
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'),
+ ).toContain('display: none');
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'),
+ ).not.toContain('display: none');
+ done();
+ });
+ });
+ });
+
+ it('should close an opened folder', (done) => {
+ setTimeout(() => {
+ // open folder
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ // close folder
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'),
+ ).toContain('display: none');
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'),
+ ).not.toContain('display: none');
+ done();
+ });
+ });
+ });
+ });
+
+ it('should show children environments and a button to show all environments', (done) => {
+ setTimeout(() => {
+ // open folder
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ const folderInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: [environment],
+ }), { status: 200 }));
+ };
+
+ Vue.http.interceptors.push(folderInterceptor);
+
+ // wait for next async request
+ setTimeout(() => {
+ expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1);
+ expect(component.$el.querySelector('td.text-center > a.btn').textContent).toContain('Show all');
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor);
+ done();
+ });
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index 115d84b50f5..f617c4bdffe 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -1,38 +1,106 @@
import Store from '~/environments/stores/environments_store';
import { environmentsList, serverData } from './mock_data';
-(() => {
- describe('Store', () => {
- let store;
+describe('Store', () => {
+ let store;
- beforeEach(() => {
- store = new Store();
- });
+ beforeEach(() => {
+ store = new Store();
+ });
- it('should start with a blank state', () => {
- expect(store.state.environments.length).toEqual(0);
- expect(store.state.stoppedCounter).toEqual(0);
- expect(store.state.availableCounter).toEqual(0);
- expect(store.state.paginationInformation).toEqual({});
- });
+ it('should start with a blank state', () => {
+ expect(store.state.environments.length).toEqual(0);
+ expect(store.state.stoppedCounter).toEqual(0);
+ expect(store.state.availableCounter).toEqual(0);
+ expect(store.state.paginationInformation).toEqual({});
+ });
+ it('should store environments', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments.length).toEqual(serverData.length);
+ expect(store.state.environments[0]).toEqual(environmentsList[0]);
+ });
+
+ it('should store available count', () => {
+ store.storeAvailableCount(2);
+ expect(store.state.availableCounter).toEqual(2);
+ });
+
+ it('should store stopped count', () => {
+ store.storeStoppedCount(2);
+ expect(store.state.stoppedCounter).toEqual(2);
+ });
+
+ describe('store environments', () => {
it('should store environments', () => {
store.storeEnvironments(serverData);
expect(store.state.environments.length).toEqual(serverData.length);
- expect(store.state.environments[0]).toEqual(environmentsList[0]);
});
- it('should store available count', () => {
- store.storeAvailableCount(2);
- expect(store.state.availableCounter).toEqual(2);
+ it('should add folder keys when environment is a folder', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ };
+
+ store.storeEnvironments([environment]);
+ expect(store.state.environments[0].isFolder).toEqual(true);
+ expect(store.state.environments[0].folderName).toEqual('bar');
+ });
+
+ it('should extract content of `latest` key when provided', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ latest: {
+ last_deployment: {},
+ isStoppable: true,
+ },
+ };
+
+ store.storeEnvironments([environment]);
+ expect(store.state.environments[0].last_deployment).toEqual({});
+ expect(store.state.environments[0].isStoppable).toEqual(true);
});
- it('should store stopped count', () => {
- store.storeStoppedCount(2);
- expect(store.state.stoppedCounter).toEqual(2);
+ it('should store latest.name when the environment is not a folder', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[0].name).toEqual(serverData[0].latest.name);
});
- it('should store pagination information', () => {
+ it('should store root level name when environment is a folder', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[1].folderName).toEqual(serverData[1].name);
+ });
+ });
+
+ describe('toggleFolder', () => {
+ it('should toggle folder', () => {
+ store.storeEnvironments(serverData);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.state.environments[1].isOpen).toEqual(true);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.state.environments[1].isOpen).toEqual(false);
+ });
+ });
+
+ describe('setfolderContent', () => {
+ it('should store folder content', () => {
+ store.storeEnvironments(serverData);
+
+ store.setfolderContent(store.state.environments[1], serverData);
+
+ expect(store.state.environments[1].children.length).toEqual(serverData.length);
+ expect(store.state.environments[1].children[0].isChildren).toEqual(true);
+ });
+ });
+
+ describe('store pagination', () => {
+ it('should store normalized and integer pagination information', () => {
const pagination = {
'X-nExt-pAge': '2',
'X-page': '1',
@@ -55,4 +123,4 @@ import { environmentsList, serverData } from './mock_data';
expect(store.state.paginationInformation).toEqual(expectedResult);
});
});
-})();
+});
diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js
index 30861481cc5..15e11aa686b 100644
--- a/spec/javascripts/environments/mock_data.js
+++ b/spec/javascripts/environments/mock_data.js
@@ -84,3 +84,19 @@ export const environment = {
updated_at: '2017-01-31T10:53:46.894Z',
},
};
+
+export const folder = {
+ folderName: 'build',
+ size: 5,
+ id: 12,
+ name: 'build/update-README',
+ state: 'available',
+ external_url: null,
+ environment_type: 'build',
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
+};
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 4b08a02ec73..6675d26734e 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -69,6 +69,15 @@ describe Gitlab::Shell, lib: true do
expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
end
+ it 'handles multiple spaces in the key' do
+ io = spy(:io)
+ adder = described_class.new(io)
+
+ adder.add_key('key-42', "ssh-rsa foo")
+
+ expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
+ end
+
it 'raises an exception if the key contains a tab' do
expect do
described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 6c84a4c8b73..8f09266c3b3 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -40,6 +40,15 @@ describe Gitlab::OAuth::User, lib: true do
let(:provider) { 'twitter' }
describe 'signup' do
+ it 'marks user as having password_automatically_set' do
+ stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter'])
+
+ oauth_user.save
+
+ expect(gl_user).to be_persisted
+ expect(gl_user).to be_password_automatically_set
+ end
+
shared_examples 'to verify compliance with allow_single_sign_on' do
context 'provider is marked as external' do
it 'marks user as external' do
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index 66f68650f81..a111aec2f89 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -122,6 +122,32 @@ describe Users::CreateService, services: true do
end
end
+ context 'when password_automatically_set parameter is true' do
+ let(:params) do
+ {
+ name: 'John Doe',
+ username: 'jduser',
+ email: 'jd@example.com',
+ password: 'mydummypass',
+ password_automatically_set: true
+ }
+ end
+
+ it 'persists the given attributes' do
+ user = service.execute
+ user.reload
+
+ expect(user).to have_attributes(
+ name: params[:name],
+ username: params[:username],
+ email: params[:email],
+ password: params[:password],
+ created_by_id: admin_user.id,
+ password_automatically_set: params[:password_automatically_set]
+ )
+ end
+ end
+
context 'when skip_confirmation parameter is true' do
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass', skip_confirmation: true }