Skip to content
Snippets Groups Projects
Commit 22c501b8 authored by Allen Gilliland's avatar Allen Gilliland
Browse files

Merge branch 'pulses' of github.com:metabase/metabase into pulses

parents 6c7b644a 3b01f09f
Branches
Tags
No related merge requests found
import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import { createAction } from "redux-actions";
import moment from "moment";
import { normalize, Schema, arrayOf } from "normalizr";
// HACK: just use our Angular resources for now
function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
import moment from "moment";
const user = new Schema('user');
// resource wrappers
const SessionApi = new AngularResourceProxy("Session", ["forgot_password"]);
const UserApi = new AngularResourceProxy("User", ["list", "update", "create", "delete", "update_password", "send_invite"]);
......
import { createAction } from "redux-actions";
import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import moment from "moment";
// HACK: just use our Angular resources for now
function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
// resource wrappers
const CardApi = new AngularResourceProxy("Card", ["list"]);
const MetadataApi = new AngularResourceProxy("Metabase", ["db_list", "db_metadata"]);
......
import _ from "underscore";
import { createAction } from "redux-actions";
import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import { normalize, Schema, arrayOf } from "normalizr";
import moment from "moment";
......@@ -9,33 +10,6 @@ import { getPositionForNewDashCard } from "metabase/lib/dashboard_grid";
const DATASET_TIMEOUT = 60;
// HACK: just use our Angular resources for now
function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
// normalizr schemas
const dashcard = new Schema('dashcard');
const card = new Schema('card');
......
import _ from "underscore";
import moment from "moment";
// HACK: just use our Angular resources for now
function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
// resource wrappers
const ActivityApi = new AngularResourceProxy("Activity", ["list", "recent_views"]);
......
// HACK: just use our Angular resources for now
export function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
export function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
......@@ -394,6 +394,35 @@ CoreServices.factory('Revision', ['$resource', function($resource) {
});
}]);
CoreServices.factory('Pulse', ['$resource', '$cookies', function($resource, $cookies) {
return $resource('/api/pulse/:pulseId', {}, {
list: {
url: '/api/pulse',
method: 'GET',
isArray: true
},
create: {
url: '/api/pulse',
method: 'POST',
headers: { 'X-CSRFToken': () => $cookies.csrftoken },
},
get: {
method: 'GET',
params: { pulseId: '@pulseId' },
},
update: {
method: 'PUT',
params: { pulseId: '@id' },
headers: { 'X-CSRFToken': () => $cookies.csrftoken },
},
delete: {
method: 'DELETE',
params: { pulseId: '@pulseId' },
headers: { 'X-CSRFToken': () => $cookies.csrftoken },
},
});
}]);
CoreServices.factory('Util', ['$resource', '$cookies', function($resource, $cookies) {
return $resource('/api/util/', {}, {
password_check: {
......
//import _ from "underscore";
import { createAction } from "redux-actions";
import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import MetabaseAnalytics from "metabase/lib/analytics";
import MetabaseCookies from "metabase/lib/cookies";
import MetabaseSettings from "metabase/lib/settings";
// HACK: just use our Angular resources for now
function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
// // resource wrappers
const SetupApi = new AngularResourceProxy("Setup", ["create", "validate_db"]);
const UtilApi = new AngularResourceProxy("Util", ["password_check"]);
......
import { createAction } from "redux-actions";
// HACK: just use our Angular resources for now
function AngularResourceProxy(serviceName, methods) {
methods.forEach((methodName) => {
this[methodName] = function(...args) {
let service = angular.element(document.querySelector("body")).injector().get(serviceName);
return service[methodName](...args).$promise;
}
});
}
// similar to createAction but accepts a (redux-thunk style) thunk and dispatches based on whether
// the promise returned from the thunk resolves or rejects, similar to redux-promise
function createThunkAction(actionType, actionThunkCreator) {
return function(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs);
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState);
dispatch({ type: actionType, payload });
} catch (error) {
dispatch({ type: actionType, payload: error, error: true });
throw error;
}
}
}
}
import { AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
// resource wrappers
const AppState = new AngularResourceProxy("AppState", ["refreshCurrentUser"]);
......
......@@ -2,13 +2,18 @@
"/api/pulse endpoints."
(:require [korma.core :refer [where subselect fields order limit]]
[compojure.core :refer [defroutes GET PUT POST DELETE]]
[hiccup.core :refer [html]]
[medley.core :refer :all]
[metabase.api.common :refer :all]
[metabase.db :as db]
(metabase.models [common :as common]
[metabase.driver :as driver]
(metabase.models [card :refer [Card] :as card]
[common :as common]
[database :refer [Database]]
[hydrate :refer :all]
[pulse :refer [Pulse] :as pulse])
[metabase.util :as util]))
[metabase.util :as util]
[metabase.pulse :as p]))
(defendpoint GET "/"
......@@ -56,5 +61,12 @@
[id]
(db/cascade-delete Pulse :id id))
(defendpoint GET "/preview_card/:id"
"Get HTML rendering of a `Card` with ID."
[id]
(let [card (Card id)]
(read-check Database (:database (:dataset_query card)))
(let [data (:data (driver/dataset-query (:dataset_query card) {:executed_by *current-user-id*}))]
{:status 200 :body (html [:html [:body {:style ""} (p/render-pulse-card card data)]])})))
(define-routes)
(ns metabase.pulse
(:require [hiccup.core :refer [html]]))
(def ^:private td-style " font-size: 14pt; text-align: left; padding-right: 1em; padding-top: 8px; ")
(def ^:private th-style " font-size: 12pt; text-transform: uppercase; border-bottom: 4px solid rgb(248, 248, 248); padding-bottom: 10px; ")
(defn bar-chart-row
[index row max-value]
[:tr {:style (if (odd? index) "color: rgb(189,193,191);" "color: rgb(124,131,129);")}
[:td {:style td-style} (first row)]
[:td {:style (str td-style " font-weight: bolder;")} (second row)]
[:td {:style td-style}
[:div {:style (str "background-color: rgb(135, 93, 175); height: 40px; width: " (float (* 100 (/ (second row) max-value))) "%")} " "]]])
(defn bar-chart
[data]
(let [{cols :cols rows :rows } data
max-value (apply max (map second rows))]
[:table {:style "border-collapse: collapse;"}
[:thead
[:tr
[:th {:style (str td-style th-style "min-width: 60px;")}
(:display_name (first cols))]
[:th {:style (str td-style th-style "min-width: 60px;")}
(:display_name (second cols))]
[:th {:style (str td-style th-style "width: 99%")}]]]
[:tbody
(map-indexed #(bar-chart-row %1 %2 max-value) rows)]]))
(defn render-pulse-card
[card, data]
[:section {:style "font-family: Lato, \"Helvetica Neue\", Helvetica, sans-serif; padding-left: 1em; padding-right: 1em; padding-bottom: 15px;"}
[:h2 (:name card)]
(bar-chart data)])
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment