diff --git a/portiaui/app/components/buffered-input.js b/portiaui/app/components/buffered-input.js index 34068ee81..0a81eda62 100644 --- a/portiaui/app/components/buffered-input.js +++ b/portiaui/app/components/buffered-input.js @@ -78,6 +78,9 @@ export default Ember.Component.extend({ endEditing(reason) { const value = this.get('viewValue'); + if (typeof this.attrs.validate === 'function' && !this.attrs.validate(value)) { + return Ember.run.next(this, this.setInputFocus); + } this.setProperties({ focused: false, value: value, diff --git a/portiaui/app/components/list-item-editable.js b/portiaui/app/components/list-item-editable.js index 78dbe1d6d..9d66026b6 100644 --- a/portiaui/app/components/list-item-editable.js +++ b/portiaui/app/components/list-item-editable.js @@ -6,6 +6,7 @@ export default Ember.Component.extend({ editing: false, onChange: null, + validate: null, spellcheck: true, value: null, diff --git a/portiaui/app/components/project-structure-listing.js b/portiaui/app/components/project-structure-listing.js index 4ab289ee6..ba0f45939 100644 --- a/portiaui/app/components/project-structure-listing.js +++ b/portiaui/app/components/project-structure-listing.js @@ -5,6 +5,8 @@ export default Ember.Component.extend({ browser: Ember.inject.service(), dispatcher: Ember.inject.service(), uiState: Ember.inject.service(), + notificationManager: Ember.inject.service(), + routing: Ember.inject.service('-routing'), tagName: '', @@ -43,8 +45,32 @@ export default Ember.Component.extend({ this.get('dispatcher').removeSpider(spider); }, - saveSpider(spider) { - spider.save(); + validateSpiderName(name) { + const nm = this.get('notificationManager'); + if(!/^[a-zA-Z0-9][a-zA-Z0-9_\.-]*$/.test(name)) { + nm.showWarningNotification(`Invalid spider name. + Only letters, numbers, underscores, dashes and dots are allowed.`); + return false; + } + return true; + }, + + loadSpider(spider) { + // Using link-to was problematic because it interpreted an enter in + // the input field as intention to follow the link (rather than + // saving the value) + this.get('routing').transitionTo('projects.project.spider', [spider], {}, true); + }, + + saveSpiderName(spider) { + // HACK: Renaming the spider will change it's ID, changing the ID + // of a record is not supported in Ember data, so we return a new + // record from the server and mark the original as deleted. + spider.save().then(() => { + if(spider.get('name') === '_deleted') { + spider.unloadRecord(); + } + }); } } }); diff --git a/portiaui/app/templates/components/list-item-editable.hbs b/portiaui/app/templates/components/list-item-editable.hbs index 519080750..27600419a 100644 --- a/portiaui/app/templates/components/list-item-editable.hbs +++ b/portiaui/app/templates/components/list-item-editable.hbs @@ -1,5 +1,5 @@ {{#if editing}} - {{buffered-input class="input-list-item" value=(mut value) focused=editing autoSelect=true spellcheck=spellcheck onChange=onChange}} + {{buffered-input class="input-list-item" value=(mut value) focused=editing autoSelect=true spellcheck=spellcheck onChange=onChange validate=validate}} {{else}} {{value}} {{icon-button icon='edit' action=(action 'startEditing') bubbles=false}} diff --git a/portiaui/app/templates/components/project-structure-listing.hbs b/portiaui/app/templates/components/project-structure-listing.hbs index 0dad69d15..0cae7dab9 100644 --- a/portiaui/app/templates/components/project-structure-listing.hbs +++ b/portiaui/app/templates/components/project-structure-listing.hbs @@ -12,7 +12,11 @@ {{#tree-list-item hide=(and currentSpider (not-eq spider currentSpider)) as |options|}} {{#link-to 'projects.project.spider' spider}} {{list-item-icon icon='spider'}} - {{list-item-editable value=(mut spider.name) editing=(mut spider.new) onChange=(action 'saveSpider' spider)}} + {{#if currentSpider}} + {{#list-item-text}}{{spider.name}}{{/list-item-text}} + {{else}} + {{list-item-editable value=(mut spider.name) onChange=(action 'saveSpiderName' spider) validate=(action 'validateSpiderName')}} + {{/if}} {{#animation-container class="icon" setWidth=false setHeight=false}} {{list-item-icon icon='remove' action=(action 'removeSpider' spider) bubbles=false}} {{/animation-container}} diff --git a/slyd/slyd/gitstorage/projectspec.py b/slyd/slyd/gitstorage/projectspec.py index 2b8ac71b3..3152e7e4f 100644 --- a/slyd/slyd/gitstorage/projectspec.py +++ b/slyd/slyd/gitstorage/projectspec.py @@ -1,4 +1,5 @@ import json +import re from os.path import join from .repoman import Repoman @@ -23,6 +24,9 @@ def _rfile_name(self, *resources): def rename_spider(self, from_name, to_name): if to_name == from_name: return + if not re.match('^[a-zA-Z0-9][a-zA-Z0-9_\.-]*$', to_name): + raise BadRequest('Bad Request', 'Invalid spider name') + if to_name in self.list_spiders(): raise BadRequest('Bad Request', 'A spider already exists with the ' 'name, "%s".' % to_name) diff --git a/slyd/slyd/resources/spiders.py b/slyd/slyd/resources/spiders.py index f182257b7..905f900da 100644 --- a/slyd/slyd/resources/spiders.py +++ b/slyd/slyd/resources/spiders.py @@ -38,7 +38,9 @@ def update_spider(manager, spider_id, attributes): spider = manager.spider_json(spider_id) spider.update(attributes) get_schema_validator('spider').validate(spider) - if spider.get('name') and spider_id != spider['name']: + rename = spider.get('name') and spider_id != spider['name'] + original_id = spider_id + if rename: manager.rename_spider(spider_id, spider['name'].encode('utf-8')) spider_id = spider['name'] spider['id'] = spider_id @@ -46,7 +48,15 @@ def update_spider(manager, spider_id, attributes): manager.savejson(spider, ['spiders', spider_id.encode('utf-8')]) spider['samples'] = [{'id': name} for name in spider['template_names']] context = ctx(manager, spider_id=spider_id) - return SpiderSchema(context=context).dump(spider).data + response = SpiderSchema(context=context).dump(spider).data + if rename: + # HACK: Ember doesn't allow changing IDs, so return a "new" spider + # and mark the original as deleted. + new_spider = SpiderSchema(context=context).dump(spider).data + response['included'] = [new_spider['data']] + response['data']['attributes']['name'] = "_deleted" + response['data']['id'] = original_id + return response def delete_spider(manager, spider_id, attributes=None):