Commit c5612efc authored by Alper Akgun's avatar Alper Akgun Committed by Mike Greiling

Uniquify path slug creation for new group name

Suggests a unique path for a group with an ajax call
Checks manual path inputs and warns users if already exists
parent f7c12df5
import $ from 'jquery'; import $ from 'jquery';
import { slugify } from './lib/utils/text_utility'; import { slugify } from './lib/utils/text_utility';
import fetchGroupPathAvailability from '~/pages/groups/new/fetch_group_path_availability';
import flash from '~/flash';
import { __ } from '~/locale';
export default class Group { export default class Group {
constructor() { constructor() {
this.groupPath = $('#group_path'); this.groupPath = $('#group_path');
this.groupName = $('#group_name'); this.groupName = $('#group_name');
this.parentId = $('#group_parent_id');
this.updateHandler = this.update.bind(this); this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this); this.resetHandler = this.reset.bind(this);
this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this);
if (this.groupName.val() === '') { if (this.groupName.val() === '') {
this.groupName.on('keyup', this.updateHandler); this.groupName.on('keyup', this.updateHandler);
this.groupPath.on('keydown', this.resetHandler); this.groupPath.on('keydown', this.resetHandler);
if (!this.parentId.val()) {
this.groupName.on('blur', this.updateGroupPathSlugHandler);
}
} }
} }
...@@ -21,5 +29,21 @@ export default class Group { ...@@ -21,5 +29,21 @@ export default class Group {
reset() { reset() {
this.groupName.off('keyup', this.updateHandler); this.groupName.off('keyup', this.updateHandler);
this.groupPath.off('keydown', this.resetHandler); this.groupPath.off('keydown', this.resetHandler);
this.groupName.off('blur', this.checkPathHandler);
}
updateGroupPathSlug() {
const slug = this.groupPath.val() || slugify(this.groupName.val());
if (!slug) return;
fetchGroupPathAvailability(slug)
.then(({ data }) => data)
.then(data => {
if (data.exists && data.suggests.length > 0) {
const suggestedSlug = data.suggests[0];
this.groupPath.val(suggestedSlug);
}
})
.catch(() => flash(__('An error occurred while checking group path')));
} }
} }
import axios from '~/lib/utils/axios_utils';
const rootUrl = gon.relative_url_root;
export default function fetchGroupPathAvailability(groupPath) {
return axios.get(`${rootUrl}/users/${groupPath}/suggests`);
}
import InputValidator from '~/validators/input_validator';
import _ from 'underscore';
import fetchGroupPathAvailability from './fetch_group_path_availability';
import flash from '~/flash';
import { __ } from '~/locale';
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
const successInputClass = 'gl-field-success-outline';
const successMessageSelector = '.validation-success';
const pendingMessageSelector = '.validation-pending';
const unavailableMessageSelector = '.validation-error';
const suggestionsMessageSelector = '.gl-path-suggestions';
export default class GroupPathValidator extends InputValidator {
constructor(opts = {}) {
super();
const container = opts.container || '';
const validateElements = document.querySelectorAll(`${container} .js-validate-group-path`);
this.debounceValidateInput = _.debounce(inputDomElement => {
GroupPathValidator.validateGroupPathInput(inputDomElement);
}, debounceTimeoutDuration);
validateElements.forEach(element =>
element.addEventListener('input', this.eventHandler.bind(this)),
);
}
eventHandler(event) {
const inputDomElement = event.target;
GroupPathValidator.resetInputState(inputDomElement);
this.debounceValidateInput(inputDomElement);
}
static validateGroupPathInput(inputDomElement) {
const groupPath = inputDomElement.value;
if (inputDomElement.checkValidity() && groupPath.length > 0) {
GroupPathValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
fetchGroupPathAvailability(groupPath)
.then(({ data }) => data)
.then(data => {
GroupPathValidator.setInputState(inputDomElement, !data.exists);
GroupPathValidator.setMessageVisibility(inputDomElement, pendingMessageSelector, false);
GroupPathValidator.setMessageVisibility(
inputDomElement,
data.exists ? unavailableMessageSelector : successMessageSelector,
);
if (data.exists) {
GroupPathValidator.showSuggestions(inputDomElement, data.suggests);
}
})
.catch(() => flash(__('An error occurred while validating group path')));
}
}
static showSuggestions(inputDomElement, suggestions) {
const messageElement = inputDomElement.parentElement.parentElement.querySelector(
suggestionsMessageSelector,
);
const textSuggestions = suggestions && suggestions.length > 0 ? suggestions.join(', ') : 'none';
messageElement.textContent = textSuggestions;
}
static setMessageVisibility(inputDomElement, messageSelector, isVisible = true) {
const messageElement = inputDomElement.parentElement.parentElement.querySelector(
messageSelector,
);
messageElement.classList.toggle('hide', !isVisible);
}
static setInputState(inputDomElement, success = true) {
inputDomElement.classList.toggle(successInputClass, success);
inputDomElement.classList.toggle(invalidInputClass, !success);
}
static resetInputState(inputDomElement) {
GroupPathValidator.setMessageVisibility(inputDomElement, successMessageSelector, false);
GroupPathValidator.setMessageVisibility(inputDomElement, unavailableMessageSelector, false);
if (inputDomElement.checkValidity()) {
inputDomElement.classList.remove(successInputClass, invalidInputClass);
}
}
}
import $ from 'jquery';
import BindInOut from '~/behaviors/bind_in_out'; import BindInOut from '~/behaviors/bind_in_out';
import Group from '~/group'; import Group from '~/group';
import initAvatarPicker from '~/avatar_picker'; import initAvatarPicker from '~/avatar_picker';
import GroupPathValidator from './group_path_validator';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const parentId = $('#group_parent_id');
if (!parentId.val()) {
new GroupPathValidator(); // eslint-disable-line no-new
}
BindInOut.initAll(); BindInOut.initAll();
new Group(); // eslint-disable-line no-new new Group(); // eslint-disable-line no-new
initAvatarPicker(); initAvatarPicker();
......
...@@ -22,11 +22,16 @@ ...@@ -22,11 +22,16 @@
- if parent - if parent
%strong= parent.full_path + '/' %strong= parent.full_path + '/'
= f.hidden_field :parent_id = f.hidden_field :parent_id
= f.text_field :path, placeholder: 'my-awesome-group', class: 'form-control', = f.text_field :path, placeholder: 'my-awesome-group', class: 'form-control js-validate-group-path',
autofocus: local_assigns[:autofocus] || false, required: true, autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: _('Please choose a group URL with no special characters.'), title: _('Please choose a group URL with no special characters.'),
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}" "data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
%p.validation-error.gl-field-error.field-validation.hide
= _('Group path is already taken. Suggestions: ')
%span.gl-path-suggestions
%p.validation-success.gl-field-success.field-validation.hide= _('Group path is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking group path availability...')
- if @group.persisted? - if @group.persisted?
.alert.alert-warning.prepend-top-10 .alert.alert-warning.prepend-top-10
......
---
title: New group path uniqueness check
merge_request: 17394
author:
type: added
...@@ -1517,6 +1517,9 @@ msgstr "" ...@@ -1517,6 +1517,9 @@ msgstr ""
msgid "An error occurred when updating the issue weight" msgid "An error occurred when updating the issue weight"
msgstr "" msgstr ""
msgid "An error occurred while checking group path"
msgstr ""
msgid "An error occurred while deleting the approvers group" msgid "An error occurred while deleting the approvers group"
msgstr "" msgstr ""
...@@ -1679,6 +1682,9 @@ msgstr "" ...@@ -1679,6 +1682,9 @@ msgstr ""
msgid "An error occurred while updating the comment" msgid "An error occurred while updating the comment"
msgstr "" msgstr ""
msgid "An error occurred while validating group path"
msgstr ""
msgid "An error occurred while validating username" msgid "An error occurred while validating username"
msgstr "" msgstr ""
...@@ -3059,6 +3065,9 @@ msgstr "" ...@@ -3059,6 +3065,9 @@ msgstr ""
msgid "Checking branch availability..." msgid "Checking branch availability..."
msgstr "" msgstr ""
msgid "Checking group path availability..."
msgstr ""
msgid "Checking username availability..." msgid "Checking username availability..."
msgstr "" msgstr ""
...@@ -8359,6 +8368,12 @@ msgstr "" ...@@ -8359,6 +8368,12 @@ msgstr ""
msgid "Group overview content" msgid "Group overview content"
msgstr "" msgstr ""
msgid "Group path is already taken. Suggestions: "
msgstr ""
msgid "Group path is available."
msgstr ""
msgid "Group pipeline minutes were successfully reset." msgid "Group pipeline minutes were successfully reset."
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment