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

pulibrary / tigerdata-app / 3d88d2e1-809d-4b21-a053-6167ddb1e670

06 Nov 2025 03:55PM UTC coverage: 91.201% (+10.5%) from 80.673%
3d88d2e1-809d-4b21-a053-6167ddb1e670

push

circleci

web-flow
Fixed link, replaced image, added a test (#2158)

Closes #2156 and #2141

2840 of 3114 relevant lines covered (91.2%)

1092.36 hits per line

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

98.48
/app/models/mediaflux/asset_metadata_request.rb
1
# frozen_string_literal: true
2
module Mediaflux
6✔
3
  # Get metadata about an asset in mediaflux
4
  # @example
5
  #   metadata_request = Mediaflux::AssetMetadataRequest.new(
6
  #   session_token: current_user.mediaflux_session, id: mediaflux_id).metadata
7
  class AssetMetadataRequest < Request
6✔
8
    attr_reader :id
6✔
9

10
    # Constructor
11
    # @param session_token [String] the API token for the authenticated session
12
    # @param id [Integer] Id of the Asset to return the metadata for
13
    def initialize(session_token:, id:)
6✔
14
      super(session_token: session_token)
920✔
15
      @id = id
920✔
16
    end
17

18
    # Specifies the Mediaflux service to use when getting asset metadata
19
    # @return [String]
20
    def self.service
6✔
21
      "asset.get"
1,840✔
22
    end
23

24
    # parse the returned XML into a hash about the asset that can be utilized
25
    def metadata
6✔
26
      xml = response_xml
912✔
27
      asset = xml.xpath("/response/reply/result/asset")
904✔
28
      metadata = parse(asset)
904✔
29

30
      if metadata[:collection]
904✔
31
        metadata[:total_file_count] = asset.xpath("./collection/accumulator/value/non-collections").text
898✔
32
        metadata[:size] = asset.xpath("./collection/accumulator/value/total/@h").text
898✔
33
        metadata[:accum_names] = asset.xpath("./collection/accumulator/@name")
898✔
34
        metadata[:ctime] = asset.xpath("./ctime")
898✔
35
      end
36

37
      parse_image(asset.xpath("./meta/mf-image"), metadata) # this does not do anything because mf-image is not a part of the meta xpath
904✔
38

39
      parse_note(asset.xpath("./meta/mf-note"), metadata) # this does not do anything because mf-note is not a part of the meta xpath
904✔
40

41
      parse_quota(asset.xpath("./collection/quota"), metadata)
904✔
42
      metadata
904✔
43
    end
44

45
    private
6✔
46

47
      def build_http_request_body(name:)
6✔
48
        super do |xml|
1,840✔
49
          xml.args do
1,840✔
50
            xml.id id
1,840✔
51
          end
52
        end
53
      end
54

55
      def parse_note(note, metadata)
6✔
56
        if note.count > 0
904✔
57
          metadata[:mf_note] = note.text
2✔
58
        end
59
      end
60

61
      def parse_image(image, metadata)
6✔
62
        if image.count > 0
904✔
63
          metadata[:image_size] = image.xpath("./width").text + " X " + image.xpath("./height").text
×
64
        end
65
      end
66

67
      def parse_quota(quota, metadata)
6✔
68
        metadata[:quota_allocation] = quota.xpath("./allocation/@h").text
904✔
69
        metadata[:quota_allocation_raw] = quota.xpath("./allocation").text.to_i
904✔
70
        metadata[:quota_used] = quota.xpath("./used/@h").text
904✔
71
        metadata[:quota_used_raw] = quota.xpath("./used").text.to_i
904✔
72
      end
73

74
      # Update this to match full 0.6.1 schema
75
      def parse(asset)
6✔
76
        {
77
          id: asset.xpath("./@id").text,
904✔
78
          name: asset.xpath("./name").text,
79
          creator: asset.xpath("./creator/user").text,
80
          description: asset.xpath("./description").text,
81
          collection: asset.xpath("./@collection")&.text == "true",
82
          path: asset.xpath("./path").text,
83
          type: asset.xpath("./type").text,
84
          namespace: asset.xpath("./namespace").text,
85
          accumulators: asset.xpath("./collection/accumulator/value") # list of accumulator values in xml format. Can parse further through xpath
86
        }.merge(parse_project(asset.xpath("//tigerdata:project", "tigerdata" => "tigerdata").first, asset))
87
      end
88

89
      # rubocop:disable Metrics/MethodLength
90
      def parse_project(project, asset)
6✔
91
        return {} if project.blank?
904✔
92
        metadata = {
93
          description: project.xpath("./Description").text,
896✔
94
          data_sponsor: project.xpath("./DataSponsor").text,
95
          data_manager: project.xpath("./DataManager").text,
96
          departments: project.xpath("./Department").children.map(&:text),
97
          project_directory: project.xpath("./ProjectDirectory").text,
98
          project_id: project.xpath("./ProjectID").text,
99
          submission: parse_submission(project),
100
          title: project.xpath("./Title").text,
101
          project_purpose: project.xpath("./ProjectPurpose").text
102
        }
103
        metadata.merge!(parse_data_users(asset, project))
896✔
104
        metadata.merge!(parse_project_dates(project))
896✔
105
        metadata.merge!(parse_storage_options(project))
896✔
106
      end
107
      # rubocop:enable Metrics/MethodLength
108

109
      # NOTE: We are still using the "DataUser" attribute in the Project Metadata
110
      # to drive the list of users who can read and/or write to the Mediaflux
111
      # asset. In the future we could go by the ACL information in the asset
112
      # alone (and bypass the DataUser attribute) and that will be more accurate.
113
      def parse_data_users(asset, project)
6✔
114
        data_users = data_users_from_string(project.xpath("./DataUser").text)
896✔
115
        rw_users = parse_read_write_users(asset, data_users)
896✔
116
        {
117
          rw_users: rw_users,
896✔
118
          ro_users: data_users - rw_users
119
        }
120
      end
121

122
      def parse_project_dates(project)
6✔
123
        {
124
          created_by: project.xpath("./CreatedBy").text,
896✔
125
          created_on: project.xpath("./CreatedOn").text,
126
          updated_by: project.xpath("./UpdatedBy").text,
127
          updated_on: project.xpath("./UpdatedOn").text
128
        }
129
      end
130

131
      def parse_storage_options(project)
6✔
132
        {
133
          number_of_files: project.xpath("./NumberofFiles").text,
896✔
134
          hpc: project.xpath("./Hpc").text == "true",
135
          smb: project.xpath("./Smb").text == "true",
136
          globus: project.xpath("./Globus").text == "true"
137
        }
138
      end
139

140
      def parse_submission(project)
6✔
141
        submission = project.xpath("./Submission")
896✔
142
        {
143
          requested_by: submission.xpath("./RequestedBy").text,
896✔
144
          requested_on: submission.xpath("./RequestDateTime").text,
145
          approved_by: submission.xpath("./ApprovedBy").text,
146
          approved_on: submission.xpath("./ApprovalDateTime").text
147
        }
148
      end
149

150
      def data_users_from_string(users)
6✔
151
        return [] if users.blank?
896✔
152
        users.split(",").compact_blank
124✔
153
      end
154

155
      # Calculates which of the `data_users` listed in the metadata have
156
      # write access in Mediaflux for the given asset.
157
      def parse_read_write_users(asset, data_users)
6✔
158
        users = asset.xpath("./acl").map do |acl|
896✔
159
          uid = acl.xpath("./actor").text.gsub("princeton:", "")
1,364✔
160
          if data_users.include?(uid) && acl.xpath("./metadata").map(&:text).include?("write")
1,364✔
161
            uid
22✔
162
          else
163
            ""
1,342✔
164
          end
165
        end
166
        users.compact_blank
896✔
167
      end
168
  end
169
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