• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / tigerdata-app / ae92dc00-e0b3-4dab-b9c2-66e829d6d3e6

08 Jul 2025 06:28PM UTC coverage: 71.631% (-0.9%) from 72.487%
ae92dc00-e0b3-4dab-b9c2-66e829d6d3e6

Pull #1581

circleci

web-flow
Merge branch 'main' into 114-mflux-sync
Pull Request #1581: Remove schema create and update functionality from tigerdata-app

4 of 18 branches covered (22.22%)

27 of 28 new or added lines in 4 files covered. (96.43%)

14 existing lines in 2 files now uncovered.

2934 of 4096 relevant lines covered (71.63%)

503.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.08
/app/models/tigerdata_schema.rb
1
# frozen_string_literal: true
2

3
# Defines the Tigerdata schema in MediaFlux along with the
4
# document types allowed. At this point we only support "projects"
5
class TigerdataSchema
1✔
6
  attr_accessor :schema_name, :schema_description
1✔
7

8
  SCHEMA_VERSION = "0.6.1"
1✔
9

10
  def initialize(schema_name: nil, schema_description: nil, session_id:)
1✔
11
    @schema_name = schema_name || "tigerdata"
3✔
12
    @schema_description = schema_description || "TigerData Metadata"
3✔
13
    @session_id = session_id
3✔
14
  end
15

16
  def create
1✔
UNCOV
17
    define_schema_namespace
×
UNCOV
18
    define_project
×
19
  end
20

21
  def define_schema_namespace
1✔
UNCOV
22
    schema_request = Mediaflux::SchemaCreateRequest.new(name: @schema_name,
×
23
                                                              description: @schema_description,
24
                                                              session_token: @session_id)
UNCOV
25
    schema_request.resolve
×
26
  end
27

28
  def define_project
1✔
UNCOV
29
    fields_request = Mediaflux::SchemaFieldsCreateRequest.new(
×
30
      schema_name: @schema_name,
31
      document: "project",
32
      description: "Project Metadata",
33
      fields: project_schema_fields,
34
      session_token: @session_id
35
    )
UNCOV
36
    fields_request.resolve
×
UNCOV
37
    if fields_request.error?
×
38
      raise "Could not create or update schema #{fields_request.response_error}"
×
39
    end
40
  end
41

42
  def create_aterm_schema_command(line_terminator = nil)
1✔
43
    namespace_command = "asset.doc.namespace.update :create true :namespace tigerdata :description \"TigerData metadata schema\"\n\n"
3✔
44
    field_command = "asset.doc.type.update :create true :description \"Project metadata\" :type tigerdata:project :definition <#{line_terminator}"
3✔
45
    project_schema_fields.each do |field|
3✔
46
      field_command += aterm_element(field:, line_terminator:)
57✔
47
    end
48
    field_command += ">"
3✔
49
    namespace_command + field_command
3✔
50
  end
51

52
  def create_aterm_doc_script(filename: Rails.root.join("docs", "schema_script.txt"))
1✔
53
    File.open(filename, "w") do |script|
1✔
54
      script.write("# This file was automatically generated on #{Time.current.in_time_zone("America/New_York").iso8601}\n")
1✔
55
      script.write("# Create the \"tigerdata\" namespace schema and the \"project\" definition inside of it.\n#\n")
1✔
56
      script.write("# To run this script, issue the following command from Aterm\n#\n")
1✔
57
      script.write("# script.execute :in file://full/path/to/tigerdata-app/docs/schema_script.txt\n#\n")
1✔
58
      script.write("# Notice that if you copy and paste the (multi-line) asset.doc.type.update command\n")
1✔
59
      script.write("# into Aterm you'll have to make it single line (i.e. remove the \\)\n")
1✔
60

61
      script.write(create_aterm_schema_command(" \\\n"))
1✔
62
      script.write("\n")
1✔
63
    end
64
  end
65

66
  # rubocop:disable Metrics/MethodLength
67
  def self.project_schema_fields
1✔
68
    # WARNING: Do not use `id` as field name, MediaFlux uses specific rules for an `id` field.
69
    project_directory = { name: "ProjectDirectory", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1, label: "Project Directory", description: "The locally unique name for the project's top-level directory",
15,500✔
70
                          instructions: "The locally unique name for the project's top-level directory, as shown in a file path. Data Sponsors may suggest a project directory name that is meaningful to them, subject to system administrator approval."
71
                        }
72
    title = { name: "Title", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Title",
15,500✔
73
              description: "A plain-language title for the project",
74
              instructions: "A plain-language title for the project (at the highest level, if sub-projects exist), which will display in metadata records and search results, and which can be edited later (unlike the Project ID)."
75
            }
76
    description = { name: "Description", type: "string", index: false, "min-occurs" => 0, "max-occurs" => 1,
15,500✔
77
                    label: "Description",
78
                    description: "A brief description of the project",
79
                    instructions: "A brief description of the project (at the highest level, if sub-projects exist), which serves to summarize the project objectives and (anticipated) data and metadata included in the project."
80
                  }
81
    status = { name: "Status", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1, label: "Status", description: "The current status of the project",
15,500✔
82
               instructions: "The current status of the project, as it pertains to the major events tracked by provenance fields (e.g., active, approved, pending, published, or retired)."
83
              }
84
    data_sponsor = { name: "DataSponsor", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1,
15,500✔
85
                    label: "Data Sponsor",
86
                    description: "The person who takes primary responsibility for the project",
87
                    instructions: "The 'Data Sponsor' is the person who takes primary responsibility for the project, including oversight of all of the other roles, all of the data contained in the project," \
88
                                 " and all of the metadata associated with the data and the project itself." \
89
                                 " This field is required for all projects in TigerData, and all files in a given project inherit the Data Sponsor value from the project metadata." \
90
                                 " The person filling the role must be both a registered TigerData user and a current member of the list of eligible Data Sponsors for TigerData."
91
                   }
92
    data_manager = { name: "DataManager", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1,
15,500✔
93
                      label: "Data Manager",
94
                      description: "The person who manages the day-to-day activities for the project",
95
                      instructions: "The 'Data Manager' is the person who manages the day-to-day activities for the project, including both data and metadata, but not including role assignments, which is instead determined by the Data Sponsor." \
96
                                   " (However, the same person may fill both the Data Sponsor and the Data Manager roles on the same project, provided they are eligible for both.) This field is required for all projects in TigerData, and all files in a given project inherit the Data Manager value from the project metadata." \
97
                                   " The person filling the role must be both a registered TigerData user and current member of the list of eligible Data Managers for TigerData."
98
                    }
99
    data_users = { name: "DataUser", type: "string", index: true, "min-occurs" => 0,
15,500✔
100
                   label: "Data User(s)",
101
                   description: "A person who has read and write access privileges to the project",
102
                   instructions: "A 'Data User' is a person who has access privileges to a given project or file, including data and metadata."\
103
                                " This field is optional for both projects and files."\
104
                                " Any number of Data Users may be assigned to a given project or file, with or without a read-only restriction."\
105
                                " All Data Users must be registered for TigerData prior to assignment.",
106
                   attributes: [{ name: "ReadOnly", type: "boolean", index: false, "min-occurs" => 0, description: "Determines whether a given Data User is limited to read-only access to files" }] }
107
    departments = { name: "Department", type: "string", index: true, "min-occurs" => 1,
15,500✔
108
                    label: "Department(s)",
109
                    description: "The primary Princeton University department(s) affiliated with the project",
110
                    instructions: "The primary Princeton University department(s) affiliated with the project. In cases where the Data Sponsor holds cross-appointments, or where multiple departments are otherwise " \
111
                                  "involved with the project, multiple departments may be recorded. This field is not meant to capture the departmental affiliations of every person connected to this project, " \
112
                                  "but rather the departments directly tied to the project itself."
113
                  }
114
    created_on = { name: "CreatedOn", type: "date", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Created On", description: "Timestamp project was created" }
15,500✔
115
    created_by = { name: "CreatedBy", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Created By", description: "User that created the project" }
15,500✔
116
    updated_on = { name: "UpdatedOn", type: "date", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Updated On", description: "Timestamp project was updated" }
15,500✔
117
    updated_by = { name: "UpdatedBy", type: "string", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Updated By", description: "User that updated the project" }
15,500✔
118
    project_id = { name: "ProjectID", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1, label: "Project ID",
15,500✔
119
                   description: "The universally unique identifier for the project (or in some cases, for the sub-project), automatically generated as a valid DOI compliant with ISO 26324:2012.",
120
                   instructions: "Records the DOI reserved for the project, from which the automatic code component of the Project ID is determined"
121
                 }
122

123
    requested_attribute = { name: "Requested", type: "string", index: false, "min-occurs" => 0, description: "The requested value provided by the Data Sponsor or Data Manager."}
15,500✔
124
    approved_attribute = { name: "Approved", type: "string", index: false, "min-occurs" => 0, description: "The value approved and assigned by a system administrator (may not be the same as the requested value)."}
15,500✔
125
    storage_size = { name: "Size", type: "float", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Size", description: "The numerical value of the quantity",
15,500✔
126
                     instructions: "The numerical value of the quantity (e.g., count, size, magnitude, etc.)",
127
                     attributes: [requested_attribute, approved_attribute]
128
                    }
129
    storage_unit = { name: "Unit", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Unit", description: "The unit of measure for the quantity",
15,500✔
130
                     instructions: "The unit of measure for the quantity (e.g., MB, GB, TB, etc.)",
131
                     attributes: [requested_attribute, approved_attribute]
132
                    }
133
    storage_capacity = { name: "StorageCapacity", type: "document", index: false, "min-occurs" => 1, "max-occurs" => 1,
15,500✔
134
                         label: "Storage Capacity",
135
                         description: "The requested storage capacity (default 500 GB)",
136
                         instructions: "The anticipated amount of storage needed (in gigabytes or terabytes), given so that the system administrators can prepare the appropriate storage systems for access by the project team",
137
                         sub_elements: [storage_size, storage_unit]
138
                        }
139
    storage_performance = { name: "Performance", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1,
15,500✔
140
                            label: "Storage Performance Expectations",
141
                            description: "The requested storage performance (default Standard)",
142
                            instructions: "The expected needs for storage performance, i.e. relative read/write and transfer speeds."\
143
                                          " The 'Standard' default for TigerData is balanced and tuned for moderate usage."\
144
                                          " Those who expect more intensive usage should select the 'Premium' option, while those who expect to simply store their data for long-term, low-usage should select the 'Eco' option",
145
                            attributes: [requested_attribute, approved_attribute]
146
                          }
147
    project_purpose = { name: "ProjectPurpose", type: "string", index: true, "min-occurs" => 1, "max-occurs" => 1, label: "Project Purpose",
15,500✔
148
                        description: "The high-level category for the purpose of the project (research, administrative, or library)",
149
                        instructions: "The high-level category for the purpose of the project: 'Research' (default), 'Administrative', or 'Library Archive'."
150
                      }
151
    requested_by = { name: "RequestedBy", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Requested By",
15,500✔
152
                     description: "The person who made the request",
153
                     instructions: "The person who made the request, given as a locally unique user."
154
                   }
155
    requested_date = { name: "RequestDateTime", type: "date", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Request Date-Time", description: "The date and time the request was made",
15,500✔
156
                       instructions: "The date and time the request was made, following ISO 8601 standards for timestamps."
157
                      }
158
    approved_by = { name: "ApprovedBy", type: "string", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Approved By",
15,500✔
159
                       description: "The person who approved the request",
160
                       instructions: "The person who approved the request, given as a locally unique user."
161
                     }
162
    approved_date = { name: "ApprovalDateTime", type: "date", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Approval Date-Time", description: "The date and time the request was approved",
15,500✔
163
                         instructions: "The date and time the request was approved, following ISO 8601 standards for timestamps"
164
                        }
165
    denied_by = { name: "DeniedBy", type: "string", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Denied  By",
15,500✔
166
                  description: "The person who denied the request",
167
                  instructions: "The person who denied the request, given as a locally unique user."
168
                }
169
    denial_date = { name: "DenialDateTime", type: "date", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Denial Date-Time", description: "The date and time the request was denied",
15,500✔
170
                       instructions: "The date and time the request was denied, following ISO 8601 standards for timestamps"
171
                      }
172
    note_by = { name: "NoteBy", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Note By", description: "The person making the note." }
15,500✔
173
    note_date = { name: "NoteDateTime", type: "date", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Note Date-Time", description: "The date and time the note was made" }
15,500✔
174
    note_type = { name: "EventType", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Event Type", description: "A general category label for the event note" }
15,500✔
175
    message = { name: "Message", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Message", description: "The plain-language message contents of the event note." }
15,500✔
176
    event_note = { name: "EventlNote", type: "document", index: false, "min-occurs" => 0, label: "Event Note(s)", description: "A supplementary record for a provenance event",
15,500✔
177
                   instructions: "A supplementary record of noteworthy details for a given provenance event (e.g., quota decisions, storage tier assignments, revisions to submitted metadata, explanations of extenuating circumstances, etc.)",
178
                   sub_elements: [note_by, note_date, note_type, message]
179
                 }
180
    submission = { name: "Submission", type: "document", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Submission", description: "A record of a project's initial submission",
15,500✔
181
                   instructions: "A record of a project's initial submission, including the request to create a new project and the approval or denial by system administrators.",
182
                   sub_elements: [requested_by, requested_date, approved_by, approved_date, denied_by, denial_date, event_note]
183
                 }
184
    revisions = { name: "Revision", type: "document", index: false, "min-occurs" => 0, "max-occurs" => 1, label: "Revision(s)", description: "A record of major revisions to an active project, if applicable",
15,500✔
185
                   instructions: "A record of major revisions to an active project, if applicable–i.e., those requiring a special request and approval from a system administrator, such as a change in the Data Sponsor or capacity and performance increases.",
186
                   sub_elements: [requested_by, requested_date, approved_by, approved_date, denied_by, denial_date, event_note]
187
                 }
188
    schema_version = { name: "SchemaVersion", type: "string", index: false, "min-occurs" => 1, "max-occurs" => 1, label: "Schema Version",
15,500✔
189
                       description: "The version of the TigerData Standard Metadata Schema used",
190
                       instructions: "The version of the TigerData Standard Metadata Schema used for this project or subproject record. Ordinarily, the version is recorded at the time of the (sub)project creation. Values are expected to follow the numerical semantic versioning convention."
191
                     }
192
   [project_directory, title, description, status, data_sponsor, data_manager, data_users, departments, created_on, created_by, updated_on, updated_by, project_id, storage_capacity,
15,500✔
193
     storage_performance, project_purpose, submission, revisions, schema_version]
194
  end
195
  # rubocop:enable Metrics/MethodLength
196
  def project_schema_fields
1✔
197
    self.class.project_schema_fields
3✔
198
  end
199

200
  def self.required_project_schema_fields
1✔
201
    project_schema_fields.select { |field| field["min-occurs"] > 0 }
309,940✔
202
  end
203

204
  private
1✔
205

206
    def aterm_element(field:, line_terminator:, line_start: "  ")
1✔
207
      new_line_start="#{line_start}  "
129✔
208
      field_command = "#{line_start}:element -name #{field[:name]} -type #{field[:type]}"
129✔
209
      field_command += " -index #{field[:index]}" if field[:index]
129✔
210
      field_command += " -min-occurs #{field['min-occurs']}"
129✔
211
      field_command += " -max-occurs #{field['max-occurs']}" if field["max-occurs"].present?
129✔
212
      field_command += " -label \"#{field[:label]}\"" if field[:label].present?
129✔
213
      field_command += line_terminator.to_s
129✔
214
      if field[:description].present? || field[:attributes].present? || field[:sub_elements]&.count > 0
129✔
215
        field_command += "#{new_line_start}<#{line_terminator}"
129✔
216
        indented_line_start="#{new_line_start}  "
129✔
217
        if field[:description].present?
129✔
218
          field_command += "#{indented_line_start}:description \"#{field[:description]}\"#{line_terminator}"
129✔
219
        end
220
        if field[:instructions].present?
129✔
221
          field_command += "#{indented_line_start}:instructions \"#{field[:instructions]}\"#{line_terminator}"
93✔
222
        end
223
        if field[:attributes].present?
129✔
224
          field[:attributes].each do |attribute|
12✔
225
            field_command += "#{indented_line_start}:attribute -name #{attribute[:name]} -type #{attribute[:type]} -min-occurs #{attribute['min-occurs']}"
21✔
226
            field_command += "-max-occurs #{attribute['max-occurs']} " if attribute["max-occurs"].present?
21✔
227
            field_command += "#{line_terminator}#{indented_line_start}  < :description \"#{attribute[:description]}\" >"
21✔
228
            field_command += line_terminator.to_s
21✔
229
          end
230
        end
231
        field[:sub_elements]&.each do |sub_field|
129✔
232
          field_command += aterm_element(field: sub_field, line_terminator:, line_start: indented_line_start )
72✔
233
        end
234
        field_command += "#{new_line_start}>#{line_terminator}"
129✔
235
      end
236
      field_command
129✔
237
    end
238
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc