10111: Merge branch 'master' into 10111-collection-labels
[arvados.git] / services / api / app / controllers / arvados / v1 / groups_controller.rb
1 class Arvados::V1::GroupsController < ApplicationController
2
3   def self._contents_requires_parameters
4     params = _index_requires_parameters.
5       merge({
6               uuid: {
7                 type: 'string', required: false, default: nil
8               },
9             })
10     params.delete(:select)
11     params
12   end
13
14   def render_404_if_no_object
15     if params[:action] == 'contents'
16       if !params[:uuid]
17         # OK!
18         @object = nil
19         true
20       elsif @object
21         # Project group
22         true
23       elsif (@object = User.where(uuid: params[:uuid]).first)
24         # "Home" pseudo-project
25         true
26       else
27         super
28       end
29     else
30       super
31     end
32   end
33
34   def contents
35     load_searchable_objects
36     send_json({
37       :kind => "arvados#objectList",
38       :etag => "",
39       :self_link => "",
40       :offset => @offset,
41       :limit => @limit,
42       :items_available => @items_available,
43       :items => @objects.as_api_response(nil)
44     })
45   end
46
47   protected
48
49   def load_searchable_objects
50     all_objects = []
51     @items_available = 0
52
53     # Trick apply_where_limit_order_params into applying suitable
54     # per-table values. *_all are the real ones we'll apply to the
55     # aggregate set.
56     limit_all = @limit
57     offset_all = @offset
58     # save the orders from the current request as determined by load_param,
59     # but otherwise discard them because we're going to be getting objects
60     # from many models
61     request_orders = @orders.clone
62     @orders = []
63
64     request_filters = @filters
65
66     klasses = [Group,
67      Job, PipelineInstance, PipelineTemplate, ContainerRequest, Workflow,
68      Collection,
69      Human, Specimen, Trait]
70
71     table_names = Hash[klasses.collect { |k| [k, k.table_name] }]
72
73     disabled_methods = Rails.configuration.disable_api_methods
74     avail_klasses = table_names.select{|k, t| !disabled_methods.include?(t+'.index')}
75     klasses = avail_klasses.keys
76
77     request_filters.each do |col, op, val|
78       if col.index('.') && !table_names.values.include?(col.split('.', 2)[0])
79         raise ArgumentError.new("Invalid attribute '#{col}' in filter")
80       end
81     end
82
83     wanted_klasses = []
84     request_filters.each do |col,op,val|
85       if op == 'is_a'
86         (val.is_a?(Array) ? val : [val]).each do |type|
87           type = type.split('#')[-1]
88           type[0] = type[0].capitalize
89           wanted_klasses << type
90         end
91       end
92     end
93
94     seen_last_class = false
95     klasses.each do |klass|
96       @offset = 0 if seen_last_class  # reset offset for the new next type being processed
97
98       # if current klass is same as params['last_object_class'], mark that fact
99       seen_last_class = true if((params['count'].andand.==('none')) and
100                                 (params['last_object_class'].nil? or
101                                  params['last_object_class'].empty? or
102                                  params['last_object_class'] == klass.to_s))
103
104       # if klasses are specified, skip all other klass types
105       next if wanted_klasses.any? and !wanted_klasses.include?(klass.to_s)
106
107       # don't reprocess klass types that were already seen
108       next if params['count'] == 'none' and !seen_last_class
109
110       # don't process rest of object types if we already have needed number of objects
111       break if params['count'] == 'none' and all_objects.size >= limit_all
112
113       # If the currently requested orders specifically match the
114       # table_name for the current klass, apply that order.
115       # Otherwise, order by recency.
116       request_order =
117         request_orders.andand.find { |r| r =~ /^#{klass.table_name}\./i } ||
118         klass.default_orders.join(", ")
119
120       @select = nil
121       where_conds = {}
122       where_conds[:owner_uuid] = @object.uuid if @object
123       if klass == Collection
124         @select = klass.selectable_attributes - ["manifest_text"]
125       elsif klass == Group
126         where_conds[:group_class] = "project"
127       end
128
129       @filters = request_filters.map do |col, op, val|
130         if !col.index('.')
131           [col, op, val]
132         elsif (col = col.split('.', 2))[0] == klass.table_name
133           [col[1], op, val]
134         else
135           nil
136         end
137       end.compact
138
139       @objects = klass.readable_by(*@read_users).
140         order(request_order).where(where_conds)
141       klass_limit = limit_all - all_objects.count
142       @limit = klass_limit
143       apply_where_limit_order_params klass
144       klass_object_list = object_list(model_class: klass)
145       klass_items_available = klass_object_list[:items_available] || 0
146       @items_available += klass_items_available
147       @offset = [@offset - klass_items_available, 0].max
148       all_objects += klass_object_list[:items]
149
150       if klass_object_list[:limit] < klass_limit
151         # object_list() had to reduce @limit to comply with
152         # max_index_database_read. From now on, we'll do all queries
153         # with limit=0 and just accumulate items_available.
154         limit_all = all_objects.count
155       end
156     end
157
158     @objects = all_objects
159     @limit = limit_all
160     @offset = offset_all
161   end
162
163 end