Merge branch '12479-wb-structured-vocabulary'
authorLucas Di Pentima <ldipentima@veritasgenetics.com>
Tue, 16 Jan 2018 21:36:27 +0000 (18:36 -0300)
committerLucas Di Pentima <ldipentima@veritasgenetics.com>
Tue, 16 Jan 2018 21:36:27 +0000 (18:36 -0300)
Closes #12479

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <ldipentima@veritasgenetics.com>

15 files changed:
apps/workbench/app/assets/javascripts/application.js
apps/workbench/app/assets/javascripts/components/edit_tags.js [new file with mode: 0644]
apps/workbench/app/assets/javascripts/edit_collection_tags.js [deleted file]
apps/workbench/app/assets/javascripts/mithril_mount.js
apps/workbench/app/assets/javascripts/models/session_db.js
apps/workbench/app/assets/stylesheets/application.css.scss
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/views/collections/_show_tag_rows.html.erb [deleted file]
apps/workbench/app/views/collections/_show_tags.html.erb
apps/workbench/app/views/collections/save_tags.js.erb [deleted file]
apps/workbench/app/views/collections/tags.js.erb [deleted file]
apps/workbench/npm_packages
apps/workbench/public/vocabulary-example.json [new file with mode: 0644]
apps/workbench/test/controllers/collections_controller_test.rb
apps/workbench/test/integration/collections_test.rb

index bba2f9dcc044b8b20b42e12cac8c62af5c4846f4..b90081f46fe9d5ccdec360165e6bc2528817d7b2 100644 (file)
@@ -33,6 +33,7 @@
 //= require jquery.number.min
 //= require npm-dependencies
 //= require mithril/stream/stream
+//= require awesomplete
 //= require_tree .
 
 Es6ObjectAssign.polyfill()
diff --git a/apps/workbench/app/assets/javascripts/components/edit_tags.js b/apps/workbench/app/assets/javascripts/components/edit_tags.js
new file mode 100644 (file)
index 0000000..ac4d2df
--- /dev/null
@@ -0,0 +1,265 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.SimpleInput = {
+    view: function(vnode) {
+        return m("input.form-control", {
+            style: {
+                width: '100%',
+            },
+            type: 'text',
+            placeholder: 'Add ' + vnode.attrs.placeholder,
+            value: vnode.attrs.value,
+            onchange: function() {
+                if (this.value != '') {
+                    vnode.attrs.value(this.value)
+                }
+            },
+        }, vnode.attrs.value)
+    },
+}
+
+window.SelectOrAutocomplete = {
+    view: function(vnode) {
+        return m("input.form-control", {
+            style: {
+                width: '100%'
+            },
+            type: 'text',
+            value: vnode.attrs.value,
+            placeholder: (vnode.attrs.create ? 'Add or select ': 'Select ') + vnode.attrs.placeholder,
+        }, vnode.attrs.value)
+    },
+    oncreate: function(vnode) {
+        vnode.state.awesomplete = new Awesomplete(vnode.dom, {
+            list: vnode.attrs.options,
+            minChars: 0,
+            maxItems: 1000000,
+            autoFirst: true,
+            sort: false,
+        })
+        vnode.state.create = vnode.attrs.create
+        vnode.state.options = vnode.attrs.options
+        // Option is selected from the list.
+        $(vnode.dom).on('awesomplete-selectcomplete', function(event) {
+            vnode.attrs.value(this.value)
+        })
+        $(vnode.dom).on('change', function(event) {
+            if (!vnode.state.create && !(this.value in vnode.state.options)) {
+                this.value = vnode.attrs.value()
+            } else {
+                if (vnode.attrs.value() !== this.value) {
+                    vnode.attrs.value(this.value)
+                }
+            }
+        })
+        $(vnode.dom).on('focusin', function(event) {
+            if (this.value === '') {
+                vnode.state.awesomplete.evaluate()
+                vnode.state.awesomplete.open()
+            }
+        })
+    },
+    onupdate: function(vnode) {
+        vnode.state.awesomplete.list = vnode.attrs.options
+        vnode.state.create = vnode.attrs.create
+        vnode.state.options = vnode.attrs.options
+    },
+}
+
+window.TagEditorRow = {
+    view: function(vnode) {
+        var nameOpts = Object.keys(vnode.attrs.vocabulary().tags)
+        var valueOpts = []
+        var inputComponent = SelectOrAutocomplete
+        if (nameOpts.length === 0) {
+            // If there's not vocabulary defined, switch to a simple input field
+            inputComponent = SimpleInput
+        } else {
+            // Name options list
+            if (vnode.attrs.name() != '' && !(vnode.attrs.name() in vnode.attrs.vocabulary().tags)) {
+                nameOpts.push(vnode.attrs.name())
+            }
+            // Value options list
+            if (vnode.attrs.name() in vnode.attrs.vocabulary().tags &&
+                'values' in vnode.attrs.vocabulary().tags[vnode.attrs.name()]) {
+                    valueOpts = vnode.attrs.vocabulary().tags[vnode.attrs.name()].values
+            }
+        }
+        return m("tr", [
+            // Erase tag
+            m("td", [
+                vnode.attrs.editMode &&
+                m('div.text-center', m('a.btn.btn-default.btn-sm', {
+                    style: {
+                        align: 'center'
+                    },
+                    onclick: function(e) { vnode.attrs.removeTag() }
+                }, m('i.fa.fa-fw.fa-trash-o')))
+            ]),
+            // Tag key
+            m("td", [
+                vnode.attrs.editMode ?
+                m("div", {key: 'key'}, [
+                    m(inputComponent, {
+                        options: nameOpts,
+                        value: vnode.attrs.name,
+                        // Allow any tag name unless "strict" is set to true.
+                        create: !vnode.attrs.vocabulary().strict,
+                        placeholder: 'key',
+                    })
+                ])
+                : vnode.attrs.name
+            ]),
+            // Tag value
+            m("td", [
+                vnode.attrs.editMode ?
+                m("div", {key: 'value'}, [
+                    m(inputComponent, {
+                        options: valueOpts,
+                        value: vnode.attrs.value,
+                        placeholder: 'value',
+                        // Allow any value on tags not listed on the vocabulary.
+                        // Allow any value on tags without values, or the ones
+                        // that aren't explicitly declared to be strict.
+                        create: !(vnode.attrs.name() in vnode.attrs.vocabulary().tags)
+                            || !vnode.attrs.vocabulary().tags[vnode.attrs.name()].values
+                            || vnode.attrs.vocabulary().tags[vnode.attrs.name()].values.length === 0
+                            || !vnode.attrs.vocabulary().tags[vnode.attrs.name()].strict,
+                    })
+                ])
+                : vnode.attrs.value
+            ])
+        ])
+    }
+}
+
+window.TagEditorTable = {
+    view: function(vnode) {
+        return m("table.table.table-condensed.table-justforlayout", [
+            m("colgroup", [
+                m("col", {width:"5%"}),
+                m("col", {width:"25%"}),
+                m("col", {width:"70%"}),
+            ]),
+            m("thead", [
+                m("tr", [
+                    m("th"),
+                    m("th", "Key"),
+                    m("th", "Value"),
+                ])
+            ]),
+            m("tbody", [
+                vnode.attrs.tags.length > 0
+                ? vnode.attrs.tags.map(function(tag, idx) {
+                    return m(TagEditorRow, {
+                        key: tag.rowKey,
+                        removeTag: function() {
+                            vnode.attrs.tags.splice(idx, 1)
+                            vnode.attrs.dirty(true)
+                        },
+                        editMode: vnode.attrs.editMode,
+                        name: tag.name,
+                        value: tag.value,
+                        vocabulary: vnode.attrs.vocabulary
+                    })
+                })
+                : m("tr", m("td[colspan=3]", m("center", "Loading tags...")))
+            ]),
+        ])
+    }
+}
+
+var uniqueID = 1
+
+window.TagEditorApp = {
+    appendTag: function(vnode, name, value) {
+        var tag = {name: m.stream(name), value: m.stream(value), rowKey: uniqueID++}
+        vnode.state.tags.push(tag)
+        // Set dirty flag when any of name/value changes to non empty string
+        tag.name.map(function() { vnode.state.dirty(true) })
+        tag.value.map(function() { vnode.state.dirty(true) })
+        tag.name.map(m.redraw)
+    },
+    oninit: function(vnode) {
+        vnode.state.sessionDB = new SessionDB()
+        // Get vocabulary
+        vnode.state.vocabulary = m.stream({"strict":false, "tags":{}})
+        var vocabularyTimestamp = parseInt(Date.now() / 300000) // Bust cache every 5 minutes
+        m.request('/vocabulary.json?v=' + vocabularyTimestamp).then(vnode.state.vocabulary)
+        vnode.state.editMode = vnode.attrs.targetEditable
+        vnode.state.tags = []
+        vnode.state.dirty = m.stream(false)
+        vnode.state.dirty.map(m.redraw)
+        vnode.state.objPath = '/arvados/v1/'+vnode.attrs.targetController+'/'+vnode.attrs.targetUuid
+        // Get tags
+        vnode.state.sessionDB.request(
+            vnode.state.sessionDB.loadLocal(),
+            '/arvados/v1/'+vnode.attrs.targetController,
+            {
+                data: {
+                    filters: JSON.stringify([['uuid', '=', vnode.attrs.targetUuid]]),
+                    select: JSON.stringify(['properties'])
+                },
+            }).then(function(obj) {
+                if (obj.items.length == 1) {
+                    o = obj.items[0]
+                    Object.keys(o.properties).forEach(function(k) {
+                        vnode.state.appendTag(vnode, k, o.properties[k])
+                    })
+                    if (vnode.state.editMode) {
+                        vnode.state.appendTag(vnode, '', '')
+                    }
+                    // Data synced with server, so dirty state should be false
+                    vnode.state.dirty(false)
+                    // Add new tag row when the last one is completed
+                    vnode.state.dirty.map(function() {
+                        if (!vnode.state.editMode) { return }
+                        lastTag = vnode.state.tags.slice(-1).pop()
+                        if (lastTag === undefined || (lastTag.name() !== '' || lastTag.value() !== '')) {
+                            vnode.state.appendTag(vnode, '', '')
+                        }
+                    })
+                }
+            }
+        )
+    },
+    view: function(vnode) {
+        return [
+            vnode.state.editMode &&
+            m("div.pull-left", [
+                m("a.btn.btn-primary.btn-sm"+(vnode.state.dirty() ? '' : '.disabled'), {
+                    style: {
+                        margin: '10px 0px'
+                    },
+                    onclick: function(e) {
+                        var tags = {}
+                        vnode.state.tags.forEach(function(t) {
+                            // Only ignore tags with empty key
+                            if (t.name() != '') {
+                                tags[t.name()] = t.value()
+                            }
+                        })
+                        vnode.state.sessionDB.request(
+                            vnode.state.sessionDB.loadLocal(),
+                            vnode.state.objPath, {
+                                method: "PUT",
+                                data: {properties: JSON.stringify(tags)}
+                            }
+                        ).then(function(v) {
+                            vnode.state.dirty(false)
+                        })
+                    }
+                }, vnode.state.dirty() ? ' Save changes ' : ' Saved ')
+            ]),
+            // Tags table
+            m(TagEditorTable, {
+                editMode: vnode.state.editMode,
+                tags: vnode.state.tags,
+                vocabulary: vnode.state.vocabulary,
+                dirty: vnode.state.dirty
+            })
+        ]
+    },
+}
diff --git a/apps/workbench/app/assets/javascripts/edit_collection_tags.js b/apps/workbench/app/assets/javascripts/edit_collection_tags.js
deleted file mode 100644 (file)
index e1c1515..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-jQuery(function($){
-  $(document).
-    on('click', '.collection-tag-save, .collection-tag-cancel', function(event) {
-        $('.edit-collection-tags').removeClass('disabled');
-        $('#edit-collection-tags').attr("title", "Edit tags");
-        $('.collection-tag-add').addClass('hide');
-        $('.collection-tag-remove').addClass('hide');
-        $('.collection-tag-save').addClass('hide');
-        $('.collection-tag-cancel').addClass('hide');
-        $('.collection-tag-field').prop("contenteditable", false);
-    }).
-    on('click', '.edit-collection-tags', function(event) {
-        $('.edit-collection-tags').addClass('disabled');
-        $('#edit-collection-tags').attr("title", "");
-        $('.collection-tag-add').removeClass('hide');
-        $('.collection-tag-remove').removeClass('hide');
-        $('.collection-tag-save').removeClass('hide');
-        $('.collection-tag-cancel').removeClass('hide');
-        $('.collection-tag-field').prop("contenteditable", true);
-        $('div').remove('.collection-tags-status-label');
-    }).
-    on('click', '.collection-tag-save', function(event) {
-      var tag_data = {};
-      var has_tags = false;
-
-      var $tags = $(".collection-tags-table");
-      $tags.find('tr').each(function (i, el) {
-        var $tds = $(this).find('td');
-        var $key = $tds.eq(1).text();
-        if ($key && $key.trim().length > 0) {
-          has_tags = true;
-          tag_data[$key.trim()] = $tds.eq(2).text().trim();
-        }
-      });
-
-      var to_send;
-      if (has_tags == false) {
-        to_send = {tag_data: "empty"}
-      } else {
-        to_send = {tag_data: tag_data}
-      }
-
-      $.ajax($(location).attr('pathname')+'/save_tags', {
-          type: 'POST',
-          data: to_send
-      }).success(function(data, status, jqxhr) {
-        $('.collection-tags-status').append('<div class="collection-tags-status-label alert alert-success"><p class="contain-align-left">Saved successfully.</p></div>');
-      }).fail(function(jqxhr, status, error) {
-        $('.collection-tags-status').append('<div class="collection-tags-status-label alert alert-danger"><p class="contain-align-left">We are sorry. There was an error saving tags. Please try again.</p></div>');
-      });
-    }).
-    on('click', '.collection-tag-cancel', function(event) {
-      $.ajax($(location).attr('pathname')+'/tags', {
-          type: 'GET'
-      });
-    }).
-    on('click', '.collection-tag-remove', function(event) {
-      $(this).parents('tr').detach();
-    }).
-    on('click', '.collection-tag-add', function(event) {
-      var $collection_tags = $(this).closest('.collection-tags-container');
-      var $clone = $collection_tags.find('tr.hide').clone(true).removeClass('hide');
-      $collection_tags.find('table').append($clone);
-    }).
-    on('keypress', '.collection-tag-field', function(event){
-      return event.which != 13;
-    });
-});
index f4689b51d7ebfc265476dfa3e0b748ac33b62b94..7995ffea6ab8a69dd97030bef774de3599dfe5e6 100644 (file)
@@ -4,6 +4,7 @@
 
 $(document).on('ready arv:pane:loaded', function() {
     $('[data-mount-mithril]').each(function() {
-        m.mount(this, window[$(this).data('mount-mithril')])
+        var data = $(this).data()
+        m.mount(this, {view: function () {return m(window[data.mountMithril], data)}})
     })
 })
index ad9ad1878417370dfd75294e9bd9cecbe25880d1..a43cd79545b66ff47dda07f3097b3c8803a47262 100644 (file)
@@ -28,6 +28,17 @@ window.SessionDB = function() {
             })
             return sessions
         },
+        loadLocal: function() {
+            var sessions = db.loadActive()
+            var s = false
+            Object.values(sessions).forEach(function(session) {
+                if (session.isFromRails) {
+                    s = session
+                    return
+                }
+            })
+            return s
+        },
         save: function(k, v) {
             var sessions = db.loadAll()
             sessions[k] = v
@@ -134,10 +145,11 @@ window.SessionDB = function() {
             // Guess workbench.{apihostport} is a Workbench... unless
             // the host part of apihostport is an IPv4 or [IPv6]
             // address.
-            if (!session.baseURL.match('://(\\[|\\d+\\.\\d+\\.\\d+\\.\\d+[:/])'))
+            if (!session.baseURL.match('://(\\[|\\d+\\.\\d+\\.\\d+\\.\\d+[:/])')) {
                 var wbUrl = session.baseURL.replace('://', '://workbench.')
                 // Remove the trailing slash, if it's there.
                 return wbUrl.slice(-1) == '/' ? wbUrl.slice(0, -1) : wbUrl
+            }
             return null
         },
         // Return a m.stream that will get fulfilled with the
index 32d80255d9fd831d2ea7f3e55ec163e53f154f06..4112b22b8ce60b0c6beb2171c7f9d834ba09bbc9 100644 (file)
@@ -16,6 +16,7 @@
  *= require bootstrap
  *= require bootstrap3-editable/bootstrap-editable
  *= require morris
+ *= require awesomplete
  *= require_tree .
  */
 
@@ -320,3 +321,13 @@ ul.nav.nav-tabs {
 {
     width: 98%!important;
 }
+
+/* Needed for awesomplete to play nice with bootstrap */
+div.awesomplete {
+    display: block;
+}
+/* Makes awesomplete listings to be scrollable */
+.awesomplete > ul {
+    max-height: 410px;
+    overflow-y: auto;
+}
\ No newline at end of file
index 5fcb2dc569ff6b2446c602dc26de61a069155ba2..0a7f22b95789edc163198fbf32ab55045317f298 100644 (file)
@@ -300,30 +300,6 @@ class CollectionsController < ApplicationController
     end
   end
 
-  def tags
-    render
-  end
-
-  def save_tags
-    tags_param = params['tag_data']
-    if tags_param
-      if tags_param.is_a?(String) && tags_param == "empty"
-        tags = {}
-      else
-        tags = tags_param
-      end
-    end
-
-    if tags
-      if @object.update_attributes properties: tags
-        @saved_tags = true
-        render
-      else
-        self.render_error status: 422
-      end
-    end
-  end
-
   protected
 
   def find_usable_token(token_list)
diff --git a/apps/workbench/app/views/collections/_show_tag_rows.html.erb b/apps/workbench/app/views/collections/_show_tag_rows.html.erb
deleted file mode 100644 (file)
index eb57913..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<%# Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: AGPL-3.0 %>
-
-<%
-  tags = object.properties
-%>
-      <% if tags.andand.is_a?(Hash) %>
-        <% tags.each do |k, v| %>
-          <tr class="collection-tag-<%=k%>">
-            <td>
-              <% if object.editable? %>
-                <i class="glyphicon glyphicon-remove collection-tag-remove hide" style="cursor: pointer;"></i>
-              <% end %>
-            </td>
-            <td class="collection-tag-field collection-tag-field-key">
-              <%= k %>
-            </td>
-            <td class="collection-tag-field collection-tag-field-value">
-              <%= v %>
-            </td>
-          </tr>
-        <% end %>
-      <% end %>
-
-      <% if @object.editable? %>
-        <!-- A hidden row to add new tag -->
-        <tr class="collection-tag-hidden hide">
-          <td>
-            <i class="glyphicon glyphicon-remove collection-tag-remove hide" style="cursor: pointer"></i>
-          </td>
-          <td class="collection-tag-field collection-tag-field-key"></td>
-          <td class="collection-tag-field collection-tag-field-value"></td>
-        </tr>
-      <% end %>
index afab5266e9c2e46dbe5c84425ba9f42b7f41f544..3e0460a2398134955e936df58cc58622f0aa6385 100644 (file)
@@ -2,51 +2,11 @@
 
 SPDX-License-Identifier: AGPL-3.0 %>
 
-<%
-  object = @object unless object
-%>
+  <div class="arv-log-refresh-control"
+    data-load-throttle="86486400000" <%# 1001 nights (in milliseconds) %>
+    ></div>
 
   <div class="collection-tags-container" style="padding-left:2em;padding-right:2em;">
-    <% if object.editable? %>
-      <p title="Edit tags" id="edit-collection-tags">
-        <a class="btn btn-primary edit-collection-tags">Edit</a>
-      </p>
-    <% end %>
-
-    <table class="table table-condensed table-fixedlayout collection-tags-table" border="1">
-      <colgroup>
-        <col width="5%" />
-        <col width="25%" />
-        <col width="70%" />
-      </colgroup>
-
-      <thead>
-        <tr>
-          <th></th>
-          <th>Key</th>
-          <th>Value</th>
-        </tr>
-      </thead>
-
-      <tbody class="collection-tag-rows">
-        <%= render partial: 'show_tag_rows', locals: {object: object} %>
-      </tbody>
-    </table>
-    <div>
-      <% if object.editable? %>
-        <div class="pull-left">
-          <a class="btn btn-primary btn-sm collection-tag-add hide"><i class="glyphicon glyphicon-plus"></i> Add new tag </a>
-        </div>
-        <div class="pull-right">
-          <%= link_to(save_tags_collection_path, {class: 'btn btn-sm btn-primary collection-tag-save hide', :remote => true, method: 'post', return_to: request.url}) do %>
-            Save
-          <% end %>
-          <%= link_to(tags_collection_path, {class: 'btn btn-sm btn-primary collection-tag-cancel hide', :remote => true, method: 'get', return_to: request.url}) do %>
-            Cancel
-          <% end %>
-        </div>
-
-        <div><div class="collection-tags-status"/></div></div>
-      <% end %>
-    </div>
+    <div data-mount-mithril="TagEditorApp" data-target-controller="<%= controller_name %>" data-target-uuid="<%= @object.uuid %>" data-target-editable="<%= @object.editable? %>"></div>
   </div>
\ No newline at end of file
diff --git a/apps/workbench/app/views/collections/save_tags.js.erb b/apps/workbench/app/views/collections/save_tags.js.erb
deleted file mode 100644 (file)
index 073db7d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<%# Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: AGPL-3.0 %>
-
-<% if @saved_tags %>
-$(".collection-tag-rows").html("<%= escape_javascript(render partial: 'show_tag_rows', locals: {object: @object}) %>");
-<% end %>
diff --git a/apps/workbench/app/views/collections/tags.js.erb b/apps/workbench/app/views/collections/tags.js.erb
deleted file mode 100644 (file)
index e2154d6..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%# Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: AGPL-3.0 %>
-
-$(".collection-tag-rows").html("<%= escape_javascript(render partial: 'show_tag_rows', locals: {object: @object}) %>");
index 2d57573dfd53dc3ceb5b90dccfe3bef363081ecc..c126b559fb138387b27424773cd931053f6bcc86 100644 (file)
@@ -6,6 +6,8 @@
 
 # Browserify is required.
 npm 'browserify', require: false
+npm 'jquery'
+npm 'awesomplete'
 
 npm 'mithril'
 npm 'es6-object-assign'
diff --git a/apps/workbench/public/vocabulary-example.json b/apps/workbench/public/vocabulary-example.json
new file mode 100644 (file)
index 0000000..b227dc2
--- /dev/null
@@ -0,0 +1,32 @@
+{
+    "strict": false,
+    "tags": {
+        "fruit": {
+            "values": ["pineapple", "tomato", "orange", "banana", "advocado", "lemon", "apple", "peach", "strawberry"],
+            "strict": true
+        },
+        "animal": {
+            "values": ["human", "dog", "elephant", "eagle"],
+            "strict": false
+        },
+        "color": {
+            "values": ["yellow", "red", "magenta", "green"],
+            "strict": false
+        },
+        "text": {},
+        "category": {
+            "values": ["experimental", "development", "production"]
+        },
+        "comments": {},
+        "importance": {
+            "values": ["critical", "important", "low priority"]
+        },
+        "size": {
+            "values": ["x-small", "small", "medium", "large", "x-large"]
+        },
+        "country": {
+            "values": ["Afghanistan","Ă…land Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"],
+            "strict": true
+        }
+    }
+}
\ No newline at end of file
index 773a4f45714b515d664ec290ecb61e32bcca5695..abe7f6af453f1c72070251ba90a889407469097a 100644 (file)
@@ -728,64 +728,4 @@ class CollectionsControllerTest < ActionController::TestCase
     assert_response 422
     assert_includes json_response['errors'], 'Duplicate file path'
   end
-
-  [
-    [:active, true],
-    [:spectator, false],
-  ].each do |user, editable|
-    test "tags tab #{editable ? 'shows' : 'does not show'} edit button to #{user}" do
-      use_token user
-
-      get :tags, {
-        id: api_fixture('collections')['collection_with_tags_owned_by_active']['uuid'],
-        format: :js,
-      }, session_for(user)
-
-      assert_response :success
-
-      found = 0
-      response.body.scan /<i[^>]+>/ do |remove_icon|
-        remove_icon.scan(/\ collection-tag-remove(.*?)\"/).each do |i,|
-          found += 1
-        end
-      end
-
-      if editable
-        assert_equal(3, found)  # two from the tags + 1 from the hidden "add tag" row
-      else
-        assert_equal(0, found)
-      end
-    end
-  end
-
-  test "save_tags and verify that 'other' properties are retained" do
-    use_token :active
-
-    collection = api_fixture('collections')['collection_with_tags_owned_by_active']
-
-    new_tags = {"new_tag1" => "new_tag1_value",
-                "new_tag2" => "new_tag2_value"}
-
-    post :save_tags, {
-      id: collection['uuid'],
-      tag_data: new_tags,
-      format: :js,
-    }, session_for(:active)
-
-    assert_response :success
-    assert_equal true, response.body.include?("new_tag1")
-    assert_equal true, response.body.include?("new_tag1_value")
-    assert_equal true, response.body.include?("new_tag2")
-    assert_equal true, response.body.include?("new_tag2_value")
-    assert_equal false, response.body.include?("existing tag 1")
-    assert_equal false, response.body.include?("value for existing tag 1")
-
-    updated_tags = Collection.find(collection['uuid']).properties
-    assert_equal true, updated_tags.keys.include?(:'new_tag1')
-    assert_equal new_tags['new_tag1'], updated_tags[:'new_tag1']
-    assert_equal true, updated_tags.keys.include?(:'new_tag2')
-    assert_equal new_tags['new_tag2'], updated_tags[:'new_tag2']
-    assert_equal false, updated_tags.keys.include?(:'existing tag 1')
-    assert_equal false, updated_tags.keys.include?(:'existing tag 2')
-  end
 end
index 71cfe38abfda32b2d5b5ce943ecdbf26f46ff52b..443130a4a92c60cd6a46a4f4ca749d9712a5a7f9 100644 (file)
@@ -434,88 +434,4 @@ class CollectionsTest < ActionDispatch::IntegrationTest
     first('.lock-collection-btn').click
     accept_alert
   end
-
-  test "collection tags tab" do
-    visit page_with_token('active', '/collections/zzzzz-4zz18-bv31uwvy3neko21')
-
-    click_link 'Tags'
-    wait_for_ajax
-
-    # verify initial state
-    assert_selector 'a', text: 'Edit'
-    assert_no_selector 'a', text: 'Add new tag'
-    assert_no_selector 'a', text: 'Save'
-    assert_no_selector 'a', text: 'Cancel'
-
-    # Verify controls in edit mode
-    first('.edit-collection-tags').click
-    assert_selector 'a.disabled', text: 'Edit'
-    assert_selector 'a', text: 'Add new tag'
-    assert_selector 'a', text: 'Save'
-    assert_selector 'a', text: 'Cancel'
-
-    # add two tags
-    first('.glyphicon-plus').click
-    first('.collection-tag-field-key').click
-    first('.collection-tag-field-key').set('key 1')
-    first('.collection-tag-field-value').click
-    first('.collection-tag-field-value').set('value 1')
-
-    first('.glyphicon-plus').click
-    editable_key_fields = page.all('.collection-tag-field-key')
-    editable_key_fields[1].click
-    editable_key_fields[1].set('key 2')
-    editable_val_fields = page.all('.collection-tag-field-value')
-    editable_val_fields[1].click
-    editable_val_fields[1].set('value 2')
-
-    click_on 'Save'
-    wait_for_ajax
-
-    # added tags; verify
-    assert_text 'key 1'
-    assert_text 'value 1'
-    assert_text 'key 2'
-    assert_text 'value 2'
-    assert_selector 'a', text: 'Edit'
-    assert_no_selector 'a', text: 'Save'
-
-    # remove first tag
-    first('.edit-collection-tags').click
-    assert_not_nil first('.glyphicon-remove')
-    first('.glyphicon-remove').click
-    click_on 'Save'
-    wait_for_ajax
-
-    assert_text 'key 2'
-    assert_text 'value 2'
-    assert_no_text 'key 1'
-    assert_no_text 'value 1'
-    assert_selector 'a', text: 'Edit'
-
-    # Click on cancel and verify
-    first('.edit-collection-tags').click
-    first('.collection-tag-field-key').click
-    first('.collection-tag-field-key').set('this key wont stick')
-    first('.collection-tag-field-value').click
-    first('.collection-tag-field-value').set('this value wont stick')
-
-    click_on 'Cancel'
-    wait_for_ajax
-
-    assert_text 'key 2'
-    assert_text 'value 2'
-    assert_no_text 'this key wont stick'
-    assert_no_text 'this value wont stick'
-
-    # remove all tags
-    first('.edit-collection-tags').click
-    first('.glyphicon-remove').click
-    click_on 'Save'
-    wait_for_ajax
-
-    assert_selector 'a', text: 'Edit'
-    assert_no_text 'key 2'
-    assert_no_text 'value 2'
-  end
 end