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

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

parents df89d588 cc932340
No related branches found
No related tags found
No related merge requests found
......@@ -7,7 +7,6 @@
.ColumnarSelector-column {
min-width: 180px;
min-height: 300px;
max-height: 340px;
flex: 1;
}
......
......@@ -338,6 +338,8 @@
}
.Toggle {
position: relative;
display: inline-block;
color: var(--brand-color);
box-sizing: border-box;
width: 48px;
......@@ -345,7 +347,6 @@
border-radius: 99px;
border: 1px solid #EAEAEA;
background-color: #F7F7F7;
position: relative;
transition: all 0.3s;
}
......
......@@ -60,7 +60,7 @@ export default class PulseEdit extends Component {
failedText="Save failed"
successText="Saved"
/>
<a className="text-bold flex-align-right" href="/pulse">Cancel</a>
<a className="text-bold flex-align-right no-decoration text-brand-hover" href="/pulse">Cancel</a>
</div>
</div>
);
......
......@@ -38,7 +38,7 @@ export default class PulseEditCard extends Component {
}
return (
<div className="py4">
<div className="py1">
<h2>Pick your data</h2>
<p>Pick up to five questions you'd like to send in this pulse</p>
<ol className="my3">
......
import React, { Component, PropTypes } from "react";
import Select from "metabase/components/Select.jsx";
// import CheckBox from "metabase/components/CheckBox.jsx";
import cx from "classnames";
// const SCHEDULE_NAMES = {
// "hourly": (<span>Hour<br /></span>),
// "daily": (<span>Day<br />(8 am every week day)</span>),
// "weekly": (<span>Week<br />(8 am on Mondays)</span>)
// };
//
// function getScheduleField(options) {
// return {
// name: "schedule",
// displayName: "Send every",
// type: "select-button",
// options: options.map(o => ({ name: SCHEDULE_NAMES[o], value: o })),
// required: true
// };
// }
// const CHANNELS = {
// "email": {
// displayName: "Email",
// fields: [
// {
// name: "recipients",
// displayName: "Send to",
// multi: true,
// type: "email",
// placeholder: "Enter email address these questions should be sent to",
// required: true
// },
// getScheduleField(["daily", "weekly"])
// ]
// },
// "slack": {
// displayName: "Slack",
// fields: [
// {
// name: "channel",
// displayName: "Send to",
// multi: false,
// type: "select",
// options: ["#general", "#random", "#ios"],
// required: true
// },
// getScheduleField(["hourly", "daily"])
// ]
// }
// };
import Toggle from "metabase/components/Toggle.jsx";
import { capitalize } from "metabase/lib/formatting";
import _ from "underscore";
const CHANNELS = [
{
type: "email",
name: "Email",
recipients: ["account", "email"],
schedules: ["daily", "weekly"]
},
{
type: "slack",
name: "Slack",
fields: [
{
name: "channel",
type: "select",
multi: false,
required: true,
options: ["#general", "#random", "#ios"],
displayName: "Post to"
}
],
schedules: ["hourly", "daily"]
}
];
const HOUR_OPTIONS = _.times(12, (n) => (
{ name: (n === 0 ? 12 : n)+":00", value: n }
));
const AM_PM_OPTIONS = [
{ name: "AM", value: 0 },
{ name: "PM", value: 1 }
];
const DAY_OF_WEEK_OPTIONS = [
{ name: "Sunday", value: "sun" },
{ name: "Monday", value: "mon" },
{ name: "Tuesday", value: "tue" },
{ name: "Wednesday", value: "wed" },
{ name: "Thursday", value: "thu" },
{ name: "Friday", value: "fri" },
{ name: "Saturday", value: "sat" }
];
export default class PulseEditChannel extends Component {
constructor(props) {
......@@ -63,16 +61,26 @@ export default class PulseEditChannel extends Component {
addChannel(type) {
let { pulse } = this.props;
let channel = { type: type, schedule: "daily" };
let channels = [...pulse.channels, channel];
this.props.setPulse({ ...pulse, channels });
let channelSpec = _.find(CHANNELS, (c) => c.type === type);
if (!channelSpec) {
return;
}
let channel = {
channel_type: type,
recipients: [],
details: {},
schedule_type: channelSpec.schedules[0],
schedule_details: { day_of_week: "mon", hour_of_day: 8 }
};
this.props.setPulse({ ...pulse, channels: pulse.channels.concat(channel) });
}
removeChannel(index) {
let { pulse } = this.props;
let channels = [...pulse.channels];
channels.splice(index, 1);
this.props.setPulse({ ...pulse, channels });
this.props.setPulse({ ...pulse, channels: pulse.channels.filter((c,i) => i !== index) });
}
onChannelPropertyChange(index, name, value) {
......@@ -82,90 +90,102 @@ export default class PulseEditChannel extends Component {
this.props.setPulse({ ...pulse, channels });
}
renderField(field, channel, index) {
switch (field.type) {
case "email":
return (
<input
className="input"
type="email"
multiple={true}
value={channel[field.name]}
onChange={(e) => this.onChannelPropertyChange(index, field.name, e.target.value)}
/>
);
case "select":
return (
<Select
value={channel[field.name]}
options={field.options}
optionNameFn={o => o}
optionValueFn={o => o}
onChange={(o) => this.onChannelPropertyChange(index, field.name, o)}
/>
);
case "select-button":
return (
<div className="Button-group flex">
{field.options.map(o =>
<a className={cx("Button flex-full", { "Button--primary": channel[field.name] === o.value })} onClick={() => this.onChannelPropertyChange(index, field.name, o.value)}>
{o.name}
</a>
)}
</div>
)
default:
return "unknown field type"
toggleChannel(type, enable) {
if (enable) {
this.addChannel(type)
} else {
let { pulse } = this.props;
this.props.setPulse({ ...pulse, channels: pulse.channels.filter((c) => c.channel_type !== type) });
}
}
render() {
// let { pulse } = this.props;
// let indexesForChannel = {};
// for (let [index, channel] of Object.entries(pulse.channels)) {
// indexesForChannel[channel.type] = indexesForChannel[channel.type] || []
// indexesForChannel[channel.type].push(index);
// }
let channels = [];
// Object.entries(CHANNELS).map(([type, CHANNEL]) => {
// if (indexesForChannel[type]) {
// indexesForChannel[type].map(index => {
// let channel = pulse.channels[index];
// channels.push(
// <div>
// <div className="flex align-center">
// <CheckBox checked={true} onChange={this.removeChannel.bind(this, index)} />
// <h3 className="ml1">{CHANNEL.displayName}</h3>
// </div>
// <ul className="ml3" >
// {CHANNEL.fields.map(field =>
// <li className="py1" key={field.name}>
// <h4 className="py1">{field.displayName}</h4>
// <div>{this.renderField(field, channel, index)}</div>
// </li>
// )}
// </ul>
// </div>
// )
// });
// } else {
// channels.push(
// <div className="flex align-center">
// <CheckBox checked={false} onChange={this.addChannel.bind(this, type)} />
// <h3 className="ml1">{CHANNEL.displayName}</h3>
// </div>
// )
// }
// });
renderDayPicker(c, index) {
return (
<span className="mt1">
<span className="mx1">on</span>
<Select
value={_.find(DAY_OF_WEEK_OPTIONS, (o) => o.value === c.schedule_details.day_of_week)}
options={DAY_OF_WEEK_OPTIONS}
optionNameFn={o => o.name}
optionValueFn={o => o.value}
onChange={(o) => this.onChannelPropertyChange(index, "schedule_details", { ...c.schedule_details, day_of_week: o }) }
/>
</span>
);
}
renderHourPicker(c, index) {
let hourOfDay = isNaN(c.schedule_details.hour_of_day) ? 8 : c.schedule_details.hour_of_day;
let hour = hourOfDay % 12;
let amPm = hourOfDay >= 12 ? 1 : 0;
return (
<div className="mt1">
<span className="mr1">at</span>
<Select
className="mr1"
value={_.find(HOUR_OPTIONS, (o) => o.value === hour)}
options={HOUR_OPTIONS}
optionNameFn={o => o.name}
optionValueFn={o => o.value}
onChange={(o) => this.onChannelPropertyChange(index, "schedule_details", { ...c.schedule_details, hour_of_day: o + amPm * 12 }) }
/>
<Select
value={_.find(AM_PM_OPTIONS, (o) => o.value === amPm)}
options={AM_PM_OPTIONS}
optionNameFn={o => o.name}
optionValueFn={o => o.value}
onChange={(o) => this.onChannelPropertyChange(index, "schedule_details", { ...c.schedule_details, hour_of_day: hour + o * 12 }) }
/>
</div>
);
}
renderChannel(channel, index, channelSpec) {
return (
<li className="py1">
<span className="mr1">Sent</span>
<Select
value={channel.schedule_type}
options={channelSpec.schedules}
optionNameFn={o => capitalize(o)}
optionValueFn={o => o}
onChange={(o) => this.onChannelPropertyChange(index, "schedule_type", o)}
/>
{ channel.schedule_type === "weekly" &&
this.renderDayPicker(channel, index)
}
{ (channel.schedule_type === "daily" || channel.schedule_type === "weekly") &&
this.renderHourPicker(channel, index)
}
</li>
)
}
renderChannelSection(channelSpec) {
let { pulse } = this.props;
return (
<li key={channelSpec.type} className="py2 border-row-divider">
<div className="flex align-center">
<h3>{channelSpec.name}</h3>
<Toggle className="flex-align-right" value={pulse.channels.some(c => c.channel_type === channelSpec.type)} onChange={this.toggleChannel.bind(this, channelSpec.type)} />
</div>
<ul>
{pulse.channels.map((channel, index) =>
channel.channel_type === channelSpec.type &&
this.renderChannel(channel, index, channelSpec)
)}
</ul>
</li>
)
}
render() {
return (
<div className="py4">
<div className="py1">
<h2>Where should this data go?</h2>
<ul>
{channels.map(channel =>
<li className="my2">{channel}</li>
{CHANNELS.map(channelSpec =>
this.renderChannelSection(channelSpec)
)}
</ul>
</div>
......
......@@ -21,7 +21,7 @@ export default class PulseEditName extends Component {
render() {
let { pulse } = this.props;
return (
<div className="py4">
<div className="py1">
<h2>Name your pulse</h2>
<p>Give your pulse a name to help others understand what it's about.</p>
<div className="my3">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment