aleph_analytics 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/app/assets/javascripts/angular/aleph.js.es6 +9 -4
- data/app/assets/javascripts/angular/controllers/alerts/alert_show_controller.js.es6 +4 -4
- data/app/assets/javascripts/angular/controllers/controllers.js.es6 +1 -0
- data/app/assets/javascripts/angular/controllers/query/query_index_controller.js.es6 +2 -5
- data/app/assets/javascripts/angular/controllers/query/query_repl_controller.js.es6 +33 -9
- data/app/assets/javascripts/angular/controllers/result/single_result_show_controller.js.es6 +26 -0
- data/app/assets/javascripts/angular/directives/query/query_details_directive.js.es6 +0 -1
- data/app/assets/javascripts/angular/directives/result/results_directive.js.es6 +17 -0
- data/app/assets/javascripts/angular/services/lib/open_repl_service.js.es6 +4 -27
- data/app/assets/javascripts/angular/services/query/query_handler.js.es6 +2 -2
- data/app/assets/stylesheets/shared.css.sass +45 -0
- data/app/assets/stylesheets/sidebar.css.sass +0 -30
- data/app/controllers/results_controller.rb +1 -0
- data/app/models/alert.rb +1 -1
- data/app/models/query_version.rb +1 -1
- data/app/models/result.rb +1 -1
- data/app/models/visualization.rb +1 -1
- data/app/views/layouts/application.html.haml +2 -0
- data/app/views/queries/_query_version_sidebar.html.haml +1 -1
- data/app/views/queries/_results.html.haml +9 -0
- data/app/views/results/_show.html.haml +2 -0
- data/app/views/results/_single_result_show.html.haml +9 -0
- data/public/assets/.sprockets-manifest-f2e907b8dd5ccd1b5394fc3d2f4c2f5b.json +1 -0
- data/public/assets/angular/{aleph.js-19b4df67407de5dab249abfc391c0eaf.es6 → aleph.js-5c6fb1f7eaa833a4e9fc35bca222deb7.es6} +9 -4
- data/public/assets/angular/controllers/alerts/{alert_show_controller.js-a984dc82fbcac380cdc8682bf8986933.es6 → alert_show_controller.js-550f7fd183df9504c71d18ce7251b3d0.es6} +4 -4
- data/public/assets/angular/controllers/{controllers.js-7431ae468ea2f0d392661448a61cfdf3.es6 → controllers.js-94cb19ce7a5c88bfe6832a75a90b39d6.es6} +1 -0
- data/public/assets/angular/controllers/query/{query_index_controller.js-828eadeee6a971b11b5c07f65f0c6def.es6 → query_index_controller.js-75f9ac815f2e2dd3eae846d16e07ac25.es6} +2 -5
- data/public/assets/angular/controllers/query/{query_repl_controller.js-3c4bac22e41766e2e83e720d53acade3.es6 → query_repl_controller.js-0448a8eed79f78efe189d454626a714f.es6} +33 -9
- data/public/assets/angular/controllers/result/single_result_show_controller.js-e18738b84c1ebe7c12707706cb876188.es6 +26 -0
- data/public/assets/angular/directives/query/{query_details_directive.js-2dd979e2463826558f166d9dc9e2c8a9.es6 → query_details_directive.js-0f6ac0f0515854a026ec6582e07e76b8.es6} +0 -1
- data/public/assets/angular/directives/result/{results_directive.js-fcbf9750790823a11ca313e630bd2b0f.es6 → results_directive.js-1931bff514e14d41ecf47f73fd7e24e2.es6} +17 -0
- data/public/assets/angular/services/lib/open_repl_service.js-cf17778946bb990beecd1c32bfcc668d.es6 +33 -0
- data/public/assets/angular/services/query/{query_handler.js-d8cb3c66bcf6d16cd13bb5bcd0921496.es6 → query_handler.js-23a60003545f1f1381bc0c98be8f121c.es6} +2 -2
- data/public/assets/{application-ef5c2ae95804ac9785f190a9aea7ad8b.js → application-119e0de54b9da9bf755ed98f87f1fab2.js} +8 -8
- data/public/assets/{application-9153e0bf570016651e5eb8c67e037d3a.css → application-dfbb8b5c9f13286f32601250c5aef5e8.css} +2 -2
- data/public/assets/resque_web/{application-d2bc6e68cf7c6215e804fe9e3f46f1d0.js → application-705d1a4a22a72f773a8cd5c70ef9b284.js} +4 -4
- data/vendor/assets/javascripts/angular-clipboard/angular-clipboard.js +83 -0
- data/vendor/assets/stylesheets/angular-spinner/treasure-overlay-spinner.css +2 -2
- metadata +19 -16
- data/public/assets/.sprockets-manifest-bd008d70493ba1dbee54c64e00f373a4.json +0 -1
- data/public/assets/angular/services/lib/open_repl_service.js-2332490470ea58f3e5ce7f6fe5dd2659.es6 +0 -56
- data/vendor/assets/javascripts/ng-clip/ngClip.js +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba0eaef53bed7d98b1a209c8b4bdb4bd23d8b59c
|
4
|
+
data.tar.gz: d581fabceec434ea16c4cc5e915d8b14517cb9db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb813201164b87f46061fe0ce6743120daa8774c5bcd3260dad1d9484fe2288f00c71752aa08186ab60701596f4460dd4c9741a385d2df9e29b85a17409bf977
|
7
|
+
data.tar.gz: 308a0d37c2c5bbb5cd4b3a09e089cc7c189522fafa2e55469e2576c20b6b036f6a1db339b47caf87970e7c6ef7e0b70c82f039a650b538a92913c54a2c9899a6
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file using [Semantic Versioning](http://semver.org/).
|
3
3
|
|
4
|
+
## [0.0.5] - 2016-09-26
|
5
|
+
### Features
|
6
|
+
- [Link to specific result / single result page](https://github.com/lumoslabs/aleph/issues/12)
|
7
|
+
- [Use shift enter as hotkey to run queries in REPL](https://github.com/lumoslabs/aleph/issues/16)
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
- [Alerts searching bug](https://github.com/lumoslabs/aleph/issues/15)
|
11
|
+
- [Alerts sorting bug](https://github.com/lumoslabs/aleph/issues/13)
|
12
|
+
- [Query saving after REPL closes issue](https://github.com/lumoslabs/aleph/issues/14)
|
4
13
|
|
5
14
|
## [0.0.4] - 2016-07-12
|
6
15
|
### Fixed
|
7
|
-
- Saving QueryRoles
|
16
|
+
- Saving QueryRoles bug
|
8
17
|
- Streamline sign out page
|
@@ -8,7 +8,7 @@
|
|
8
8
|
'alephServices',
|
9
9
|
'alephFilters',
|
10
10
|
'ngAnimate',
|
11
|
-
'
|
11
|
+
'angular-clipboard',
|
12
12
|
'ui.bootstrap',
|
13
13
|
'infinite-scroll',
|
14
14
|
'ngTagsInput',
|
@@ -27,9 +27,9 @@
|
|
27
27
|
}])
|
28
28
|
|
29
29
|
.value('KeyBindings', {
|
30
|
-
saveQuery: new KeyBinding('Save Query', {mac: 'command+shift+s', win:'ctrl+shift+s'}),
|
31
|
-
runQuery: new KeyBinding('Run Query', {mac: '
|
32
|
-
detectParameters: new KeyBinding('Detect Parameters', {mac: 'command+shift+p', win:'ctrl+shift+p'})
|
30
|
+
saveQuery: new KeyBinding('Save Query', { mac: 'command+shift+s', win: 'ctrl+shift+s' }),
|
31
|
+
runQuery: new KeyBinding('Run Query', { mac: 'shift+enter', win: 'shift+enter' }),
|
32
|
+
detectParameters: new KeyBinding('Detect Parameters', { mac: 'command+shift+p', win: 'ctrl+shift+p' })
|
33
33
|
})
|
34
34
|
|
35
35
|
.config(['$locationProvider', $locationProvider => {
|
@@ -79,6 +79,11 @@
|
|
79
79
|
controller: 'AlertShowController',
|
80
80
|
controllerAs: 'alertShowCtrl'
|
81
81
|
})
|
82
|
+
.when('/results/query/:queryId/query_version/:queryVersionId/result/:resultId', {
|
83
|
+
templateUrl: 'singleResultShow',
|
84
|
+
controller: 'SingleResultShowController',
|
85
|
+
controllerAs: 'singleResultShowCtrl'
|
86
|
+
})
|
82
87
|
.when('/snippets', {
|
83
88
|
title: 'Snippets',
|
84
89
|
templateUrl: 'snippetIndex',
|
@@ -19,8 +19,8 @@
|
|
19
19
|
if ($routeParams.alertId === 'new') {
|
20
20
|
this.alert.initItem()
|
21
21
|
.then(this.query.initItem.bind(this.query, null))
|
22
|
-
.then(this._openReplService.open.bind(this._openReplService, {}))
|
23
|
-
.then(
|
22
|
+
.then(this._openReplService.open.bind(this._openReplService, { skipSave: true }))
|
23
|
+
.then(query => {
|
24
24
|
this.query = query;
|
25
25
|
this.alert.item.query_title = query.item.title;
|
26
26
|
this.query.item.tags.push('alert-query');
|
@@ -59,9 +59,9 @@
|
|
59
59
|
editAlert() {
|
60
60
|
this._openReplService.open({
|
61
61
|
query: this.query
|
62
|
-
}).then(
|
62
|
+
}).then(query => {
|
63
63
|
this.query = query;
|
64
|
-
return this.query.save(
|
64
|
+
return this.query.save();
|
65
65
|
}).then(this.alert.save.bind(this.alert));
|
66
66
|
}
|
67
67
|
|
@@ -24,7 +24,7 @@
|
|
24
24
|
this._roleModel = RoleModel;
|
25
25
|
this._tagResource = TagResource;
|
26
26
|
this._$q = $q;
|
27
|
-
this.
|
27
|
+
this._repl = OpenReplService;
|
28
28
|
|
29
29
|
RoleModel.initCollection();
|
30
30
|
}
|
@@ -61,10 +61,7 @@
|
|
61
61
|
}
|
62
62
|
|
63
63
|
openRepl() {
|
64
|
-
this.
|
65
|
-
.then(({query, result}) => {
|
66
|
-
return query.save({ result_id: result.id });
|
67
|
-
})
|
64
|
+
this._repl.open()
|
68
65
|
.then(this._handler.navigateToLatestVersion.bind(this._handler))
|
69
66
|
.then(this._handler.success.bind(this._handler, 'create', true))
|
70
67
|
.catch(this._handler.handleReplExit.bind(this._handler));
|
@@ -2,12 +2,11 @@
|
|
2
2
|
'use strict';
|
3
3
|
|
4
4
|
class QueryReplController {
|
5
|
-
constructor($uibModalInstance, getResultCsv, DefaultAceConfigurator, ResultRunner,
|
6
|
-
Results, NavigationGuard, KeyBindings, hotkeys, $scope) {
|
5
|
+
constructor($uibModalInstance, getResultCsv, DefaultAceConfigurator, ResultRunner, Query,
|
6
|
+
Results, NavigationGuard, KeyBindings, hotkeys, options, $scope) {
|
7
|
+
this._initQuery(options, Query);
|
7
8
|
this._modalInstance = $uibModalInstance;
|
8
|
-
|
9
9
|
this.results = new Results();
|
10
|
-
this.query = query;
|
11
10
|
this.resultRunner = new ResultRunner(this.query, this.results, {
|
12
11
|
sandbox: true,
|
13
12
|
enablePolling: true
|
@@ -78,11 +77,19 @@
|
|
78
77
|
this._navigationGuard.disable();
|
79
78
|
this._setParameterDefaultValues(this.query.item.version.parameters, this.resultRunner.substitutionValues);
|
80
79
|
let result = this.results.collection.shift();
|
80
|
+
if(this._options.skipSave) {
|
81
|
+
/* FIXME: this is for alerts where we want to pop up the repl and save later
|
82
|
+
We probably have to rethink the alerts UI/UX to take this hack out */
|
83
|
+
this._toModelAndClose(angular.copy(this.query.item));
|
84
|
+
} else {
|
85
|
+
this.query.save(_.exists(result) ? { result_id: result.item.id } : {})
|
86
|
+
.then(this._toModelAndClose.bind(this))
|
87
|
+
.catch(err => {
|
88
|
+
console.error('query save failed', err);
|
89
|
+
alert('Query save failed!');
|
90
|
+
});
|
91
|
+
}
|
81
92
|
|
82
|
-
this._modalInstance.close({
|
83
|
-
query: this.query,
|
84
|
-
result: _.exists(result) ? result.item : {},
|
85
|
-
});
|
86
93
|
}
|
87
94
|
|
88
95
|
exit() {
|
@@ -99,6 +106,12 @@
|
|
99
106
|
|
100
107
|
// private methods
|
101
108
|
|
109
|
+
_toModelAndClose(item) {
|
110
|
+
let model = new this._Query();
|
111
|
+
model.internalize(item);
|
112
|
+
this._modalInstance.close(model);
|
113
|
+
}
|
114
|
+
|
102
115
|
_queryBody() {
|
103
116
|
return this.query.item.version.body;
|
104
117
|
}
|
@@ -112,6 +125,17 @@
|
|
112
125
|
});
|
113
126
|
}
|
114
127
|
|
128
|
+
_initQuery(options, Query) {
|
129
|
+
this._Query = Query;
|
130
|
+
this._options = options;
|
131
|
+
this.query = new Query();
|
132
|
+
if(_.exists(options.query)) {
|
133
|
+
this.query.internalize(angular.copy(options.query.item));
|
134
|
+
} else {
|
135
|
+
this.query.initItem();
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
115
139
|
_initKeyBindings(KeyBindings, hotkeys, $scope) {
|
116
140
|
this._saveKb = KeyBindings.saveQuery.withKeyFn(() => {
|
117
141
|
if(this.validToSave()) {
|
@@ -138,7 +162,7 @@
|
|
138
162
|
}
|
139
163
|
|
140
164
|
QueryReplController.$inject = ['$uibModalInstance', 'getResultCsv', 'DefaultAceConfigurator', 'ResultRunner',
|
141
|
-
'
|
165
|
+
'Query', 'Results', 'NavigationGuard', 'KeyBindings', 'hotkeys', 'options', '$scope'];
|
142
166
|
|
143
167
|
angular
|
144
168
|
.module('alephControllers.queryReplController', ['alephServices'])
|
@@ -0,0 +1,26 @@
|
|
1
|
+
!(angular => {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
class SingleResultShowController {
|
5
|
+
constructor($routeParams, Query, Result, PageTitleManager) {
|
6
|
+
this.resultId = $routeParams.resultId;
|
7
|
+
this.queryId = $routeParams.queryId;
|
8
|
+
this.queryVersionId = $routeParams.queryVersionId;
|
9
|
+
|
10
|
+
this.query = new Query();
|
11
|
+
this.query.initItem(this.queryId, this.queryVersionId).then(query => {
|
12
|
+
this.panelTitle = 'Result id=' + this.resultId + ' for "' + query.title + '"';
|
13
|
+
PageTitleManager.title = this.panelTitle ;
|
14
|
+
});
|
15
|
+
|
16
|
+
this.result = new Result();
|
17
|
+
this.result.initItem(this.resultId);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
SingleResultShowController.$inject = ['$routeParams', 'Query', 'Result', 'PageTitleManager'];
|
22
|
+
|
23
|
+
angular
|
24
|
+
.module('alephControllers.singleResultShowController', ['alephServices'])
|
25
|
+
.controller('SingleResultShowController', SingleResultShowController);
|
26
|
+
}(angular));
|
@@ -18,7 +18,6 @@
|
|
18
18
|
|
19
19
|
openRepl() {
|
20
20
|
this._repl.open( { query: this.query } )
|
21
|
-
.then(({query, result}) => query.save({ result_id: result.id }))
|
22
21
|
.then(this._handler.navigateToLatestVersion.bind(this._handler))
|
23
22
|
.then(this._handler.success.bind(this._handler, 'update', false))
|
24
23
|
.catch(this._handler.handleReplExit.bind(this._handler))
|
@@ -2,11 +2,28 @@
|
|
2
2
|
'use strict';
|
3
3
|
|
4
4
|
class ResultsController {
|
5
|
+
|
6
|
+
constructor(AlertFlash) {
|
7
|
+
this._alertFlash = AlertFlash;
|
8
|
+
this._hostname = angular.copy(location.host);
|
9
|
+
}
|
10
|
+
|
5
11
|
runQuery() {
|
6
12
|
this.resultRunner.run();
|
7
13
|
}
|
14
|
+
|
15
|
+
generateResultLink(result) {
|
16
|
+
return this._hostname + '/results/query/' + this.query.item.id +
|
17
|
+
'/query_version/' + this.query.item.version.id + '/result/' + result.item.id;
|
18
|
+
}
|
19
|
+
|
20
|
+
alertCopied() {
|
21
|
+
this._alertFlash.emitSuccess('Result link copied to clipboard!');
|
22
|
+
}
|
8
23
|
}
|
9
24
|
|
25
|
+
ResultsController.$inject = ['AlertFlash'];
|
26
|
+
|
10
27
|
function resultsComponent() {
|
11
28
|
return {
|
12
29
|
restrict: 'E',
|
@@ -3,9 +3,8 @@
|
|
3
3
|
|
4
4
|
class OpenReplService {
|
5
5
|
|
6
|
-
constructor($uibModal,
|
6
|
+
constructor($uibModal, AceCompleters) {
|
7
7
|
this._$modal = $uibModal;
|
8
|
-
this._Query = Query;
|
9
8
|
this._AceCompleters = AceCompleters;
|
10
9
|
}
|
11
10
|
|
@@ -21,36 +20,14 @@
|
|
21
20
|
windowClass: 'query-repl-dialog',
|
22
21
|
backdrop: 'static',
|
23
22
|
keyboard: false,
|
24
|
-
resolve: {
|
25
|
-
query: () => _.exists(options.query) ? this._clone(options.query) : this._newQuery()
|
26
|
-
}
|
23
|
+
resolve: { options: () => options }
|
27
24
|
});
|
28
25
|
|
29
|
-
return modalInstance.result
|
30
|
-
return {
|
31
|
-
query: this._clone(query),
|
32
|
-
result: result
|
33
|
-
};
|
34
|
-
});
|
35
|
-
}
|
36
|
-
|
37
|
-
// private methods
|
38
|
-
|
39
|
-
_clone(q) {
|
40
|
-
let copiedItem = angular.copy(q.item);
|
41
|
-
let m = new this._Query();
|
42
|
-
m.internalize(copiedItem);
|
43
|
-
return m;
|
44
|
-
}
|
45
|
-
|
46
|
-
_newQuery() {
|
47
|
-
let m = new this._Query();
|
48
|
-
m.initItem();
|
49
|
-
return m;
|
26
|
+
return modalInstance.result;
|
50
27
|
}
|
51
28
|
}
|
52
29
|
|
53
|
-
OpenReplService.$inject = ['$uibModal', '
|
30
|
+
OpenReplService.$inject = ['$uibModal', 'AceCompleters'];
|
54
31
|
angular.module('alephServices.openReplService', []).service('OpenReplService', OpenReplService);
|
55
32
|
|
56
33
|
}(angular));
|
@@ -12,7 +12,7 @@
|
|
12
12
|
let _action = action || 'action';
|
13
13
|
let _schedule = _.exists(schedule) ? schedule : false;
|
14
14
|
|
15
|
-
this._alertFlash.emitSuccess('Query "' + query.title + '" ' + _action + 'd!', _schedule);
|
15
|
+
this._alertFlash.emitSuccess('Query "' + query.item.title + '" ' + _action + 'd!', _schedule);
|
16
16
|
return query;
|
17
17
|
}
|
18
18
|
|
@@ -26,7 +26,7 @@
|
|
26
26
|
}
|
27
27
|
|
28
28
|
navigateToLatestVersion(query) {
|
29
|
-
this._$location.path('/queries/' + query.id + '/query_versions/' + query.version.id);
|
29
|
+
this._$location.path('/queries/' + query.item.id + '/query_versions/' + query.item.version.id);
|
30
30
|
return query;
|
31
31
|
}
|
32
32
|
|
@@ -148,6 +148,9 @@ a.navbar-text.active
|
|
148
148
|
.no-list-style
|
149
149
|
list-style: none
|
150
150
|
|
151
|
+
.inline
|
152
|
+
display: inline
|
153
|
+
|
151
154
|
.looks-like-clickable
|
152
155
|
cursor: pointer
|
153
156
|
&:hover
|
@@ -155,3 +158,45 @@ a.navbar-text.active
|
|
155
158
|
color: tomato
|
156
159
|
&:active
|
157
160
|
opacity: $opacity-high
|
161
|
+
|
162
|
+
// pulsating stuff
|
163
|
+
|
164
|
+
// Chrome, Safari, Opera
|
165
|
+
@-webkit-keyframes pulsate
|
166
|
+
from
|
167
|
+
opacity: 0.0
|
168
|
+
color: tomato
|
169
|
+
|
170
|
+
to
|
171
|
+
opacity: $opacity-high
|
172
|
+
color: $slate-blue
|
173
|
+
|
174
|
+
|
175
|
+
// Standard syntax
|
176
|
+
@keyframes pulsate
|
177
|
+
from
|
178
|
+
opacity: 0.0
|
179
|
+
color: tomato
|
180
|
+
|
181
|
+
to
|
182
|
+
opacity: $opacity-high
|
183
|
+
color: $slate-blue
|
184
|
+
|
185
|
+
.pulsating
|
186
|
+
-webkit-animation-name: pulsate
|
187
|
+
-webkit-animation-duration: 3s
|
188
|
+
-webkit-animation-direction: alternate
|
189
|
+
animation-name: pulsate
|
190
|
+
animation-duration: 3s
|
191
|
+
animation-direction: alternate
|
192
|
+
animation-iteration-count: infinite
|
193
|
+
|
194
|
+
// you know, just give them a taste
|
195
|
+
.finite-pulsating
|
196
|
+
-webkit-animation-name: pulsate
|
197
|
+
-webkit-animation-duration: 3s
|
198
|
+
-webkit-animation-direction: alternate
|
199
|
+
animation-name: pulsate
|
200
|
+
animation-duration: 3s
|
201
|
+
animation-direction: alternate
|
202
|
+
animation-iteration-count: 3
|
@@ -36,36 +36,6 @@
|
|
36
36
|
.versions
|
37
37
|
padding: 15px 0px 15px 0px
|
38
38
|
|
39
|
-
// Chrome, Safari, Opera
|
40
|
-
@-webkit-keyframes pulsate
|
41
|
-
from
|
42
|
-
opacity: 0.0
|
43
|
-
color: tomato
|
44
|
-
|
45
|
-
to
|
46
|
-
opacity: $opacity-high
|
47
|
-
color: $slate-blue
|
48
|
-
|
49
|
-
|
50
|
-
// Standard syntax
|
51
|
-
@keyframes pulsate
|
52
|
-
from
|
53
|
-
opacity: 0.0
|
54
|
-
color: tomato
|
55
|
-
|
56
|
-
to
|
57
|
-
opacity: $opacity-high
|
58
|
-
color: $slate-blue
|
59
|
-
|
60
|
-
.pulsating-comment
|
61
|
-
-webkit-animation-name: pulsate
|
62
|
-
-webkit-animation-duration: 3s
|
63
|
-
-webkit-animation-direction: alternate
|
64
|
-
animation-name: pulsate
|
65
|
-
animation-duration: 3s
|
66
|
-
animation-direction: alternate
|
67
|
-
animation-iteration-count: infinite
|
68
|
-
|
69
39
|
.versions
|
70
40
|
ul
|
71
41
|
list-style-type: none
|
data/app/models/alert.rb
CHANGED
@@ -18,7 +18,7 @@ class Alert < ActiveRecord::Base
|
|
18
18
|
delegate :latest_query_version, to: :query
|
19
19
|
delegate :user, to: :latest_query_version
|
20
20
|
|
21
|
-
scope :with_role, ->(role) {
|
21
|
+
scope :with_role, ->(role) { references(:query).includes(:query_roles).where(query_roles: { role: role }) }
|
22
22
|
|
23
23
|
LOCATIONS_FOR_ATTRIBUTES = {
|
24
24
|
title: { association: :query, column: :title, type: :text },
|