12519: Moved multisite search to its own URL
[arvados.git] / apps / workbench / app / assets / javascripts / components / search.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 window.SearchResultsTable = {
6     maybeLoadMore: function(dom) {
7         var loader = this.loader
8         if (loader.state != loader.READY)
9             // Can't start getting more items anyway: no point in
10             // checking anything else.
11             return
12         var contentRect = dom.getBoundingClientRect()
13         var scroller = window // TODO: use dom's nearest ancestor with scrollbars
14         if (contentRect.bottom < 2 * scroller.innerHeight) {
15             // We have less than 1 page worth of content available
16             // below the visible area. Load more.
17             loader.loadMore()
18             // Indicate loading is in progress.
19             window.requestAnimationFrame(m.redraw)
20         }
21     },
22     oncreate: function(vnode) {
23         vnode.state.maybeLoadMore = vnode.state.maybeLoadMore.bind(vnode.state, vnode.dom)
24         window.addEventListener('scroll', vnode.state.maybeLoadMore)
25         window.addEventListener('resize', vnode.state.maybeLoadMore)
26         vnode.state.timer = window.setInterval(vnode.state.maybeLoadMore, 200)
27         vnode.state.loader = vnode.attrs.loader
28         vnode.state.onupdate(vnode)
29     },
30     onupdate: function(vnode) {
31         vnode.state.loader = vnode.attrs.loader
32     },
33     onremove: function(vnode) {
34         window.clearInterval(vnode.state.timer)
35         window.removeEventListener('scroll', vnode.state.maybeLoadMore)
36         window.removeEventListener('resize', vnode.state.maybeLoadMore)
37     },
38     view: function(vnode) {
39         var loader = vnode.attrs.loader
40         return m('table.table.table-condensed', [
41             m('thead', m('tr', [
42                 m('th'),
43                 m('th', 'uuid'),
44                 m('th', 'name'),
45                 m('th', 'last modified'),
46             ])),
47             m('tbody', [
48                 loader.items().map(function(item) {
49                     return m('tr', [
50                         m('td', [
51                             item.workbenchBaseURL() &&
52                                 m('a.btn.btn-xs.btn-default', {
53                                     title: 'Show '+item.objectType.description,
54                                     href: item.workbenchBaseURL()+'/'+item.objectType.wb_path+'/'+item.uuid,
55                                 }, item.objectType.label),
56                         ]),
57                         m('td.arvados-uuid', item.uuid),
58                         m('td', item.name || '(unnamed)'),
59                         m('td', m(LocalizedDateTime, {parse: item.modified_at})),
60                     ])
61                 }),
62             ]),
63             loader.state == loader.DONE ? null : m('tfoot', m('tr', [
64                 m('th[colspan=4]', m('button.btn.btn-xs', {
65                     className: loader.state == loader.LOADING ? 'btn-default' : 'btn-primary',
66                     style: {
67                         display: 'block',
68                         width: '12em',
69                         marginLeft: 'auto',
70                         marginRight: 'auto',
71                     },
72                     disabled: loader.state == loader.LOADING,
73                     onclick: function() {
74                         loader.loadMore()
75                         return false
76                     },
77                 }, loader.state == loader.LOADING ? '(loading)' : 'Load more')),
78             ])),
79         ])
80     },
81 }
82
83 window.Search = {
84     oninit: function(vnode) {
85         vnode.state.sessionDB = new SessionDB()
86         vnode.state.searchEntered = m.stream()
87         vnode.state.searchActive = m.stream()
88         // When searchActive changes (e.g., when restoring state
89         // after navigation), update the text field too.
90         vnode.state.searchActive.map(vnode.state.searchEntered)
91         // When searchActive changes, create a new loader that filters
92         // with the given search term.
93         vnode.state.searchActive.map(function(q) {
94             var sessions = vnode.state.sessionDB.loadActive()
95             vnode.state.loader = new MergingLoader({
96                 children: Object.keys(sessions).map(function(key) {
97                     var session = sessions[key]
98                     var workbenchBaseURL = function() {
99                         return vnode.state.sessionDB.workbenchBaseURL(session)
100                     }
101                     var searchable_objects = [
102                         {
103                             wb_path: 'groups',
104                             api_path: 'arvados/v1/groups',
105                             filters: [['group_class', '=', 'project']],
106                             label: 'P',
107                             description: 'Project',
108                         },
109                         {
110                             wb_path: 'collections',
111                             api_path: 'arvados/v1/collections',
112                             filters: [],
113                             label: 'C',
114                             description: 'Collection',
115                         },
116                     ]
117                     return new MergingLoader({
118                         sessionKey: key,
119                         // For every session, search for every object type
120                         children: searchable_objects.map(function(obj_type){
121                             return new MultipageLoader({
122                                 sessionKey: key,
123                                 objectKind: obj_type.label,
124                                 loadFunc: function(filters) {
125                                     var tsquery = to_tsquery(q)
126                                     if (tsquery) {
127                                         filters = filters.slice(0)
128                                         filters.push(['any', '@@', tsquery])
129                                     }
130                                     // Apply additional type dependant filters, if any.
131                                     for (var f of obj_type.filters) {
132                                         filters.push(f)
133                                     }
134                                     return vnode.state.sessionDB.request(session, obj_type.api_path, {
135                                         data: {
136                                             filters: JSON.stringify(filters),
137                                             count: 'none',
138                                         },
139                                     }).then(function(resp) {
140                                         resp.items.map(function(item) {
141                                             item.workbenchBaseURL = workbenchBaseURL
142                                             item.objectType = obj_type
143                                         })
144                                         return resp
145                                     })
146                                 },
147                             })
148                         })
149                     })
150                 })
151             })
152         })
153     },
154     view: function(vnode) {
155         var sessions = vnode.state.sessionDB.loadAll()
156         return m('form', {
157             onsubmit: function() {
158                 vnode.state.searchActive(vnode.state.searchEntered())
159                 vnode.state.forgetSavedHeight = true
160                 return false
161             },
162         }, [
163             m(SaveUIState, {
164                 defaultState: '',
165                 currentState: vnode.state.searchActive,
166                 forgetSavedHeight: vnode.state.forgetSavedHeight,
167                 saveBodyHeight: true,
168             }),
169             vnode.state.loader && [
170                 m('.row', [
171                     m('.col-md-6', [
172                         m('.input-group', [
173                             m('input#search.form-control[placeholder=Search collections and projects]', {
174                                 oninput: m.withAttr('value', vnode.state.searchEntered),
175                                 value: vnode.state.searchEntered(),
176                             }),
177                             m('.input-group-btn', [
178                                 m('input.btn.btn-primary[type=submit][value="Search"]'),
179                             ]),
180                         ]),
181                     ]),
182                     m('.col-md-6', [
183                         'Searching sites: ',
184                         vnode.state.loader.children.length == 0
185                             ? m('span.label.label-xs.label-danger', 'none')
186                             : vnode.state.loader.children.map(function(child) {
187                                 return [m('span.label.label-xs', {
188                                     className: child.state == child.LOADING ? 'label-warning' : 'label-success',
189                                 }, child.sessionKey), ' ']
190                             }),
191                         ' ',
192                         m('a[href="/sessions"]', 'Add/remove sites'),
193                     ]),
194                 ]),
195                 m(SearchResultsTable, {
196                     loader: vnode.state.loader,
197                 }),
198             ],
199         ])
200     },
201 }