4232: remove "dependencies" entries from examples in the tutorials if the API is...
[arvados.git] / apps / workbench / app / controllers / projects_controller.rb
1 class ProjectsController < ApplicationController
2   before_filter :set_share_links, if: -> { defined? @object }
3
4   def model_class
5     Group
6   end
7
8   def find_object_by_uuid
9     if current_user and params[:uuid] == current_user.uuid
10       @object = current_user.dup
11       @object.uuid = current_user.uuid
12       class << @object
13         def name
14           'Home'
15         end
16         def description
17           ''
18         end
19         def attribute_editable? attr, *args
20           case attr
21           when 'description', 'name'
22             false
23           else
24             super
25           end
26         end
27       end
28     else
29       super
30     end
31   end
32
33   def index_pane_list
34     %w(Projects)
35   end
36
37   # Returning an array of hashes instead of an array of strings will allow
38   # us to tell the interface to get counts for each pane (using :filters).
39   # It also seems to me that something like these could be used to configure the contents of the panes.
40   def show_pane_list
41     pane_list = [
42       {
43         :name => 'Data_collections',
44         :filters => [%w(uuid is_a arvados#collection)]
45       },
46       {
47         :name => 'Jobs_and_pipelines',
48         :filters => [%w(uuid is_a) + [%w(arvados#job arvados#pipelineInstance)]]
49       },
50       {
51         :name => 'Pipeline_templates',
52         :filters => [%w(uuid is_a arvados#pipelineTemplate)]
53       },
54       {
55         :name => 'Subprojects',
56         :filters => [%w(uuid is_a arvados#group)]
57       },
58       { :name => 'Other_objects',
59         :filters => [%w(uuid is_a) + [%w(arvados#human arvados#specimen arvados#trait)]]
60       }
61     ]
62     pane_list << { :name => 'Sharing',
63                    :count => @share_links.count } if @user_is_manager
64     pane_list << { :name => 'Advanced' }
65   end
66
67   # Called via AJAX and returns Javascript that populates tab counts into tab titles.
68   # References #show_pane_list action which should return an array of hashes each with :name
69   # and then optionally a :filters to run or a straight up :count
70   #
71   # This action could easily be moved to the ApplicationController to genericize the tab_counts behaviour,
72   # but one or more new routes would have to be created, the js.erb would also have to be moved
73   def tab_counts
74     @tab_counts = {}
75     show_pane_list.each do |pane|
76       if pane.is_a?(Hash)
77         if pane[:count]
78           @tab_counts[pane[:name]] = pane[:count]
79         elsif pane[:filters]
80           @tab_counts[pane[:name]] = @object.contents(filters: pane[:filters]).items_available
81         end
82       end
83     end
84   end
85
86   def remove_item
87     params[:item_uuids] = [params[:item_uuid]]
88     remove_items
89     render template: 'projects/remove_items'
90   end
91
92   def remove_items
93     @removed_uuids = []
94     links = []
95     params[:item_uuids].collect { |uuid| ArvadosBase.find uuid }.each do |item|
96       if (item.class == Link and
97           item.link_class == 'name' and
98           item.tail_uuid == @object.uuid)
99         # Given uuid is a name link, linking an object to this
100         # project. First follow the link to find the item we're removing,
101         # then delete the link.
102         links << item
103         item = ArvadosBase.find item.head_uuid
104       else
105         # Given uuid is an object. Delete all names.
106         links += Link.where(tail_uuid: @object.uuid,
107                             head_uuid: item.uuid,
108                             link_class: 'name')
109       end
110       links.each do |link|
111         @removed_uuids << link.uuid
112         link.destroy
113       end
114       if item.owner_uuid == @object.uuid
115         # Object is owned by this project. Remove it from the project by
116         # changing owner to the current user.
117         begin
118           item.update_attributes owner_uuid: current_user.uuid
119           @removed_uuids << item.uuid
120         rescue ArvadosApiClient::ApiErrorResponseException => e
121           if e.message.include? 'collection_owner_uuid_name_unique'
122             rename_to = item.name + ' removed from ' +
123                         (@object.name ? @object.name : @object.uuid) +
124                         ' at ' + Time.now.to_s
125             updates = {}
126             updates[:name] = rename_to
127             updates[:owner_uuid] = current_user.uuid
128             item.update_attributes updates
129             @removed_uuids << item.uuid
130           else
131             raise
132           end
133         end
134       end
135     end
136   end
137
138   def destroy
139     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
140                                   ['tail_uuid','=',@object.uuid]])).any?
141       objects.each do |object|
142         object.destroy
143       end
144     end
145     while (objects = @object.contents(include_linked: false)).any?
146       objects.each do |object|
147         object.update_attributes! owner_uuid: current_user.uuid
148       end
149     end
150     if ArvadosBase::resource_class_for_uuid(@object.owner_uuid) == Group
151       params[:return_to] ||= group_path(@object.owner_uuid)
152     else
153       params[:return_to] ||= projects_path
154     end
155     super
156   end
157
158   def find_objects_for_index
159     @objects = all_projects
160     super
161   end
162
163   def load_contents_objects kinds=[]
164     kind_filters = @filters.select do |attr,op,val|
165       op == 'is_a' and val.is_a? Array and val.count > 1
166     end
167     if /^created_at\b/ =~ @order[0] and kind_filters.count == 1
168       # If filtering on multiple types and sorting by date: Get the
169       # first page of each type, sort the entire set, truncate to one
170       # page, and use the last item on this page as a filter for
171       # retrieving the next page. Ideally the API would do this for
172       # us, but it doesn't (yet).
173
174       # To avoid losing items that have the same created_at as the
175       # last item on this page, we retrieve an overlapping page with a
176       # "created_at <= last_created_at" filter, then remove duplicates
177       # with a "uuid not in [...]" filter (see below).
178       nextpage_operator = /\bdesc$/i =~ @order[0] ? '<=' : '>='
179
180       @objects = []
181       @name_link_for = {}
182       kind_filters.each do |attr,op,val|
183         (val.is_a?(Array) ? val : [val]).each do |type|
184           objects = @object.contents(order: @order,
185                                      limit: @limit,
186                                      include_linked: true,
187                                      filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
188                                     )
189           objects.each do |object|
190             @name_link_for[object.andand.uuid] = objects.links_for(object, 'name').first
191           end
192           @objects += objects
193         end
194       end
195       @objects = @objects.to_a.sort_by(&:created_at)
196       @objects.reverse! if nextpage_operator == '<='
197       @objects = @objects[0..@limit-1]
198       @next_page_filters = @filters.reject do |attr,op,val|
199         (attr == 'created_at' and op == nextpage_operator) or
200           (attr == 'uuid' and op == 'not in')
201       end
202
203       if @objects.any?
204         last_created_at = @objects.last.created_at
205
206         last_uuids = []
207         @objects.each do |obj|
208           last_uuids << obj.uuid if obj.created_at.eql?(last_created_at)
209         end
210
211         @next_page_filters += [['created_at',
212                                 nextpage_operator,
213                                 last_created_at]]
214         @next_page_filters += [['uuid', 'not in', last_uuids]]
215         @next_page_href = url_for(partial: :contents_rows,
216                                   limit: @limit,
217                                   filters: @next_page_filters.to_json)
218       else
219         @next_page_href = nil
220       end
221     else
222       @objects = @object.contents(order: @order,
223                                   limit: @limit,
224                                   include_linked: true,
225                                   filters: @filters,
226                                   offset: @offset)
227       @next_page_href = next_page_href(partial: :contents_rows,
228                                        filters: @filters.to_json,
229                                        order: @order.to_json)
230     end
231
232     preload_links_for_objects(@objects.to_a)
233   end
234
235   def show
236     if !@object
237       return render_not_found("object not found")
238     end
239
240     if params[:partial]
241       load_contents_objects
242       respond_to do |f|
243         f.json {
244           render json: {
245             content: render_to_string(partial: 'show_contents_rows.html',
246                                       formats: [:html]),
247             next_page_href: @next_page_href
248           }
249         }
250       end
251     else
252       @objects = []
253       super
254     end
255   end
256
257   def create
258     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
259     @new_resource_attrs[:name] ||= 'New project'
260     super
261   end
262
263   def update
264     @updates = params['project']
265     super
266   end
267
268   helper_method :get_objects_and_names
269   def get_objects_and_names(objects=nil)
270     objects = @objects if objects.nil?
271     objects_and_names = []
272     objects.each do |object|
273       if objects.respond_to? :links_for and
274           !(name_links = objects.links_for(object, 'name')).empty?
275         name_links.each do |name_link|
276           objects_and_names << [object, name_link]
277         end
278       elsif @name_link_for.andand[object.uuid]
279         objects_and_names << [object, @name_link_for[object.uuid]]
280       elsif object.respond_to? :name
281         objects_and_names << [object, object]
282       else
283         objects_and_names << [object,
284                                Link.new(owner_uuid: @object.uuid,
285                                         tail_uuid: @object.uuid,
286                                         head_uuid: object.uuid,
287                                         link_class: "name",
288                                         name: "")]
289
290       end
291     end
292     objects_and_names
293   end
294 end