sufia 7.0.0 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -7
  3. data/app/assets/javascripts/sufia.js +3 -4
  4. data/app/assets/javascripts/sufia/app.js +8 -0
  5. data/app/assets/javascripts/sufia/relationships.js +2 -0
  6. data/app/assets/javascripts/sufia/relationships/table.es6 +206 -0
  7. data/app/assets/javascripts/sufia/relationships/table_row.es6 +79 -0
  8. data/app/assets/javascripts/sufia/save_work/save_work_control.es6 +3 -0
  9. data/app/assets/javascripts/sufia/save_work/uploaded_files.es6 +5 -0
  10. data/app/forms/sufia/forms/batch_edit_form.rb +2 -2
  11. data/app/forms/sufia/forms/work_form.rb +13 -1
  12. data/app/presenters/sufia/work_show_presenter.rb +11 -0
  13. data/app/views/curation_concerns/base/_form_child_work_relationships.html.erb +65 -0
  14. data/app/views/curation_concerns/base/_form_progress.html.erb +3 -1
  15. data/app/views/curation_concerns/base/_form_relationships.html.erb +11 -2
  16. data/app/views/curation_concerns/base/_metadata.html.erb +2 -2
  17. data/app/views/curation_concerns/base/_relationships.html.erb +7 -22
  18. data/app/views/curation_concerns/base/_relationships_member_rows.html.erb +24 -0
  19. data/app/views/curation_concerns/base/_relationships_parent_row.html.erb +16 -0
  20. data/app/views/curation_concerns/base/_relationships_parent_row_empty.html.erb +6 -0
  21. data/app/views/curation_concerns/base/_relationships_parent_rows.html.erb +23 -0
  22. data/app/views/curation_concerns/file_sets/_actions.html.erb +1 -1
  23. data/app/views/curation_concerns/file_sets/_show_actions.html.erb +8 -9
  24. data/app/views/curation_concerns/file_sets/_single_use_link_rows.html.erb +19 -0
  25. data/app/views/curation_concerns/file_sets/_single_use_links.html.erb +10 -0
  26. data/app/views/curation_concerns/file_sets/show.html.erb +1 -0
  27. data/app/views/records/edit_fields/_in_works_ids.html.erb +59 -0
  28. data/config/locales/sufia.en.yml +22 -3
  29. data/lib/generators/sufia/templates/config/sufia.rb +5 -0
  30. data/lib/sufia/configuration.rb +6 -0
  31. data/lib/sufia/engine.rb +0 -2
  32. data/lib/sufia/version.rb +1 -1
  33. data/spec/features/search_spec.rb +7 -2
  34. data/spec/forms/sufia/forms/work_form_spec.rb +20 -0
  35. data/spec/javascripts/helpers/test_fixtures.js.coffee +9 -0
  36. data/spec/javascripts/helpers/test_responses.js +10 -5
  37. data/spec/javascripts/relationships_table_spec.js.coffee +83 -0
  38. data/spec/javascripts/save_work_spec.js +4 -2
  39. data/spec/javascripts/support/jasmine.yml +1 -1
  40. data/spec/javascripts/uploaded_files_spec.js +23 -0
  41. data/spec/views/batch_edits/edit.html.erb_spec.rb +1 -1
  42. data/spec/views/catalog/index.html.erb_spec.rb +0 -4
  43. data/spec/views/curation_concerns/base/_form_child_work_relationships.html.erb_spec.rb +113 -0
  44. data/spec/views/curation_concerns/base/_relationships.html.erb_spec.rb +89 -3
  45. data/spec/views/curation_concerns/file_sets/_single_use_links.html.erb_spec.rb +32 -0
  46. data/spec/views/curation_concerns/file_sets/show.html.erb_spec.rb +4 -0
  47. data/spec/views/records/edit_fields/_in_works_ids.html.erb_spec.rb +113 -0
  48. data/sufia.gemspec +1 -2
  49. metadata +35 -21
  50. data/app/assets/javascripts/sufia/single_use_link.js +0 -30
  51. data/spec/javascripts/single_use_link_spec.js.coffee +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acfeaab8524307303e47d77881ceb17215fb9207
4
- data.tar.gz: 2b2e2f5c6f8812d4f63d5e240cdbb9f62f9f49ad
3
+ metadata.gz: 4959186bc36418c96f0c6118865f46ed422b86d5
4
+ data.tar.gz: ee6e03dd75c97f0d9fe345bb969f7a6940b6e643
5
5
  SHA512:
6
- metadata.gz: 1df4473fc8e9a9673fd1e82c6239427770420a69bc4d73f000c969220f13c96c81d9ad4d478086a99f973e981ba0000ca634ddcc0f26245e4f1d9905181cc3a8
7
- data.tar.gz: 547d3fed24466909efb179dfd8d54b790b6105cb48c192fdd1500b586e8f5e1e920dc5054c6d2c9b6ee17731508bd50ae352921f0982e931d9aa1fed156baebd
6
+ metadata.gz: 8bb24da31692f203e40a97a20572b258564c033635c08279fef9ad993f1101c43feef8f71652de061ce05ba0f4c862e6675a5e42b9bb426cd6e7cf660a1a1126
7
+ data.tar.gz: 7485282e6e6ec891998a94bd978843eafbb4023fee59d780a0382ffb1b666a89fd39cbf7dde9ab8994949f16fbdc23f1d4930fbe0039dc2e954123d3ff8cbe56
data/README.md CHANGED
@@ -11,7 +11,7 @@ Docs: [![Apache 2.0 License](http://img.shields.io/badge/APACHE2-license-blue.sv
11
11
  [![Contribution Guidelines](http://img.shields.io/badge/CONTRIBUTING-Guidelines-blue.svg)](./.github/CONTRIBUTING.md)
12
12
  [![API Docs](http://img.shields.io/badge/API-docs-blue.svg)](http://rubydoc.info/gems/sufia)
13
13
 
14
- Jump in: [![Slack Status](http://slack.projecthydra.org/badge.svg)](http://slack.projecthydra.org/) [![Ready Tickets](https://badge.waffle.io/projecthydra/sufia.png?label=ready&milestone=7.0.0&title=Ready)](https://waffle.io/projecthydra/sufia?milestone=7.0.0)
14
+ Jump in: [![Slack Status](http://slack.projecthydra.org/badge.svg)](http://slack.projecthydra.org/) [![Ready Tickets](https://badge.waffle.io/projecthydra/sufia.png?label=ready&milestone=7.2.0&title=Ready)](https://waffle.io/projecthydra/sufia?milestone=7.2.0)
15
15
 
16
16
  # Table of Contents
17
17
 
@@ -49,7 +49,7 @@ Sufia uses the full power of [Hydra](http://projecthydra.org/) and extends it to
49
49
 
50
50
  Sufia has the following features:
51
51
 
52
- * Multiple file, or folder, upload
52
+ * Multiple file upload, and folder uploads (for Chrome browser only)
53
53
  * Flexible user- and group-based access controls
54
54
  * Transcoding of audio and video files
55
55
  * Generation and validation of identifiers
@@ -79,6 +79,7 @@ Sufia has the following features:
79
79
  * Proxy deposit and transfers of ownership
80
80
  * Integration with Zotero for automatic population of user content
81
81
  * Suggested values from controlled vocabularies provided by [Questioning Authority](https://github.com/projecthydra-labs/questioning_authority)
82
+ * [ResourceSync](http://www.openarchives.org/rs/1.0/resourcesync) capability lists and resource lists
82
83
 
83
84
  See [Sufia's documentation site](http://sufia.io/) for more non-technical documentation.
84
85
 
@@ -89,7 +90,7 @@ If you have questions or need help, please email [the Hydra community tech list]
89
90
  # Getting started
90
91
 
91
92
  This document contains instructions specific to setting up an app with __Sufia
92
- v7.0.0.beta1__. If you are looking for instructions on installing a different
93
+ v7.1.0__. If you are looking for instructions on installing a different
93
94
  version, be sure to select the appropriate branch or tag from the drop-down
94
95
  menu above.
95
96
 
@@ -143,10 +144,10 @@ We recommend either Ruby 2.3 or the latest 2.2 version.
143
144
 
144
145
  ## Rails
145
146
 
146
- Generate a new Rails application. We recommend the latest Rails 4.2 release (4.2.6 when last updated).
147
+ Generate a new Rails application. We recommend the latest Rails 4.2 release.
147
148
 
148
149
  ```
149
- gem install rails -v 4.2.6
150
+ gem install rails -v 4.2.7.1
150
151
  rails new my_app
151
152
  ```
152
153
 
@@ -155,7 +156,7 @@ rails new my_app
155
156
  Add the following lines to your application's Gemfile.
156
157
 
157
158
  ```
158
- gem 'sufia', '7.0.0'
159
+ gem 'sufia', '7.1.0'
159
160
  ```
160
161
 
161
162
  Then install Sufia as a dependency of your app via `bundle install`
@@ -170,7 +171,7 @@ rails generate sufia:install -f
170
171
 
171
172
  ## Generate a primary work type
172
173
 
173
- While earlier versions of Sufia came with a pre-defined object model, Sufia 7.0 and greater allow you to specify your primary work type by using tooling provided by the CurationConcerns gem. Work on the 7.x series will include adding support for users to generate an arbitrary number of work types, not **just** a primary work type. At this time we do *not* recommend generating multiple work types.
174
+ While earlier versions of Sufia came with a pre-defined object model, Sufia 7.x and greater allow you to specify your primary work type by using tooling provided by the CurationConcerns gem. Work on the 7.x series will include adding support for users to generate an arbitrary number of work types, not **just** a primary work type. At this time we do *not* recommend generating multiple work types.
174
175
 
175
176
  Pass a (CamelCased) model name to Sufia's work generator to get started, e.g.:
176
177
 
@@ -17,13 +17,13 @@
17
17
  //= require bootstrap/tab
18
18
 
19
19
  //= require jquery.validate
20
- //= require zeroclipboard
21
20
  //= require select2
22
21
  //= require fixedsticky
23
22
 
24
23
  //= require jquery.flot
25
24
  //= require jquery.flot.time
26
25
  //= require jquery.flot.selection
26
+ //= require clipboard
27
27
 
28
28
  //= require batch_edit
29
29
  //
@@ -35,7 +35,6 @@
35
35
  //= require sufia/featured_researcher
36
36
  //= require sufia/batch_select_all
37
37
  //= require sufia/browse_everything
38
- //= require sufia/single_use_link
39
38
  //= require sufia/search
40
39
  //= require sufia/editor
41
40
  //= require sufia/ga_events
@@ -45,6 +44,7 @@
45
44
  //= require sufia/proxy_rights
46
45
  //= require sufia/sorting
47
46
  //= require curation_concerns/batch_select
47
+ //= require curation_concerns/single_use_links_manager
48
48
  //= require sufia/dashboard_actions
49
49
  //= require sufia/batch
50
50
  //= require sufia/flot_stats
@@ -57,11 +57,10 @@
57
57
  //= require sufia/autocomplete/location
58
58
  //= require sufia/autocomplete/subject
59
59
  //= require sufia/autocomplete/language
60
-
60
+ //= require sufia/relationships
61
61
  //= require curation_concerns/collections
62
62
  //= require hydra-editor/hydra-editor
63
63
  //= require nestable
64
64
 
65
65
  // this needs to be after batch_select so that the form ids get setup correctly
66
66
  //= require sufia/batch_edit
67
-
@@ -9,6 +9,7 @@ Sufia = {
9
9
  this.permissions();
10
10
  this.notifications();
11
11
  this.transfers();
12
+ this.relationships_table();
12
13
  },
13
14
 
14
15
  autocomplete: function () {
@@ -58,6 +59,13 @@ Sufia = {
58
59
 
59
60
  transfers: function () {
60
61
  $("#proxy_deposit_request_transfer_to").userSearch();
62
+ },
63
+
64
+ relationships_table: function () {
65
+ var rel = require('sufia/relationships/table');
66
+ $('table.relationships-ajax-enabled').each(function () {
67
+ new rel.RelationshipsTable($(this));
68
+ });
61
69
  }
62
70
  };
63
71
 
@@ -0,0 +1,2 @@
1
+ //= require sufia/relationships/table
2
+ //= require sufia/relationships/table_row
@@ -0,0 +1,206 @@
1
+ import { RelationshipsTableRow } from './table_row'
2
+
3
+ export class RelationshipsTable {
4
+
5
+ /**
6
+ * Initializes the class in the context of an individual table element
7
+ * @param {jQuery} element the table element that this class represents
8
+ */
9
+ constructor(element) {
10
+ this.$element = element;
11
+ this.form_action = this.$element.parents("form").attr("action");
12
+ this.query_url = this.$element.data('query-url');
13
+ this.existing_related_works_values = this.$element.find("input.related_works_ids:not(.new-form-control)").map(function(i, e){ return e.value; });
14
+
15
+ // TODO: Fall back to just cloning rows and removing rows for form posting
16
+ if (!this.query_url)
17
+ return;
18
+
19
+ this.bindAddButton();
20
+ this.bindRemoveButton();
21
+ this.bindKeyEvents();
22
+ }
23
+
24
+ /**
25
+ * Handle click events by the "Add" button in the table, setting a warning
26
+ * message if the input is empty or calling the server to handle the request
27
+ */
28
+ bindAddButton() {
29
+ let $this = this;
30
+
31
+ $this.$element.on("click", ".btn-add-row", function(event) {
32
+ let $row = $(this).parents("tr:first");
33
+ let $input = $row.find("input.new-form-control");
34
+
35
+ // Display an error when the input field is empty, or if the work ID is already related,
36
+ // otherwise clone the row and set appropriate styles
37
+ if ($input.val() == "") {
38
+ $this.setWarningMessage($row, "ID cannot be empty.");
39
+ } else if ($.inArray($input.val(), $this.existing_related_works_values) > -1) {
40
+ $this.setWarningMessage($row, "Work is already related.");
41
+ } else {
42
+
43
+ let query_url = $this.query_url.replace('$id', $input.val());
44
+ $this.hideWarningMessage($row);
45
+ $this.callAjax({
46
+ row: $row,
47
+ table: $this.$element,
48
+ input: $input,
49
+ url: $this.form_action,
50
+ query_url: query_url,
51
+ on_error: $this.handleError,
52
+ on_success: $this.handleAddRowSuccess
53
+ });
54
+ }
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Find and fire off the click event for the "Add" button
60
+ * @param {jQuery} $row the row containing the add button to click
61
+ */
62
+ clickAddbutton($row) {
63
+ $row.find(".btn-add-row").click();
64
+ }
65
+
66
+ /**
67
+ * Handle click events by the "Remove" buttons in the table, and calling the
68
+ * server to handle the request
69
+ */
70
+ bindRemoveButton() {
71
+ let $this = this;
72
+
73
+ $this.$element.on("click", ".btn-remove-row", function(event) {
74
+ let $row = $(this).parents("tr:first");
75
+ let $input = $row.find("input.related_works_ids:first");
76
+
77
+ // Track which input is attemping to be removed, to provide an easy way
78
+ // for the ajax call to exclude it when making the call to the server
79
+ $input.addClass("removing");
80
+ $this.callAjax({
81
+ row: $row,
82
+ table: $this.$element,
83
+ input: $input,
84
+ url: $this.form_action,
85
+ on_error: $this.handleError,
86
+ on_success: $this.handleRemoveRowSuccess
87
+ });
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Handle keyup and keypress events at the form level to prevent the ENTER key
93
+ * from submitting the form. ENTER key within a relationships table should
94
+ * click the "Add" button instead. ESC key should clear the input and hide the
95
+ * error message.
96
+ */
97
+ bindKeyEvents() {
98
+ let $this = this;
99
+ let $form = this.$element.parents("form");
100
+
101
+ $form.on("keyup keypress", "input.related_works_ids", function(event) {
102
+ let $row = $(this).parents("tr:first");
103
+ let key_code = event.keyCode || event.which;
104
+
105
+ // ENTER key was pressed, wait for keyup to click the Add button
106
+ if (key_code === 13) {
107
+ if (event.type == "keyup") {
108
+ $this.clickAddbutton($row);
109
+ }
110
+ event.preventDefault();
111
+ return false;
112
+ }
113
+
114
+ // ESC key was pressed, clear the input field and hide the error
115
+ if (key_code === 27 && event.type == "keyup") {
116
+ $(this).val("");
117
+ $this.hideWarningMessage($row);
118
+ }
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Set the warning message related to the appropriate row in the table
124
+ * @param {jQuery} $row the row containing the warning message to display
125
+ * @param {String} message the warning message text to set
126
+ */
127
+ setWarningMessage($row, message) {
128
+ $row.find(".message.has-warning").text(message).removeClass("hidden");
129
+ }
130
+
131
+ /**
132
+ * Hide the warning message on the appropriate row
133
+ * @param {jQuery} $row the row containing the warning message to hide
134
+ */
135
+ hideWarningMessage($row){
136
+ $row.find(".message").addClass("hidden");
137
+ }
138
+
139
+ /**
140
+ * Call the server, then call the appropriate callbacks to handle success and errors
141
+ * @param {Object} args the table, row, input, url, and callbacks
142
+ */
143
+ callAjax(args) {
144
+ let $this = this;
145
+ // Send only the IDs in this table that aren't in the midst of being "removed"
146
+ let data = args.table.find("input.related_works_ids:not('.removing')").serialize();
147
+ $.ajax({
148
+ type: 'patch',
149
+ url: args.url,
150
+ dataType: 'json',
151
+ data: data
152
+ })
153
+ .done(function(json) {
154
+ args.on_success($this, args, json);
155
+ })
156
+ .fail(function(jqxhr, status, err) {
157
+ args.on_error($this, args, jqxhr, status, err);
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Set a warning message to alert the user on an error
163
+ * @param {jQuery} $this the RelationshipsTable class instance
164
+ * @param {Object} args the table, row, input, url, and callbacks
165
+ * @param {Object} jqxhr the jQuery XHR response object
166
+ * @param {String} status the HTTP error status
167
+ * @param {String} err the HTTP error
168
+ */
169
+ handleError($this, args, jqxhr, status, err) {
170
+ args.row.find('input.removing').removeClass('removing');
171
+ let message = jqxhr.statusText;
172
+ if(jqxhr.responseJSON){
173
+ message = jqxhr.responseJSON.description;
174
+ }
175
+ $this.setWarningMessage(args.row, message);
176
+ }
177
+
178
+ /**
179
+ * Remove the row when the API returns this type of success
180
+ * @param {jQuery} $this the RelationshipsTable class instance
181
+ * @param {Object} args the table, row, input, url, and callbacks
182
+ * @param {String} json the returned JSON string
183
+ */
184
+ handleRemoveRowSuccess($this, args, json) {
185
+ args.row.remove();
186
+ }
187
+
188
+ /**
189
+ * Add a new row to the table, query the server for details about the work to
190
+ * set the title and link for the new work that was added. Hide the input
191
+ * field and display the title and edit button
192
+ * @param {jQuery} $this the RelationshipsTable class instance
193
+ * @param {Object} args the table, row, input, url, and callbacks
194
+ * @param {String} json the returned JSON string
195
+ */
196
+ handleAddRowSuccess($this, args, json) {
197
+ let new_row = new RelationshipsTableRow(args.table);
198
+ new_row.clone(args.row);
199
+ new_row.callAjaxQuery(args.query_url);
200
+
201
+ // finally, empty the "add" row input value
202
+ args.row.find("input.new-form-control").val("");
203
+ // synch the related_works_values to include the new work relationship
204
+ $this.existing_related_works_values = $this.$element.find("input.related_works_ids:not(.new-form-control)").map(function(i, e){ return e.value; });
205
+ }
206
+ }
@@ -0,0 +1,79 @@
1
+ export class RelationshipsTableRow {
2
+ /**
3
+ * Inititializes a new table row class
4
+ * @param {jQuery} $table the table which this row is related to
5
+ */
6
+ constructor($table){
7
+ this.table = $table;
8
+ this.element = null;
9
+ }
10
+
11
+ /**
12
+ * The title element
13
+ * @returns {jQuery} the title element
14
+ */
15
+ get title() { return this.element.find("a.title:first"); }
16
+
17
+ /**
18
+ * The input field
19
+ * @returns {jQuery} the input field
20
+ */
21
+ get input() { return this.element.find("input.related_works_ids:first"); }
22
+
23
+ /**
24
+ * The "Add" button
25
+ * @returns {jQuery} the add button element
26
+ */
27
+ get addButton() { return this.element.find(".btn-add-row"); }
28
+
29
+ /**
30
+ * The "Remove" button
31
+ * @returns {jQuery} the remove button element
32
+ */
33
+ get removeButton() { return this.element.find(".btn-remove-row"); }
34
+
35
+ /**
36
+ * The "Edit" button
37
+ * @returns {jQuery} the edit button element
38
+ */
39
+ get editButton() { return this.element.find("a.edit:first"); }
40
+
41
+ /**
42
+ * Clone the row, set the element in this instance of the class, and reset the proper styles for this row. Insert
43
+ * this new row before the row passed in.. leaving the passed in row styled as-is, the caller of this method is responsible
44
+ * for properly handling adjustment to the passed in row
45
+ * @param {jQuery} $row the row to be cloned
46
+ */
47
+ clone($row){
48
+ this.element = $row.clone();
49
+ this.addButton.addClass("hidden");
50
+ this.removeButton.removeClass("hidden");
51
+ this.element.insertBefore($row);
52
+ }
53
+
54
+ /**
55
+ * Make an ajax call to the supplied url.
56
+ * After a row is cloned, this method could be called to refresh the row with more details (title, appropriate links, etc)
57
+ * based on the details returned from the server.
58
+ * @param {String} query_url the url to be called for querying details from the server
59
+ */
60
+ callAjaxQuery(query_url) {
61
+ let $this = this;
62
+ $.getJSON(query_url, function (data) {
63
+ // Set the cloned input to have the proper name and value for posting the
64
+ // form to the server, and hide it.
65
+ $this.input.removeClass("new-form-control")
66
+ .addClass("hidden")
67
+ .val(data.id);
68
+
69
+ // Set the linkified title and show.
70
+ $this.title.text(data.title[0])
71
+ .attr("href", query_url)
72
+ .removeClass("hidden");
73
+
74
+ // Set the edit button link and show.
75
+ $this.editButton.attr("href", query_url + "/edit")
76
+ .removeClass("hidden");
77
+ });
78
+ }
79
+ }
@@ -104,6 +104,9 @@ export class SaveWorkControl {
104
104
 
105
105
  // sets the files indicator to complete/incomplete
106
106
  validateFiles() {
107
+ if (!this.uploads.hasFileRequirement) {
108
+ return true
109
+ }
107
110
  if (!this.isNew || this.uploads.hasFiles) {
108
111
  this.requiredFiles.check()
109
112
  return true
@@ -5,6 +5,11 @@ export class UploadedFiles {
5
5
  $('#fileupload').bind('fileuploadcompleted', callback)
6
6
  }
7
7
 
8
+ get hasFileRequirement() {
9
+ let fileRequirement = this.form.find('li#required-files')
10
+ return fileRequirement.size() > 0
11
+ }
12
+
8
13
  get hasFiles() {
9
14
  let fileField = this.form.find('input[name="uploaded_files[]"]')
10
15
  return fileField.size() > 0