sufia 7.3.0.rc3 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.eslintrc +6 -5
  4. data/.rubocop.yml +0 -1
  5. data/.rubocop_todo.yml +0 -8
  6. data/README.md +10 -5
  7. data/app/actors/sufia/actors/attach_members_actor.rb +70 -0
  8. data/app/actors/sufia/apply_permission_template_actor.rb +4 -4
  9. data/app/actors/sufia/default_admin_set_actor.rb +10 -7
  10. data/app/assets/javascripts/sufia.js +3 -1
  11. data/app/assets/javascripts/sufia/app.js +7 -18
  12. data/app/assets/javascripts/sufia/autocomplete.es6 +56 -57
  13. data/app/assets/javascripts/sufia/autocomplete/language.es6 +1 -2
  14. data/app/assets/javascripts/sufia/autocomplete/location.es6 +1 -1
  15. data/app/assets/javascripts/sufia/autocomplete/subject.es6 +1 -3
  16. data/app/assets/javascripts/sufia/autocomplete/work.es6 +31 -22
  17. data/app/assets/javascripts/sufia/{editor.js → content_blocks.js} +0 -0
  18. data/app/assets/javascripts/sufia/editor.es6 +47 -0
  19. data/app/assets/javascripts/sufia/editor/admin_set_widget.es6 +39 -0
  20. data/app/assets/javascripts/sufia/notifications.es6 +13 -13
  21. data/app/assets/javascripts/sufia/permissions/control.es6 +3 -3
  22. data/app/assets/javascripts/sufia/permissions/group_controls.es6 +2 -2
  23. data/app/assets/javascripts/sufia/permissions/user_controls.es6 +3 -3
  24. data/app/assets/javascripts/sufia/relationships.js +4 -2
  25. data/app/assets/javascripts/sufia/relationships/control.es6 +83 -0
  26. data/app/assets/javascripts/sufia/relationships/registry.es6 +60 -0
  27. data/app/assets/javascripts/sufia/relationships/registry_entry.es6 +38 -0
  28. data/app/assets/javascripts/sufia/relationships/work.es6 +7 -0
  29. data/app/assets/javascripts/sufia/save_work/required_fields.es6 +1 -1
  30. data/app/assets/javascripts/sufia/save_work/save_work_control.es6 +19 -11
  31. data/app/assets/javascripts/sufia/save_work/visibility_component.es6 +19 -10
  32. data/app/assets/javascripts/sufia/select_work_type.es6 +8 -6
  33. data/app/controllers/concerns/sufia/batch_uploads_controller_behavior.rb +1 -1
  34. data/app/controllers/concerns/sufia/works_controller_behavior.rb +7 -0
  35. data/app/controllers/sufia/admin/admin_sets_controller.rb +5 -2
  36. data/app/forms/sufia/forms/batch_edit_form.rb +2 -1
  37. data/app/forms/sufia/forms/work_form.rb +19 -17
  38. data/app/models/concerns/sufia/admin_set_behavior.rb +38 -0
  39. data/app/presenters/sufia/admin_set_options_presenter.rb +51 -0
  40. data/app/presenters/sufia/admin_set_presenter.rb +11 -0
  41. data/app/search_builders/sufia/find_works_search_builder.rb +1 -0
  42. data/app/services/sufia/actor_factory.rb +1 -0
  43. data/app/services/sufia/admin_set_create_service.rb +9 -9
  44. data/app/services/sufia/admin_set_service.rb +0 -28
  45. data/app/services/sufia/workflow/abstract_notification.rb +14 -1
  46. data/app/services/sufia/workflow/changes_required_notification.rb +2 -2
  47. data/app/services/sufia/workflow/deposited_notification.rb +1 -1
  48. data/app/services/sufia/workflow/pending_review_notification.rb +1 -1
  49. data/app/views/curation_concerns/base/_find_work_widget.html.erb +10 -16
  50. data/app/views/curation_concerns/base/_form.html.erb +5 -1
  51. data/app/views/curation_concerns/base/_form_child_work_relationships.html.erb +22 -32
  52. data/app/views/curation_concerns/base/_form_relationships.html.erb +4 -7
  53. data/app/views/curation_concerns/base/_guts4form.html.erb +7 -1
  54. data/app/views/sufia/admin/admin_sets/_form_participants.html.erb +3 -3
  55. data/app/views/sufia/admin/admin_sets/show.html.erb +10 -2
  56. data/config/initializers/monkey_patch_admin_set.rb +2 -0
  57. data/config/locales/sufia.en.yml +2 -0
  58. data/db/migrate/20170317141521_permission_template_change_column_workflow_name.rb +5 -0
  59. data/lib/generators/sufia/templates/workflow.json.erb +1 -0
  60. data/lib/sufia/version.rb +1 -1
  61. data/lib/tasks/migrate.rake +1 -1
  62. data/spec/actors/sufia/actors/attach_members_actor_spec.rb +68 -0
  63. data/spec/actors/sufia/apply_permission_template_actor_spec.rb +7 -3
  64. data/spec/actors/sufia/default_admin_set_actor_spec.rb +1 -1
  65. data/spec/controllers/sufia/admin/admin_sets_controller_spec.rb +21 -6
  66. data/spec/controllers/sufia/admin/permission_templates_controller_spec.rb +1 -1
  67. data/spec/controllers/sufia/batch_uploads_controller_spec.rb +24 -2
  68. data/spec/factories/permission_templates.rb +1 -0
  69. data/spec/factories/workflows.rb +1 -1
  70. data/spec/forms/curation_concerns/generic_work_form_spec.rb +5 -2
  71. data/spec/forms/sufia/forms/batch_edit_form_spec.rb +1 -1
  72. data/spec/forms/sufia/forms/work_form_spec.rb +32 -19
  73. data/spec/javascripts/autocomplete_spec.js.coffee +12 -33
  74. data/spec/javascripts/helpers/test_fixtures.js.coffee +10 -1
  75. data/spec/javascripts/relationships_control_spec.js.coffee +27 -0
  76. data/spec/javascripts/save_work_spec.js +9 -6
  77. data/spec/javascripts/visibility_component_spec.js +17 -8
  78. data/spec/models/admin_set_spec.rb +69 -0
  79. data/spec/presenters/sufia/admin_set_options_presenter_spec.rb +73 -0
  80. data/spec/presenters/sufia/admin_set_presenter_spec.rb +31 -2
  81. data/spec/services/sufia/actor_factory_spec.rb +2 -0
  82. data/spec/services/sufia/admin_set_create_service_spec.rb +4 -2
  83. data/spec/services/sufia/admin_set_service_spec.rb +0 -72
  84. data/spec/services/sufia/workflow/changes_required_notification_spec.rb +1 -1
  85. data/spec/services/sufia/workflow/deposited_notification_spec.rb +1 -1
  86. data/spec/services/sufia/workflow/pending_review_notification_spec.rb +1 -1
  87. data/spec/views/curation_concerns/base/_find_work_widget.html.erb_spec.rb +1 -2
  88. data/spec/views/curation_concerns/base/_form_child_work_relationships.html.erb_spec.rb +27 -62
  89. data/spec/views/curation_concerns/base/_form_relationships.html.erb_spec.rb +4 -2
  90. data/spec/views/sufia/admin/admin_sets/_form_participants.html.erb_spec.rb +1 -1
  91. data/spec/views/sufia/admin/admin_sets/_form_visibility.html.erb_spec.rb +3 -1
  92. data/spec/views/sufia/admin/admin_sets/show.html.erb_spec.rb +59 -0
  93. data/sufia.gemspec +3 -2
  94. data/template.rb +1 -1
  95. metadata +34 -19
  96. data/app/assets/javascripts/sufia/relationships/table.es6 +0 -206
  97. data/app/assets/javascripts/sufia/relationships/table_row.es6 +0 -79
  98. data/app/views/curation_concerns/base/_form_parent_work_relationships.html.erb +0 -43
  99. data/app/views/records/edit_fields/_in_works_ids.html.erb +0 -59
  100. data/spec/javascripts/helpers/test_responses.js +0 -13
  101. data/spec/javascripts/relationships_table_spec.js.coffee +0 -83
  102. data/spec/views/curation_concerns/base/_form_parent_work_relationships.html.erb_spec.rb +0 -114
  103. data/spec/views/records/edit_fields/_in_works_ids.html.erb_spec.rb +0 -113
@@ -1,4 +1,4 @@
1
- export class Language {
1
+ export default class Language {
2
2
  constructor(element, url) {
3
3
  this.url = url
4
4
  element.autocomplete(this.options());
@@ -22,4 +22,3 @@ export class Language {
22
22
  };
23
23
  }
24
24
  }
25
-
@@ -1,4 +1,4 @@
1
- export class Location {
1
+ export default class Location {
2
2
  constructor(element, url) {
3
3
  this.url = url
4
4
  element.autocomplete(this.options());
@@ -1,4 +1,4 @@
1
- export class Subject {
1
+ export default class Subject {
2
2
  constructor(element, url) {
3
3
  this.url = url
4
4
  element.autocomplete(this.options());
@@ -22,5 +22,3 @@ export class Subject {
22
22
  };
23
23
  }
24
24
  }
25
-
26
-
@@ -1,29 +1,38 @@
1
- export class Work {
2
- // Autocomplete for finding possible related works (child and parent).
3
- constructor(element, url, user, id) {
1
+ export default class Work {
2
+ // Autocomplete for finding possible related works.
3
+ constructor(element, url, excludeWorkId) {
4
4
  this.url = url;
5
- this.user = user;
6
- this.work_id = id;
7
- element.autocomplete(this.options());
5
+ this.excludeWorkId = excludeWorkId;
6
+ this.initUI(element)
8
7
  }
9
8
 
10
- options() {
11
- return {
12
- minLength: 2,
13
- source: ( request, response ) => {
14
- $.getJSON(this.url, {
15
- q: request.term,
16
- id: this.work_id,
17
- user: this.user
18
- }, response );
9
+ initUI(element) {
10
+ element.select2( {
11
+ minimumInputLength: 2,
12
+ initSelection : (row, callback) => {
13
+ var data = {id: row.val(), text: row.val()};
14
+ callback(data);
19
15
  },
20
- focus: function() {
21
- // prevent value inserted on focus
22
- return false;
23
- },
24
- complete: function(event) {
25
- $('.ui-autocomplete-loading').removeClass("ui-autocomplete-loading");
16
+ ajax: { // instead of writing the function to execute the request we use Select2's convenient helper
17
+ url: this.url,
18
+ dataType: 'json',
19
+ data: (term, page) => {
20
+ return {
21
+ q: term, // search term
22
+ id: this.excludeWorkId // Exclude this work
23
+ };
24
+ },
25
+ results: this.processResults
26
26
  }
27
- };
27
+ }).select2('data', null);
28
+ }
29
+
30
+ // parse the results into the format expected by Select2.
31
+ // since we are using custom formatting functions we do not need to alter remote JSON data
32
+ processResults(data, page) {
33
+ let results = data.map((obj) => {
34
+ return { id: obj.id, text: obj.label[0] };
35
+ })
36
+ return { results: results };
28
37
  }
29
38
  }
@@ -0,0 +1,47 @@
1
+ import RelationshipsControl from 'sufia/relationships/control'
2
+ import SaveWorkControl from 'sufia/save_work/save_work_control'
3
+ import AdminSetWidget from 'sufia/editor/admin_set_widget'
4
+
5
+ export default class {
6
+ constructor(element) {
7
+ this.element = element
8
+ this.adminSetWidget = new AdminSetWidget(element.find('select[id$="_admin_set_id"]'))
9
+ this.sharingTabElement = $('#tab-share')
10
+
11
+ this.sharingTab()
12
+ this.relationshipsControl()
13
+ this.saveWorkControl()
14
+ this.saveWorkFixed()
15
+ }
16
+
17
+ // Display the sharing tab if they select an admin set that permits sharing
18
+ sharingTab() {
19
+ if(this.adminSetWidget && !this.adminSetWidget.isEmpty()) {
20
+ this.adminSetWidget.on('change', (data) => this.sharingTabVisiblity(data))
21
+ this.sharingTabVisiblity(this.adminSetWidget.isSharing())
22
+ }
23
+ }
24
+
25
+ sharingTabVisiblity(visible) {
26
+ if (visible)
27
+ this.sharingTabElement.removeClass('hidden')
28
+ else
29
+ this.sharingTabElement.addClass('hidden')
30
+ }
31
+
32
+ relationshipsControl() {
33
+ new RelationshipsControl(this.element.find('[data-behavior="child-relationships"]'),
34
+ 'work_members_attributes',
35
+ 'tmpl-child-work')
36
+ }
37
+
38
+ saveWorkControl() {
39
+ new SaveWorkControl(this.element.find("#form-progress"), this.adminSetWidget)
40
+ }
41
+
42
+ saveWorkFixed() {
43
+ // Setting test to false to skip native and go right to polyfill
44
+ FixedSticky.tests.sticky = false
45
+ this.element.find('#savewidget').fixedsticky()
46
+ }
47
+ }
@@ -0,0 +1,39 @@
1
+ export default class {
2
+ // takes a jquery selector for a select field
3
+ // create a custom change event with the data when it changes
4
+ constructor(element) {
5
+ this.changeHandlers = []
6
+ this.element = element
7
+ element.on('change', (e) => {
8
+ this.change(this.data())
9
+ })
10
+ }
11
+
12
+ data() {
13
+ return this.element.find(":selected").data()
14
+ }
15
+
16
+ isEmpty() {
17
+ return this.element.children().length === 0
18
+ }
19
+
20
+ /**
21
+ * returns undefined or true
22
+ */
23
+ isSharing() {
24
+ return this.data()["sharing"]
25
+ }
26
+
27
+ on(eventName, handler) {
28
+ switch (eventName) {
29
+ case "change":
30
+ return this.changeHandlers.push(handler)
31
+ }
32
+ }
33
+
34
+ change(data) {
35
+ for (let fn of this.changeHandlers) {
36
+ setTimeout(function() { fn(data) }, 0)
37
+ }
38
+ }
39
+ }
@@ -13,28 +13,28 @@ export class Notifications {
13
13
  }
14
14
 
15
15
  poller(interval, url) {
16
- setInterval(() => { this.fetchUpdates(url) }, interval);
16
+ setInterval(() => { this.fetchUpdates(url) }, interval);
17
17
  }
18
18
 
19
19
  fetchUpdates(url) {
20
- $.getJSON( url, (data) => this.updatePage(data))
20
+ $.getJSON( url, (data) => this.updatePage(data))
21
21
  }
22
22
 
23
23
  updatePage(data) {
24
- let notification = $('.notify_number')
25
- notification.find('.count').html(data.notify_number)
26
- if (data.notify_number == 0) {
27
- notification.addClass('label-default')
28
- notification.removeClass('label-danger')
29
- } else {
30
- notification.addClass('label-danger')
31
- notification.removeClass('label-default')
32
- }
24
+ let notification = $('.notify_number')
25
+ notification.find('.count').html(data.notify_number)
26
+ if (data.notify_number === 0) {
27
+ notification.addClass('label-default')
28
+ notification.removeClass('label-danger')
29
+ } else {
30
+ notification.addClass('label-danger')
31
+ notification.removeClass('label-default')
32
+ }
33
33
  }
34
34
 
35
35
  getIntervalSeconds(default_interval) {
36
- var seconds = parseInt(this.queryStringParam("notification_seconds"));
37
- return seconds || default_interval;
36
+ var seconds = parseInt(this.queryStringParam("notification_seconds"), 10);
37
+ return seconds || default_interval;
38
38
  }
39
39
 
40
40
  // During development allow the frequency of the notifications check to
@@ -6,10 +6,10 @@ export class PermissionsControl {
6
6
  /**
7
7
  * Initialize the save controls
8
8
  * @param {jQuery} element the jquery selector for the permissions container
9
- * @param {String} template_id the identifier of the template for the added elements
9
+ * @param {String} template_id the identifier of the template for the added elements
10
10
  */
11
11
  constructor(element, template_id) {
12
- if (element.size() == 0) {
12
+ if (element.size() === 0) {
13
13
  return
14
14
  }
15
15
  this.element = element
@@ -19,7 +19,7 @@ export class PermissionsControl {
19
19
  this.group_controls = new GroupControls(this.element, this.registry)
20
20
  }
21
21
 
22
- // retrieve object_name the name of the object to create
22
+ // retrieve object_name the name of the object to create
23
23
  object_name() {
24
24
  return this.element.data('param-key')
25
25
  }
@@ -56,7 +56,7 @@ export class GroupControls {
56
56
  }
57
57
 
58
58
  selectedGroupValid() {
59
- return this.selectedGroup().index() != 0
59
+ return this.selectedGroup().index() !== 0
60
60
  }
61
61
 
62
62
  selectedGroup() {
@@ -64,7 +64,7 @@ export class GroupControls {
64
64
  }
65
65
 
66
66
  permissionValid() {
67
- return this.selectedPermission().index() != 0
67
+ return this.selectedPermission().index() !== 0
68
68
  }
69
69
 
70
70
  selectedPermission() {
@@ -60,7 +60,7 @@ export class UserControls {
60
60
  }
61
61
 
62
62
  selectedUserIsDepositor() {
63
- return this.userName() == this.depositor
63
+ return this.userName() === this.depositor
64
64
  }
65
65
 
66
66
  userValid() {
@@ -68,11 +68,11 @@ export class UserControls {
68
68
  }
69
69
 
70
70
  userNameValid() {
71
- return this.userName() != ""
71
+ return this.userName() !== ""
72
72
  }
73
73
 
74
74
  permissionValid() {
75
- return this.selectedPermission().index() != 0
75
+ return this.selectedPermission().index() !== 0
76
76
  }
77
77
 
78
78
  selectedPermission() {
@@ -1,2 +1,4 @@
1
- //= require sufia/relationships/table
2
- //= require sufia/relationships/table_row
1
+ //= require sufia/relationships/control
2
+ //= require sufia/relationships/registry
3
+ //= require sufia/relationships/registry_entry
4
+ //= require sufia/relationships/work
@@ -0,0 +1,83 @@
1
+ import Registry from './registry'
2
+ import Work from './work'
3
+
4
+ export default class RelationshipsControl {
5
+
6
+ /**
7
+ * Initializes the class in the context of an individual table element
8
+ * @param {jQuery} element the table element that this class represents
9
+ * @param {String} property the property to submit
10
+ * @param {String} templateId the template identifier for new rows
11
+ */
12
+ constructor(element, property, templateId) {
13
+ this.element = element
14
+ this.registry = new Registry(this.element, this.element.data('paramKey'), property, templateId)
15
+
16
+ this.input = this.element.find("[data-autocomplete='work']")
17
+ this.warning = this.element.find(".message.has-warning")
18
+ this.addButton = this.element.find("[data-behavior='add-relationship']")
19
+ this.errors = null
20
+ this.bindAddButton();
21
+ }
22
+
23
+ validate() {
24
+ if (this.input.val() === "") {
25
+ this.errors = ['ID cannot be empty.']
26
+ }
27
+ }
28
+
29
+ isValid() {
30
+ this.validate()
31
+ return this.errors === null
32
+ }
33
+
34
+ /**
35
+ * Handle click events by the "Add" button in the table, setting a warning
36
+ * message if the input is empty or calling the server to handle the request
37
+ */
38
+ bindAddButton() {
39
+ this.addButton.on("click", () => this.attemptToAddRow())
40
+ }
41
+
42
+ attemptToAddRow() {
43
+ // Display an error when the input field is empty, or if the work ID is already related,
44
+ // otherwise clone the row and set appropriate styles
45
+ if (this.isValid()) {
46
+ this.addRow()
47
+ } else {
48
+ this.setWarningMessage(this.errors.join(', '))
49
+ }
50
+ }
51
+
52
+ addRow() {
53
+ this.hideWarningMessage()
54
+ let data = this.searchData()
55
+ this.registry.addWork(new Work(data.id, data.text))
56
+
57
+ // finally, empty the "add" row input value
58
+ this.clearSearch();
59
+ }
60
+
61
+ searchData() {
62
+ return this.input.select2('data')
63
+ }
64
+
65
+ clearSearch() {
66
+ this.input.select2("val", '');
67
+ }
68
+
69
+ /**
70
+ * Set the warning message related to the appropriate row in the table
71
+ * @param {String} message the warning message text to set
72
+ */
73
+ setWarningMessage(message) {
74
+ this.warning.text(message).removeClass("hidden");
75
+ }
76
+
77
+ /**
78
+ * Hide the warning message on the appropriate row
79
+ */
80
+ hideWarningMessage(){
81
+ this.warning.addClass("hidden");
82
+ }
83
+ }
@@ -0,0 +1,60 @@
1
+ import RegistryEntry from './registry_entry'
2
+ export default class Registry {
3
+ /**
4
+ * Initialize the registry
5
+ * @param {jQuery} element the jquery selector for the permissions container
6
+ * @param {String} object_name the name of the object, for constructing form fields (e.g. 'generic_work')
7
+ * @param {String} templateId the the identifier of the template for the added elements
8
+ */
9
+ constructor(element, objectName, propertyName, templateId) {
10
+ this.objectName = objectName
11
+ this.propertyName = propertyName
12
+
13
+ this.templateId = templateId
14
+ this.items = []
15
+ this.element = element
16
+
17
+ // the remove button is only on preexisting grants
18
+ element.find('[data-behavior="remove-relationship"]').on('click', (evt) => this.removeWork(evt))
19
+ }
20
+
21
+ // Return an index for the hidden field when adding a new row.
22
+ // This makes the assumption that all the tr elements represent a work except
23
+ // for the final one, which is the "add another" form
24
+ nextIndex() {
25
+ return this.element.find('tbody').children('tr').length - 1;
26
+ }
27
+
28
+ addWork(work) {
29
+ work.index = this.nextIndex()
30
+ this.items.push(new RegistryEntry(work, this, this.element.find('tr:last'), this.templateId))
31
+ this.showSaveNote();
32
+ }
33
+
34
+ // removes a row that has been persisted
35
+ removeWork(evt) {
36
+ evt.preventDefault();
37
+ let button = $(evt.target);
38
+ let container = button.closest('tr');
39
+ container.addClass('hidden'); // do not show the block
40
+ this.addDestroyField(container, button.attr('data-index'));
41
+ this.showSaveNote();
42
+ }
43
+
44
+ addDestroyField(element, index) {
45
+ $('<input>').attr({
46
+ type: 'hidden',
47
+ name: `${this.fieldPrefix(index)}[_destroy]`,
48
+ value: 'true'
49
+ }).appendTo(element);
50
+ }
51
+
52
+ fieldPrefix(counter) {
53
+ return `${this.objectName}[${this.propertyName}][${counter}]`
54
+ }
55
+
56
+ showSaveNote() {
57
+ // TODO: we may want to reveal a note that changes aren't active until the work is saved
58
+ }
59
+
60
+ }
@@ -0,0 +1,38 @@
1
+ export default class RegistryEntry {
2
+ /**
3
+ * Initialize the registry entry
4
+ * @param {Work} work the work to display on the form
5
+ * @param {Registry} registry the registry that holds this registry entry.
6
+ * @param {jQuery} element a place to insert the new row
7
+ * @param {String} template identifer of the new row template.
8
+ */
9
+ constructor(work, registry, element, template) {
10
+ this.work = work
11
+ this.registry = registry
12
+ this.element = element
13
+
14
+ let row = this.createRow(work, template);
15
+ this.addHiddenField(row, work);
16
+ row.effect("highlight", {}, 3000);
17
+ }
18
+
19
+ // Remove a row that has not been persisted
20
+ createRow(work, templateId) {
21
+ let row = $(tmpl(templateId, work));
22
+ this.element.before(row);
23
+ row.find('[data-behavior="remove-relationship"]').click(function () {
24
+ row.remove();
25
+ });
26
+
27
+ return row;
28
+ }
29
+
30
+ addHiddenField(element, work) {
31
+ var prefix = this.registry.fieldPrefix(work.index);
32
+ $('<input>').attr({
33
+ type: 'hidden',
34
+ name: prefix + '[id]',
35
+ value: work.id
36
+ }).appendTo(element);
37
+ }
38
+ }