Commit c35b2264 authored by Mike Greiling's avatar Mike Greiling

Merge branch...

Merge branch '32458-update-group-creation-url-so-it-is-always-unique-and-does-not-generate-an-error' into 'master'

Uniquify path slug creation from group name

Closes #32458

See merge request gitlab-org/gitlab!17394
parents f7c12df5 c5612efc
import $ from 'jquery';
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 {
constructor() {
this.groupPath = $('#group_path');
this.groupName = $('#group_name');
this.parentId = $('#group_parent_id');
this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this);
this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this);
if (this.groupName.val() === '') {
this.groupName.on('keyup', this.updateHandler);
this.groupPath.on('keydown', this.resetHandler);
if (!this.parentId.val()) {
this.groupName.on('blur', this.updateGroupPathSlugHandler);
}
}
}
......@@ -21,5 +29,21 @@ export default class Group {
reset() {
this.groupName.off('keyup', this.updateHandler);
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 Group from '~/group';
import initAvatarPicker from '~/avatar_picker';
import GroupPathValidator from './group_path_validator';
document.addEventListener('DOMContentLoaded', () => {
const parentId = $('#group_parent_id');
if (!parentId.val()) {
new GroupPathValidator(); // eslint-disable-line no-new
}
BindInOut.initAll();
new Group(); // eslint-disable-line no-new
initAvatarPicker();
......
......@@ -22,11 +22,16 @@
- if parent
%strong= parent.full_path + '/'
= 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,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: _('Please choose a group URL with no special characters.'),
"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?
.alert.alert-warning.prepend-top-10
......
---
title: New group path uniqueness check
merge_request: 17394
author:
type: added
......@@ -1517,6 +1517,9 @@ msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
msgid "An error occurred while checking group path"
msgstr ""
msgid "An error occurred while deleting the approvers group"
msgstr ""
......@@ -1679,6 +1682,9 @@ msgstr ""
msgid "An error occurred while updating the comment"
msgstr ""
msgid "An error occurred while validating group path"
msgstr ""
msgid "An error occurred while validating username"
msgstr ""
......@@ -3059,6 +3065,9 @@ msgstr ""
msgid "Checking branch availability..."
msgstr ""
msgid "Checking group path availability..."
msgstr ""
msgid "Checking username availability..."
msgstr ""
......@@ -8359,6 +8368,12 @@ msgstr ""
msgid "Group overview content"
msgstr ""
msgid "Group path is already taken. Suggestions: "
msgstr ""
msgid "Group path is available."
msgstr ""
msgid "Group pipeline minutes were successfully reset."
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