diff --git a/Gulpfile.js b/Gulpfile.js index 7f22c38d913620aebd56f555b394dfbbac19928b..26fe35a6e304e708dd42e534cb5763688c2b9670 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -9,7 +9,8 @@ var basePath = 'resources/frontend_client/app/'; var SRC = { css: [basePath + 'css/**/*.css', basePath + 'components/**/*.css'], - jsx: [basePath + 'query_builder/*.js'] + jsx: [basePath + 'query_builder/*.js'], + appJS: [basePath + '**/*.js', '!' + basePath + 'bower_components/**/*.js', '!' + basePath + 'dist/*.js', '!' + basePath + 'query_builder/*.js', '!' + basePath + '/test/**/*.js'] }; var DEST = { @@ -41,16 +42,21 @@ gulp.task('jsx', function () { return gulp.src(SRC.jsx) .pipe(concat('query_builder.js')) .pipe(react()) - .pipe(gulp.dest(DEST.js)) -}) + .pipe(gulp.dest(DEST.js)); +}); +gulp.task('build-js', function () { + return gulp.src(SRC.appJS) + .pipe(concat('app.js')) + .pipe(gulp.dest(DEST.js)); +}); gulp.task('watch', function(){ gulp.watch(SRC.css, ['css']); gulp.watch(SRC.jsx, ['jsx']); + gulp.watch(SRC.appJS, ['build-js']); }); +gulp.task('build', ['css', 'jsx', 'build-js']); -gulp.task('build', ['css', 'jsx']); - -gulp.task('default', ['build','watch', 'jsx']); +gulp.task('default', ['build', 'watch']); diff --git a/resources/frontend_client/app/card/card.controllers.js b/resources/frontend_client/app/card/card.controllers.js index f17ef83e23b4ed8bc8a9e95727c2460bc746eac5..15f557a98cc307c5dda2a926f757765bc9a8a77d 100644 --- a/resources/frontend_client/app/card/card.controllers.js +++ b/resources/frontend_client/app/card/card.controllers.js @@ -1462,6 +1462,9 @@ CardControllers.controller('CardDetailNew', [ } }, + getDownloadLink: function () { + return '/api/meta/dataset/csv/?query=' + encodeURIComponent(JSON.stringify($scope.model.card.dataset_query)); + }, cleanFilters: function (dataset_query) { var filters = dataset_query.query.filter, cleanFilters = []; diff --git a/resources/frontend_client/app/query_builder/query_builder.js b/resources/frontend_client/app/query_builder/query_builder.js index 90d34800f5414106a9c27a30f108bf66fb769f85..e246705ba1e126fffbfd3a4254497aec8b30d587 100644 --- a/resources/frontend_client/app/query_builder/query_builder.js +++ b/resources/frontend_client/app/query_builder/query_builder.js @@ -115,7 +115,8 @@ var QueryBuilder = React.createClass({ }); var saver, - result; + result, + download; if(this.props.model.result) { saver = ( <Saver @@ -133,7 +134,10 @@ var QueryBuilder = React.createClass({ result={this.props.model.result} setDisplay={this.props.model.setDisplay.bind(this.props.model)} /> - ) + ); + download = ( + <a className="ActionButton inline-block mr1" href={this.props.model.getDownloadLink()} target="_blank">Download data</a> + ); } @@ -187,6 +191,7 @@ var QueryBuilder = React.createClass({ <div className="ActionBar"> {saver} + {download} </div> </div> ) diff --git a/resources/frontend_client/index.html b/resources/frontend_client/index.html index d3e339d42da34e80b440b685fa0e194952cbfa6f..f5af42d33af2c1171c164c3eae0a12c300a7a72c 100644 --- a/resources/frontend_client/index.html +++ b/resources/frontend_client/index.html @@ -165,16 +165,16 @@ <!-- JS INCLUDES --> - <script src="/app/bower_components/react/react-with-addons.js"></script> - <script src="/app/bower_components/react-onclickoutside/index.js"></script> - <script src="/app/bower_components/moment/min/moment.min.js"></script> - <script src="/app/bower_components/tether/tether.min.js"></script> - <script src="/app/bower_components/react-date-picker/react-datepicker.js"></script> +<!-- vendor js --> +<script src="/app/bower_components/react/react-with-addons.min.js"></script> +<script src="/app/bower_components/react-onclickoutside/index.js"></script> +<script src="/app/bower_components/moment/min/moment.min.js"></script> +<script src="/app/bower_components/tether/tether.min.js"></script> +<script src="/app/bower_components/react-date-picker/react-datepicker.js"></script> - <script src="/app/dist/query_builder.js" type="text/javascript"></script> <script src="/app/bower_components/jquery/dist/jquery.min.js"></script> -<script src="/app/bower_components/underscore/underscore.js"></script> +<script src="/app/bower_components/underscore/underscore-min.js"></script> <script src="/app/bower_components/fastclick/lib/fastclick.js"></script> <script src="/app/bower_components/angular-route/angular-route.min.js"></script> @@ -182,16 +182,16 @@ <script src="/app/bower_components/angular-cookies/angular-cookies.min.js"></script> <script src="/app/bower_components/angular-animate/angular-animate.min.js"></script> <script src="/app/bower_components/angular-sanitize/angular-sanitize.min.js"></script> -<script src="/app/bower_components/angular-xeditable/dist/js/xeditable.js"></script> +<script src="/app/bower_components/angular-xeditable/dist/js/xeditable.min.js"></script> <script src="/app/bower_components/angular-cookie/angular-cookie.min.js"></script> <script src="/app/bower_components/javascript-detect-element-resize/detect-element-resize.js"></script> -<script src="/app/bower_components/angular-gridster/src/angular-gridster.js"></script> +<script src="/app/bower_components/angular-gridster/dist/angular-gridster.min.js"></script> <script src="/app/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script> <script src="/app/bower_components/angular-http-auth/src/http-auth-interceptor.js"></script> <script src="/app/bower_components/ace-builds/src-min-noconflict/ace.js"></script> <script src="/app/bower_components/ace-builds/src-min-noconflict/mode-sql.js"></script> <script src="/app/bower_components/ace-builds/src-min-noconflict/ext-language_tools.js"></script> -<script src="/app/bower_components/angular-ui-ace/ui-ace.js"></script> +<script src="/app/bower_components/angular-ui-ace/ui-ace.min.js"></script> <script src="/app/bower_components/angularytics/dist/angularytics.min.js"></script> <script src="/app/bower_components/ng-sortable/dist/ng-sortable.min.js"></script> <script src="/app/bower_components/angular-readable-time/angular-readable-time.min.js"></script> @@ -202,83 +202,9 @@ <script src="/app/js/google_maps.js"></script> -<!-- global app --> -<script src="/app/app.js"></script> -<script src="/app/services.js"></script> -<script src="/app/controllers.js"></script> -<script src="/app/filters.js"></script> -<script src="/app/directives.js"></script> -<script src="/app/auth/auth.module.js"></script> -<script src="/app/auth/auth.controllers.js"></script> - -<!-- components and shared bits --> - -<script src="/app/annotation/annotation.services.js"></script> -<script src="/app/annotation/annotation.directives.js"></script> -<script src="/app/metabase/metabase.services.js"></script> - -<script src="/app/components/components.module.js"></script> -<script src="/app/components/core_nav/core_nav.js"></script> -<script src="/app/components/workspace/workspace.js"></script> -<script src="/app/components/icons/icons.js"></script> - -<!-- superadmin section --> -<script src="/app/superadmin/index/index.module.js"></script> -<script src="/app/superadmin/index/index.controllers.js"></script> -<script src="/app/superadmin/index/index.services.js"></script> -<script src="/app/superadmin/organization/organization.module.js"></script> -<script src="/app/superadmin/organization/organization.controllers.js"></script> - -<!-- admin section --> -<script src="/app/admin/admin.controllers.js"></script> -<script src="/app/admin/databases/databases.module.js"></script> -<script src="/app/admin/databases/databases.controllers.js"></script> -<script src="/app/admin/datasets/datasets.module.js"></script> -<script src="/app/admin/datasets/datasets.controllers.js"></script> -<script src="/app/admin/datasets/datasets.directives.js"></script> -<script src="/app/admin/emailreport/emailreport.module.js"></script> -<script src="/app/admin/emailreport/emailreport.controllers.js"></script> -<script src="/app/admin/emailreport/emailreport.services.js"></script> -<script src="/app/admin/people/people.module.js"></script> -<script src="/app/admin/people/people.controllers.js"></script> -<script src="/app/admin/people/people.directives.js"></script> -<script src="/app/admin/query/query.module.js"></script> -<script src="/app/admin/query/query.controllers.js"></script> -<script src="/app/admin/query/query.services.js"></script> -<script src="/app/admin/annotation/annotation.module.js"></script> -<script src="/app/admin/annotation/annotation.controllers.js"></script> -<script src="/app/admin/search/search.module.js"></script> -<script src="/app/admin/search/search.controllers.js"></script> - -<!-- main app --> -<script src="/app/card/card.module.js"></script> -<script src="/app/card/card.controllers.js"></script> -<script src="/app/card/card.services.js"></script> -<script src="/app/card/card.directives.js"></script> -<script src="/app/card/card.charting.js"></script> -<script src="/app/dashboard/dashboard.module.js"></script> -<script src="/app/dashboard/dashboard.controllers.js"></script> -<script src="/app/dashboard/dashboard.services.js"></script> -<script src="/app/dashboard/dashboard.directives.js"></script> -<script src="/app/explore/explore.module.js"></script> -<script src="/app/explore/explore.controllers.js"></script> -<script src="/app/explore/explore.directives.js"></script> -<script src="/app/explore/explore.services.js"></script> -<script src="/app/user/user.module.js"></script> -<script src="/app/user/user.controllers.js"></script> -<script src="/app/user/user.directives.js"></script> -<script src="/app/search/search.module.js"></script> -<script src="/app/search/search.controllers.js"></script> -<script src="/app/search/search.services.js"></script> - -<!-- custom shtuff: be wary --> -<script src="/app/operator/operator.module.js"></script> -<script src="/app/operator/operator.controllers.js"></script> -<script src="/app/operator/operator.services.js"></script> - -<script src="/app/reserve/reserve.module.js"></script> -<script src="/app/reserve/reserve.controllers.js"></script> -<script src="/app/reserve/reserve.services.js"></script> +<!-- dist app --> +<script src="/app/dist/app.js"></script> +<script src="/app/dist/query_builder.js" type="text/javascript"></script> <script src="//ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"></script> <script> diff --git a/src/metabase/util.clj b/src/metabase/util.clj index 5f02043788b749dd0f0410f3a26a8eef1aaad0ba..ee8119c5fe7e4223514714ab1494f381c085b044 100644 --- a/src/metabase/util.clj +++ b/src/metabase/util.clj @@ -149,3 +149,13 @@ false (boolean (re-matches #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" (clojure.string/lower-case v))))) + +(defn rpartial + "Like `partial`, but applies additional args *before* BOUND-ARGS. + Inspired by [`-rpartial` from dash.el](https://github.com/magnars/dash.el#-rpartial-fn-rest-args) + + ((partial - 5) 8) -> (- 5 8) -> -3 + ((rpartial - 5) 8) -> (- 8 5) -> 3" + [f & bound-args] + (fn [& args] + (apply f (concat args bound-args)))) diff --git a/test/metabase/test_data/data.clj b/test/metabase/test_data/data.clj index 779590b358272dcdeba90c1b0553a728f393b81d..a7c6b86bd3e390e32943dc3636d59e1a9d517233 100644 --- a/test/metabase/test_data/data.clj +++ b/test/metabase/test_data/data.clj @@ -12,21 +12,21 @@ ;; [name last_login] (defonce ^:private users - [["Justin Ho" (timestamp 2014 4 1 1 30)] - ["David Baden" (timestamp 2014 12 5 7 15)] - ["Roberto Sanabria" (timestamp 2014 11 6 8 15)] - ["Noah Sidman-Gale" (timestamp 2014 1 1 0 30)] - ["Cam Saül" (timestamp 2014 10 3 10 30)] - ["Kyle Doherty" (timestamp 2014 8 2 5 30)] - ["Anshu Agarwal" (timestamp 2014 8 2 2 30)] - ["Kiah Buchner" (timestamp 2014 2 1 2 15)] - ["Adam Stocker" (timestamp 2014 4 3 2 30)] - ["Allen Gilliland" (timestamp 2014 7 3 12 30)] - ["Christina Crosetti" (timestamp 2014 11 1)] - ["Sameer Al-Sakran" (timestamp 2014 7 2 18 30)] - ["Daniel Wiesenthal" (timestamp 2014 8 1 3 30)] - ["Douglas Graves" (timestamp 2014 10 3 6 45)] - ["RoveLine Ibañez" (timestamp 2014 8 1 5 45)]]) + [["Plato Yeshua" (timestamp 2014 4 1 1 30)] + ["Felipinho Asklepios" (timestamp 2014 12 5 7 15)] + ["Kaneonuskatew Eiran" (timestamp 2014 11 6 8 15)] + ["Simcha Yan" (timestamp 2014 1 1 0 30)] + ["Quentin Sören" (timestamp 2014 10 3 10 30)] + ["Shad Ferdynand" (timestamp 2014 8 2 5 30)] + ["Conchúr Tihomir" (timestamp 2014 8 2 2 30)] + ["Szymon Theutrich" (timestamp 2014 2 1 2 15)] + ["Nils Gotam" (timestamp 2014 4 3 2 30)] + ["Frans Hevel" (timestamp 2014 7 3 12 30)] + ["Spiros Teofil" (timestamp 2014 11 1)] + ["Kfir Caj" (timestamp 2014 7 2 18 30)] + ["Dwight Gresham" (timestamp 2014 8 1 3 30)] + ["Broen Olujimi" (timestamp 2014 10 3 6 45)] + ["Rüstem Hebel" (timestamp 2014 8 1 5 45)]]) ;; name (defonce ^:private categories diff --git a/test/metabase/test_util.clj b/test/metabase/test_util.clj index 331b5b165d1ce9058da47ec56356bb2aa462e9b9..b22b480878aacb842dc2b865c3d999dd6e83c66c 100644 --- a/test/metabase/test_util.clj +++ b/test/metabase/test_util.clj @@ -28,3 +28,12 @@ :a 100 :b (+ 100 (:a <>)) :c (+ 100 (:b <>)))) + + +;;; tests for RPARTIAL + +(expect 3 + ((rpartial - 5) 8)) + +(expect -7 + ((rpartial - 5 10) 8))