Refactor the import process

- Add 'Subscribe' and 'Blacklist' modes to the importer
- Removed 'override status' and the support for the 'status' field in import files
This commit is contained in:
Kailash Nadh 2018-11-05 16:59:09 +05:30
parent 9aa413013a
commit f2c09e716c
12 changed files with 352 additions and 297 deletions

View File

@ -75,7 +75,10 @@ class App extends React.PureComponent {
} catch (e) {
// If it's a GET call, throw a global notification.
if (method === cs.MethodGet) {
notification["error"]({ message: "Error fetching data", description: Utils.HttpError(e).message, duration: 0 })
notification["error"]({ placement: cs.MsgPosition,
message: "Error fetching data",
description: Utils.HttpError(e).message
})
}
// Set states and show the error on the layout.

View File

@ -321,7 +321,7 @@ class Campaigns extends React.PureComponent {
handleUpdateStatus = (record, status) => {
this.props.modelRequest(cs.ModelCampaigns, cs.Routes.UpdateCampaignStatus, cs.MethodPut, { id: record.id, status: status })
.then(() => {
notification["success"]({ placement: "topRight", message: `Campaign ${status}`, description: `"${record.name}" ${status}` })
notification["success"]({ placement: cs.MsgPosition, message: `Campaign ${status}`, description: `"${record.name}" ${status}` })
// Reload the table.
this.fetchRecords()
@ -333,7 +333,7 @@ class Campaigns extends React.PureComponent {
handleDeleteRecord = (record) => {
this.props.modelRequest(cs.ModelCampaigns, cs.Routes.DeleteCampaign, cs.MethodDelete, { id: record.id })
.then(() => {
notification["success"]({ placement: "topRight", message: "Campaign deleted", description: `"${record.name}" deleted` })
notification["success"]({ placement: cs.MsgPosition, message: "Campaign deleted", description: `"${record.name}" deleted` })
// Reload the table.
this.fetchRecords()
@ -349,7 +349,7 @@ class Campaigns extends React.PureComponent {
handleCloneCampaign = (record) => {
this.setState({ modalWaiting: true })
this.props.modelRequest(cs.ModelCampaigns, cs.Routes.CreateCampaign, cs.MethodPost, record).then((resp) => {
notification["success"]({ placement: "topRight",
notification["success"]({ placement: cs.MsgPosition,
message: "Campaign created",
description: `${record.name} created` })

View File

@ -1,5 +1,5 @@
import React from "react"
import { Row, Col, Form, Select, Input, Checkbox, Upload, Button, Icon, Spin, Progress, Popconfirm, Tag, notification } from "antd"
import { Row, Col, Form, Select, Input, Upload, Button, Radio, Icon, Spin, Progress, Popconfirm, Tag, notification } from "antd"
import * as cs from "./constants"
const StatusNone = "none"
@ -11,7 +11,8 @@ const StatusFailed = "failed"
class TheFormDef extends React.PureComponent {
state = {
confirmDirty: false,
fileList: []
fileList: [],
mode: "subscribe"
}
componentDidMount() {
@ -32,7 +33,7 @@ class TheFormDef extends React.PureComponent {
}
if(this.state.fileList.length < 1) {
notification["error"]({ message: "Error", description: "Select a valid file to upload" })
notification["error"]({ placement: cs.MsgPosition, message: "Error", description: "Select a valid file to upload" })
return
}
@ -41,11 +42,11 @@ class TheFormDef extends React.PureComponent {
params.append("file", this.state.fileList[0])
this.props.request(cs.Routes.UploadRouteImport, cs.MethodPost, params).then(() => {
notification["info"]({ message: "File uploaded",
notification["info"]({ placement: cs.MsgPosition, message: "File uploaded",
description: "Please wait while the import is running" })
this.props.fetchimportState()
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
notification["error"]({ placement: cs.MsgPosition, message: "Error", description: e.message })
})
}
@ -75,22 +76,35 @@ class TheFormDef extends React.PureComponent {
return (
<Spin spinning={false}>
<Form onSubmit={this.handleSubmit}>
<Form.Item {...formItemLayout} label="Lists" extra="Lists to subscribe to">
{getFieldDecorator("lists", { rules: [{ required: true }] })(
<Select mode="multiple">
{[...this.props.lists].map((v, i) =>
<Select.Option value={v["id"]} key={v["id"]}>{v["name"]}</Select.Option>
<Form.Item {...formItemLayout} label="Mode">
{getFieldDecorator("mode", { rules: [{ required: true }], initialValue: "subscribe" })(
<Radio.Group className="mode" onChange={(e) => { this.setState({ mode: e.target.value }) }}>
<Radio disabled={ this.props.formDisabled } value="subscribe">Subscribe</Radio>
<Radio disabled={ this.props.formDisabled } value="blacklist">Blacklist</Radio>
</Radio.Group>
)}
</Form.Item>
{ this.state.mode === "subscribe" &&
<React.Fragment>
<Form.Item {...formItemLayout} label="Lists" extra="Lists to subscribe to">
{getFieldDecorator("lists", { rules: [{ required: true }] })(
<Select mode="multiple">
{[...this.props.lists].map((v, i) =>
<Select.Option value={v["id"]} key={v["id"]}>{v["name"]}</Select.Option>
)}
</Select>
)}
</Select>
)}
</Form.Item>
<Form.Item {...formItemLayout}
label="Override status?"
extra="For existing subscribers in the system found again in the import, override their status, for example 'blacklisted' to 'active'. This is not always desirable.">
{getFieldDecorator("override_status", )(
<Checkbox initialValue="1" />
)}
</Form.Item>
</Form.Item>
</React.Fragment>
}
{ this.state.mode === "blacklist" &&
<Form.Item {...formItemTailLayout}>
<p className="ant-form-extra">
All existing subscribers found in the import will be marked as 'blacklisted' and will be
unsubscribed from their existing subscriptions. New subscribers will be imported and marked as 'blacklisted'.
</p>
</Form.Item>
}
<Form.Item {...formItemLayout} label="CSV column delimiter" extra="Default delimiter is comma">
{getFieldDecorator("delim", {
initialValue: ","
@ -113,14 +127,14 @@ class TheFormDef extends React.PureComponent {
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file here</p>
<p className="ant-upload-text">Click or drag the ZIP file here</p>
</Upload.Dragger>
)}
</div>
</Form.Item>
<Form.Item {...formItemTailLayout}>
<p className="text-grey">For existing subscribers, the names and attributes will be overwritten with the values in the CSV.</p>
<Button type="primary" htmlType="submit"><Icon type="upload" /> Upload &amp; import</Button>
<p className="ant-form-extra">For existing subscribers, the names and attributes will be overwritten with the values in the CSV.</p>
<Button type="primary" htmlType="submit"><Icon type="upload" /> Upload</Button>
</Form.Item>
</Form>
</Spin>
@ -140,7 +154,7 @@ class Importing extends React.PureComponent {
this.props.request(cs.Routes.UploadRouteImport, cs.MethodDelete).then((r) => {
this.props.fetchimportState()
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
notification["error"]({ placement: cs.MsgPosition, message: "Error", description: e.message })
})
}
@ -167,7 +181,7 @@ class Importing extends React.PureComponent {
let t = document.querySelector("#log-textarea")
t.scrollTop = t.scrollHeight;
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
notification["error"]({ placement: cs.MsgPosition, message: "Error", description: e.message })
})
}
@ -257,7 +271,7 @@ class Import extends React.PureComponent {
this.props.request(cs.Routes.GetRouteImportStats, cs.MethodGet).then((r) => {
this.setState({ importState: r.data.data })
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
notification["error"]({ placement: cs.MsgPosition, message: "Error", description: e.message })
})
}

View File

@ -69,6 +69,9 @@ body {
display: none;
}
/* Form */
/* Table actions */
td .actions a {
display: inline-block;

View File

@ -2,6 +2,17 @@
# yarn lockfile v1
"@ant-design/icons-react@~1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@ant-design/icons-react/-/icons-react-1.1.2.tgz#df25c4560864f8a3b687b305c3238daff048ed72"
dependencies:
ant-design-palettes "^1.1.3"
babel-runtime "^6.26.0"
"@ant-design/icons@~1.1.15":
version "1.1.15"
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-1.1.15.tgz#2ff689b87bb160c246a07adaa99cdb1c8dfd4412"
"@babel/helper-module-imports@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
@ -91,9 +102,9 @@ acorn@^5.0.0, acorn@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
add-dom-event-listener@1.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz#8faed2c41008721cf111da1d30d995b85be42bed"
add-dom-event-listener@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310"
dependencies:
object-assign "4.x"
@ -199,58 +210,66 @@ ansistyles@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539"
antd@^3.6.5:
version "3.8.1"
resolved "https://registry.yarnpkg.com/antd/-/antd-3.8.1.tgz#1780acd5e9bc6c80dc23b042161418eb88f4d80e"
ant-design-palettes@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/ant-design-palettes/-/ant-design-palettes-1.1.3.tgz#84119b1a4d86363adc52a38d587e65336a0a27dd"
dependencies:
array-tree-filter "^2.0.0"
tinycolor2 "^1.4.1"
antd@^3.6.5:
version "3.10.4"
resolved "https://registry.yarnpkg.com/antd/-/antd-3.10.4.tgz#e975c14e726801c7203d879a866f5ce8e47328c8"
dependencies:
"@ant-design/icons" "~1.1.15"
"@ant-design/icons-react" "~1.1.2"
array-tree-filter "^2.1.0"
babel-runtime "6.x"
classnames "~2.2.0"
create-react-class "^15.6.0"
create-react-context "^0.2.2"
css-animation "^1.2.5"
classnames "~2.2.6"
create-react-class "^15.6.3"
create-react-context "0.2.3"
css-animation "^1.4.1"
dom-closest "^0.2.0"
enquire.js "^2.1.1"
enquire.js "^2.1.6"
intersperse "^1.0.0"
lodash "^4.17.5"
moment "^2.19.3"
lodash "^4.17.11"
moment "^2.22.2"
omit.js "^1.0.0"
prop-types "^15.5.7"
prop-types "^15.6.2"
raf "^3.4.0"
rc-animate "^2.4.1"
rc-calendar "~9.6.0"
rc-cascader "~0.14.0"
rc-animate "^2.5.4"
rc-calendar "~9.7.9"
rc-cascader "~0.16.0"
rc-checkbox "~2.1.5"
rc-collapse "~1.9.0"
rc-dialog "~7.2.0"
rc-drawer "~1.6.2"
rc-dropdown "~2.2.0"
rc-editor-mention "^1.0.2"
rc-form "^2.1.0"
rc-input-number "~4.0.0"
rc-menu "~7.0.2"
rc-collapse "~1.10.0"
rc-dialog "~7.2.1"
rc-drawer "~1.7.6"
rc-dropdown "~2.2.1"
rc-editor-mention "^1.1.7"
rc-form "^2.2.3"
rc-input-number "~4.3.0"
rc-menu "~7.4.12"
rc-notification "~3.2.0"
rc-pagination "~1.16.1"
rc-progress "~2.2.2"
rc-rate "~2.4.0"
rc-select "~8.1.1"
rc-slider "~8.6.0"
rc-steps "~3.1.0"
rc-switch "~1.6.0"
rc-table "~6.2.0"
rc-tabs "~9.3.3"
rc-time-picker "~3.3.0"
rc-tooltip "~3.7.0"
rc-tree "~1.13.0"
rc-tree-select "~2.0.5"
rc-trigger "^2.5.4"
rc-upload "~2.5.0"
rc-util "^4.0.4"
react-lazy-load "^3.0.12"
react-lifecycles-compat "^3.0.2"
react-slick "~0.23.1"
shallowequal "^1.0.1"
warning "~4.0.1"
rc-pagination "~1.17.3"
rc-progress "~2.2.6"
rc-rate "~2.4.2"
rc-select "~8.4.0"
rc-slider "~8.6.3"
rc-steps "~3.3.0"
rc-switch "~1.8.0"
rc-table "~6.3.4"
rc-tabs "~9.4.6"
rc-time-picker "~3.4.0"
rc-tooltip "~3.7.3"
rc-tree "~1.14.6"
rc-tree-select "~2.3.1"
rc-trigger "^2.6.2"
rc-upload "~2.6.0"
rc-util "^4.5.1"
react-lazy-load "^3.0.13"
react-lifecycles-compat "^3.0.4"
react-slick "~0.23.2"
shallowequal "^1.1.0"
warning "~4.0.2"
anymatch@^1.3.0:
version "1.3.2"
@ -361,7 +380,7 @@ array-tree-filter@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-1.0.1.tgz#0a8ad1eefd38ce88858632f9cc0423d7634e4d5d"
array-tree-filter@^2.0.0:
array-tree-filter@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190"
@ -425,7 +444,7 @@ async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
async-validator@1.x:
async-validator@~1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-1.8.5.tgz#dc3e08ec1fd0dddb67e60842f02c0cd1cec6d7f0"
dependencies:
@ -1672,7 +1691,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@2.x, classnames@^2.2.0, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@~2.2.0:
classnames@2.x, classnames@^2.2.0, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@~2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
@ -1973,7 +1992,7 @@ copy-descriptor@^0.1.0:
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
resolved "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0, core-js@^2.5.0:
version "2.5.7"
@ -2029,7 +2048,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
create-react-class@^15.5.2, create-react-class@^15.5.3, create-react-class@^15.6.0:
create-react-class@^15.5.2, create-react-class@^15.5.3, create-react-class@^15.6.0, create-react-class@^15.6.3:
version "15.6.3"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
dependencies:
@ -2037,9 +2056,9 @@ create-react-class@^15.5.2, create-react-class@^15.5.3, create-react-class@^15.6
loose-envify "^1.3.1"
object-assign "^4.1.1"
create-react-context@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
create-react-context@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
dependencies:
fbjs "^0.8.0"
gud "^1.0.0"
@ -2072,7 +2091,7 @@ crypto-random-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
css-animation@1.x, css-animation@^1.2.5, css-animation@^1.3.2:
css-animation@1.x, css-animation@^1.3.2, css-animation@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.4.1.tgz#5b8813125de0fbbbb0bbe1b472ae84221469b7a8"
dependencies:
@ -2599,7 +2618,7 @@ enhanced-resolve@^3.4.0:
object-assign "^4.0.1"
tapable "^0.2.7"
enquire.js@^2.1.1, enquire.js@^2.1.6:
enquire.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814"
@ -3865,12 +3884,18 @@ iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
iconv-lite@^0.4.17, iconv-lite@^0.4.4:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
dependencies:
safer-buffer ">= 2.1.2 < 3"
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@ -3913,7 +3938,7 @@ immutable@^3.7.4:
immutable@~3.7.4:
version "3.7.6"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
resolved "http://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
import-lazy@^2.1.0:
version "2.1.0"
@ -5058,10 +5083,14 @@ lodash.without@~4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
"lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.16.5, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0:
"lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.3.0:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
lodash@^4.16.5, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
loglevel@^1.4.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
@ -5304,9 +5333,9 @@ mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
mini-store@^1.0.2, mini-store@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/mini-store/-/mini-store-1.1.2.tgz#cc150e0878e080ca58219d47fccefefe2c9aea3e"
mini-store@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mini-store/-/mini-store-2.0.0.tgz#0843c048d6942ce55e3e78b1b67fc063022b5488"
dependencies:
hoist-non-react-statics "^2.3.1"
prop-types "^15.6.0"
@ -5401,7 +5430,7 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
dependencies:
minimist "0.0.8"
moment@2.x, moment@^2.19.3:
moment@2.x, moment@^2.22.2:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
@ -5435,6 +5464,10 @@ multicast-dns@^6.0.1:
dns-packet "^1.3.1"
thunky "^1.0.2"
mutationobserver-shim@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#f4d5dae7a4971a2207914fb5a90ebd514b65acca"
mute-stream@0.0.7, mute-stream@~0.0.4:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@ -6580,6 +6613,10 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.14.3:
version "1.14.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895"
pretty-bytes@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
@ -6649,7 +6686,7 @@ promzard@^0.3.0:
dependencies:
read "1"
prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0, prop-types@^15.6.1:
prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
@ -6794,12 +6831,18 @@ qw@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
raf@3.4.0, raf@^3.4.0:
raf@3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
dependencies:
performance-now "^2.1.0"
raf@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
dependencies:
performance-now "^2.1.0"
randomatic@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.0.0.tgz#d35490030eb4f7578de292ce6dfb04a91a128923"
@ -6843,17 +6886,20 @@ rc-align@^2.4.0, rc-align@^2.4.1:
prop-types "^15.5.8"
rc-util "^4.0.4"
rc-animate@2.x, rc-animate@^2.3.0, rc-animate@^2.4.1:
version "2.4.4"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.4.4.tgz#a05a784c747beef140d99ff52b6117711bef4b1e"
rc-animate@2.x, rc-animate@^2.3.0, rc-animate@^2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.5.4.tgz#3b308c42e137a2e3fb578650fdb145c2100fcc35"
dependencies:
babel-runtime "6.x"
classnames "^2.2.6"
css-animation "^1.3.2"
prop-types "15.x"
raf "^3.4.0"
react-lifecycles-compat "^3.0.4"
rc-animate@3.0.0-rc.1:
version "3.0.0-rc.1"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.0.0-rc.1.tgz#39703e5be6d35c0c50ae53126a6c2424e5ec9405"
rc-animate@^3.0.0-rc.1, rc-animate@^3.0.0-rc.4, rc-animate@^3.0.0-rc.5:
version "3.0.0-rc.6"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.0.0-rc.6.tgz#04288eefa118e0cae214536c8a903ffaac1bc3fb"
dependencies:
babel-runtime "6.x"
classnames "^2.2.5"
@ -6864,22 +6910,9 @@ rc-animate@3.0.0-rc.1:
rc-util "^4.5.0"
react-lifecycles-compat "^3.0.4"
rc-animate@^3.0.0-rc.1, rc-animate@^3.0.0-rc.4:
version "3.0.0-rc.4"
resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.0.0-rc.4.tgz#caddbd849f01d987965c6237afe64167679497bd"
dependencies:
babel-runtime "6.x"
classnames "^2.2.5"
component-classes "^1.2.6"
fbjs "^0.8.16"
prop-types "15.x"
raf "^3.4.0"
rc-util "^4.5.0"
react-lifecycles-compat "^3.0.4"
rc-calendar@~9.6.0:
version "9.6.2"
resolved "https://registry.yarnpkg.com/rc-calendar/-/rc-calendar-9.6.2.tgz#c7309db41225f4b8c81d5a1dcbe46d8ce07b6aee"
rc-calendar@~9.7.9:
version "9.7.11"
resolved "https://registry.yarnpkg.com/rc-calendar/-/rc-calendar-9.7.11.tgz#fa27a6e47018eb71eb6d1857cdcbd161c266dbe0"
dependencies:
babel-runtime "6.x"
classnames "2.x"
@ -6889,9 +6922,9 @@ rc-calendar@~9.6.0:
rc-trigger "^2.2.0"
rc-util "^4.1.1"
rc-cascader@~0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-0.14.0.tgz#a956c99896f10883bf63d46fb894d0cb326842a4"
rc-cascader@~0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-0.16.0.tgz#11baa854c2aaa2d6a8f601dec75dd136d59c5156"
dependencies:
array-tree-filter "^1.0.0"
prop-types "^15.5.8"
@ -6909,35 +6942,35 @@ rc-checkbox@~2.1.5:
prop-types "15.x"
rc-util "^4.0.4"
rc-collapse@~1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-1.9.3.tgz#d9741db06a823353e1fd1aec3ba4c0f9d8af4b26"
rc-collapse@~1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-1.10.0.tgz#b39578633a1e033391597776758763a10d3bc261"
dependencies:
classnames "2.x"
css-animation "1.x"
prop-types "^15.5.6"
rc-animate "2.x"
rc-dialog@~7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-7.2.0.tgz#b56a2d3a56003437d6ff8917eac6b0fc601936ab"
rc-dialog@~7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-7.2.1.tgz#ac92fcffdf2a0eaa64b77f829336653d911a57be"
dependencies:
babel-runtime "6.x"
rc-animate "2.x"
rc-util "^4.4.0"
rc-drawer@~1.6.2:
version "1.6.3"
resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-1.6.3.tgz#f866b7fbde2d307b59cfd06c015ae697017db388"
rc-drawer@~1.7.6:
version "1.7.6"
resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-1.7.6.tgz#925ce0768cf81ef5fa83eb22d90c603422b1b1b1"
dependencies:
babel-runtime "6.x"
classnames "^2.2.5"
prop-types "^15.5.0"
rc-util "^4.5.1"
rc-dropdown@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-2.2.0.tgz#a905067666ce73c0ce26cf0980e7b75b46126825"
rc-dropdown@~2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-2.2.1.tgz#172b6e87f0909fe8ab983e375f62e2866f3250c3"
dependencies:
babel-runtime "^6.26.0"
prop-types "^15.5.8"
@ -6945,8 +6978,8 @@ rc-dropdown@~2.2.0:
react-lifecycles-compat "^3.0.2"
rc-editor-core@~0.8.3:
version "0.8.6"
resolved "https://registry.yarnpkg.com/rc-editor-core/-/rc-editor-core-0.8.6.tgz#e48b288286effb3272cbc9c6f801450dcdb0b247"
version "0.8.8"
resolved "https://registry.yarnpkg.com/rc-editor-core/-/rc-editor-core-0.8.8.tgz#331034cb8d50df218839fb399cdfb2a913e71630"
dependencies:
babel-runtime "^6.26.0"
classnames "^2.2.5"
@ -6956,23 +6989,24 @@ rc-editor-core@~0.8.3:
prop-types "^15.5.8"
setimmediate "^1.0.5"
rc-editor-mention@^1.0.2:
version "1.1.7"
resolved "https://registry.yarnpkg.com/rc-editor-mention/-/rc-editor-mention-1.1.7.tgz#c72d181859beda96669f4b43e19a941e68fa985b"
rc-editor-mention@^1.1.7:
version "1.1.8"
resolved "https://registry.yarnpkg.com/rc-editor-mention/-/rc-editor-mention-1.1.8.tgz#08dafc50b1ab9ad4d2355eedd6308373ea66473a"
dependencies:
babel-runtime "^6.23.0"
classnames "^2.2.5"
dom-scroll-into-view "^1.2.0"
draft-js "~0.10.0"
immutable "^3.7.4"
prop-types "^15.5.8"
rc-animate "^2.3.0"
rc-editor-core "~0.8.3"
rc-form@^2.1.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/rc-form/-/rc-form-2.2.1.tgz#9c6d968f1460fd3872d2e9371324d717775af260"
rc-form@^2.2.3:
version "2.2.6"
resolved "https://registry.yarnpkg.com/rc-form/-/rc-form-2.2.6.tgz#737bd1eb1f45c6ce821854e86248e145adb6230c"
dependencies:
async-validator "1.x"
async-validator "~1.8.5"
babel-runtime "6.x"
create-react-class "^15.5.3"
dom-scroll-into-view "1.x"
@ -6988,9 +7022,9 @@ rc-hammerjs@~0.6.0:
hammerjs "^2.0.8"
prop-types "^15.5.9"
rc-input-number@~4.0.0:
version "4.0.12"
resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-4.0.12.tgz#99b62f0b2395e1e76c9f72142b4ad7ea46f97d06"
rc-input-number@~4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-4.3.1.tgz#f4b7f414f00816fd803ce5f11aac25bd38dd0f22"
dependencies:
babel-runtime "6.x"
classnames "^2.2.0"
@ -6999,31 +7033,20 @@ rc-input-number@~4.0.0:
rc-util "^4.5.1"
rmc-feedback "^2.0.0"
rc-menu@^7.0.2:
version "7.2.6"
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-7.2.6.tgz#b18b8da9d637533145c6bc28cae1f29ae55c8dc7"
rc-menu@^7.3.0, rc-menu@~7.4.12:
version "7.4.19"
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-7.4.19.tgz#b65a35215966053fe6f6c3b7b12155051969f567"
dependencies:
babel-runtime "6.x"
classnames "2.x"
dom-scroll-into-view "1.x"
mini-store "^1.1.0"
prop-types "^15.5.6"
rc-animate "2.x"
rc-trigger "^2.3.0"
rc-util "^4.1.0"
rc-menu@~7.0.2:
version "7.0.5"
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-7.0.5.tgz#986b65df5ad227aadf399ea374b98d2313802316"
dependencies:
babel-runtime "6.x"
classnames "2.x"
dom-scroll-into-view "1.x"
mini-store "^1.1.0"
mini-store "^2.0.0"
mutationobserver-shim "^0.3.2"
prop-types "^15.5.6"
rc-animate "2.x"
rc-trigger "^2.3.0"
rc-util "^4.1.0"
resize-observer-polyfill "^1.5.0"
rc-notification@~3.2.0:
version "3.2.0"
@ -7035,21 +7058,21 @@ rc-notification@~3.2.0:
rc-animate "2.x"
rc-util "^4.0.4"
rc-pagination@~1.16.1:
version "1.16.5"
resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-1.16.5.tgz#550a758035e1957ccfa2f71ee6e55657da729679"
rc-pagination@~1.17.3:
version "1.17.3"
resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-1.17.3.tgz#4c5334adef607f7a80b90ca7501a1e962491aae5"
dependencies:
babel-runtime "6.x"
prop-types "^15.5.7"
rc-progress@~2.2.2:
version "2.2.5"
resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-2.2.5.tgz#e61d0544bf9d4208e5ba32fc50962159e7f952a3"
rc-progress@~2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-2.2.6.tgz#d5d07c07333b352a9ef13230c5940e13336c1e62"
dependencies:
babel-runtime "6.x"
prop-types "^15.5.8"
rc-rate@~2.4.0:
rc-rate@~2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.4.2.tgz#c097bfdba7a5783cec287c928b1461cc1621f836"
dependencies:
@ -7058,9 +7081,9 @@ rc-rate@~2.4.0:
prop-types "^15.5.8"
rc-util "^4.3.0"
rc-select@~8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-8.1.1.tgz#89335cf0af518affe201bb6beefe161e1b4b2ab6"
rc-select@~8.4.0:
version "8.4.4"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-8.4.4.tgz#b293bee3ebc2a45d4886b2b5f80b06f6d5a02b77"
dependencies:
babel-runtime "^6.23.0"
classnames "2.x"
@ -7069,15 +7092,15 @@ rc-select@~8.1.1:
prop-types "^15.5.8"
raf "^3.4.0"
rc-animate "2.x"
rc-menu "^7.0.2"
rc-trigger "^2.2.0"
rc-menu "^7.3.0"
rc-trigger "^2.5.4"
rc-util "^4.0.4"
react-lifecycles-compat "^3.0.2"
warning "^3.0.0"
warning "^4.0.2"
rc-slider@~8.6.0:
version "8.6.1"
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-8.6.1.tgz#ee5e0380dbdf4b5de6955a265b0d4ff6196405d1"
rc-slider@~8.6.3:
version "8.6.3"
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-8.6.3.tgz#1ca0e0bd2863252741de75e7bf8c9f2cfcffccb7"
dependencies:
babel-runtime "6.x"
classnames "^2.2.5"
@ -7087,41 +7110,41 @@ rc-slider@~8.6.0:
shallowequal "^1.0.1"
warning "^3.0.0"
rc-steps@~3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-3.1.1.tgz#79583ad808309d82b8e011676321d153fd7ca403"
rc-steps@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-3.3.0.tgz#8817c438a6a5648997c7edb51bde727e6f32e132"
dependencies:
babel-runtime "^6.23.0"
classnames "^2.2.3"
lodash "^4.17.5"
prop-types "^15.5.7"
rc-switch@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-1.6.0.tgz#c2d7369bdb87c1fd45e84989a27c1fb2f201d2fd"
rc-switch@~1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-1.8.0.tgz#cff32fd04c406d8c0c0397e69bc36350a333e236"
dependencies:
babel-runtime "^6.23.0"
classnames "^2.2.1"
prop-types "^15.5.6"
rc-table@~6.2.0:
version "6.2.8"
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-6.2.8.tgz#c06bf48bfab60754ab3f70102290e41e8b32388e"
rc-table@~6.3.4:
version "6.3.7"
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-6.3.7.tgz#0de4f71415abe3a8d9f9937ab7ddd142ab6143b7"
dependencies:
babel-runtime "6.x"
classnames "^2.2.5"
component-classes "^1.2.6"
lodash "^4.17.5"
mini-store "^1.0.2"
mini-store "^2.0.0"
prop-types "^15.5.8"
rc-util "^4.0.4"
react-lifecycles-compat "^3.0.2"
shallowequal "^1.0.2"
warning "^3.0.0"
rc-tabs@~9.3.3:
version "9.3.6"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-9.3.6.tgz#873890b3a68164a5814f89e343270b1ce9eb6acd"
rc-tabs@~9.4.6:
version "9.4.8"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-9.4.8.tgz#62144284189d9e36472b6a6f6948bdba715bbccc"
dependencies:
babel-runtime "6.x"
classnames "2.x"
@ -7131,9 +7154,9 @@ rc-tabs@~9.3.3:
rc-util "^4.0.4"
warning "^3.0.0"
rc-time-picker@~3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.3.1.tgz#94f8bbd51e6b93de1f01e78064aef1e6d765b367"
rc-time-picker@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.4.0.tgz#274e80122f885b37a4eace7393f3a25334fa141f"
dependencies:
babel-runtime "6.x"
classnames "2.x"
@ -7141,56 +7164,45 @@ rc-time-picker@~3.3.0:
prop-types "^15.5.8"
rc-trigger "^2.2.0"
rc-tooltip@^3.7.0, rc-tooltip@~3.7.0:
version "3.7.2"
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-3.7.2.tgz#3698656d4bacd51b72d9e327bed15d1d5a9f1b27"
rc-tooltip@^3.7.0, rc-tooltip@~3.7.3:
version "3.7.3"
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-3.7.3.tgz#280aec6afcaa44e8dff0480fbaff9e87fc00aecc"
dependencies:
babel-runtime "6.x"
prop-types "^15.5.8"
rc-trigger "^2.2.2"
rc-tree-select@~2.0.5:
version "2.0.13"
resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-2.0.13.tgz#7bd1d5de61cb69d8048bf0d29dc3785aee6815db"
rc-tree-select@~2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-2.3.2.tgz#bf617a55c0ed365feeab5c3ee5ff5cf4dd5c7fc2"
dependencies:
babel-runtime "^6.23.0"
classnames "^2.2.1"
prop-types "^15.5.8"
raf "^3.4.0"
rc-animate "^3.0.0-rc.4"
rc-tree "~1.12.2"
rc-tree "~1.14.3"
rc-trigger "^3.0.0-rc.2"
rc-util "^4.5.0"
react-lifecycles-compat "^3.0.4"
shallowequal "^1.0.2"
warning "^4.0.1"
rc-tree@~1.12.2:
version "1.12.7"
resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-1.12.7.tgz#94ce4b59d27325c555d6238c7b92feeaa5d476a0"
rc-tree@~1.14.3, rc-tree@~1.14.6:
version "1.14.8"
resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-1.14.8.tgz#31a9652d71c015370d7b6c2109c865244deb9fde"
dependencies:
babel-runtime "^6.23.0"
classnames "2.x"
prop-types "^15.5.8"
rc-animate "2.x"
rc-util "^4.0.4"
warning "^3.0.0"
rc-tree@~1.13.0:
version "1.13.2"
resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-1.13.2.tgz#47cd31a51871f26f50e34167d9dcf36a68c44955"
dependencies:
babel-runtime "^6.23.0"
classnames "2.x"
prop-types "^15.5.8"
rc-animate "3.0.0-rc.1"
rc-animate "^3.0.0-rc.5"
rc-util "^4.5.1"
react-lifecycles-compat "^3.0.4"
warning "^3.0.0"
rc-trigger@^2.2.0, rc-trigger@^2.2.2, rc-trigger@^2.3.0, rc-trigger@^2.5.1, rc-trigger@^2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.5.4.tgz#9088a24ba5a811b254f742f004e38a9e2f8843fb"
rc-trigger@^2.2.0, rc-trigger@^2.2.2, rc-trigger@^2.3.0, rc-trigger@^2.5.1, rc-trigger@^2.5.4, rc-trigger@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.6.2.tgz#a9c09ba5fad63af3b2ec46349c7db6cb46657001"
dependencies:
babel-runtime "6.x"
classnames "^2.2.6"
@ -7211,9 +7223,9 @@ rc-trigger@^3.0.0-rc.2:
rc-animate "^3.0.0-rc.1"
rc-util "^4.4.0"
rc-upload@~2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-2.5.1.tgz#7ae0c9038d98ba8750e9466d8f969e1b4bc9f0e0"
rc-upload@~2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-2.6.0.tgz#9cfb8dda8b1bbae823a076d2dd81a5c0b3f0aa00"
dependencies:
babel-runtime "6.x"
classnames "^2.2.5"
@ -7221,10 +7233,10 @@ rc-upload@~2.5.0:
warning "2.x"
rc-util@^4.0.4, rc-util@^4.1.0, rc-util@^4.1.1, rc-util@^4.3.0, rc-util@^4.4.0, rc-util@^4.5.0, rc-util@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.5.1.tgz#0e435057174c024901c7600ba8903dd03da3ab39"
version "4.6.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.6.0.tgz#ba33721783192ec4f3afb259e182b04e55deb7f6"
dependencies:
add-dom-event-listener "1.x"
add-dom-event-listener "^1.1.0"
babel-runtime "6.x"
prop-types "^15.5.10"
shallowequal "^0.2.2"
@ -7293,7 +7305,7 @@ react-error-overlay@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4"
react-lazy-load@^3.0.12:
react-lazy-load@^3.0.13:
version "3.0.13"
resolved "https://registry.yarnpkg.com/react-lazy-load/-/react-lazy-load-3.0.13.tgz#3b0a92d336d43d3f0d73cbe6f35b17050b08b824"
dependencies:
@ -7386,14 +7398,15 @@ react-scripts@1.1.4:
optionalDependencies:
fsevents "^1.1.3"
react-slick@~0.23.1:
version "0.23.1"
resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.23.1.tgz#15791c4107f0ba3a5688d5bd97b7b7ceaa0dd181"
react-slick@~0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.23.2.tgz#8d8bdbc77a6678e8ad36f50c32578c7c0f1c54f6"
dependencies:
classnames "^2.2.5"
enquire.js "^2.1.6"
json2mq "^0.2.0"
lodash.debounce "^4.0.8"
prettier "^1.14.3"
resize-observer-polyfill "^1.5.0"
react@^16.4.1:
@ -8026,7 +8039,7 @@ shallowequal@^0.2.2:
dependencies:
lodash.keys "^3.1.2"
shallowequal@^1.0.1, shallowequal@^1.0.2:
shallowequal@^1.0.1, shallowequal@^1.0.2, shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
@ -8601,6 +8614,10 @@ tiny-relative-date@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07"
tinycolor2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -8706,8 +8723,8 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
ua-parser-js@^0.7.18:
version "0.7.18"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"
version "0.7.19"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
uglify-js@3.4.x, uglify-js@^3.0.13:
version "3.4.4"
@ -8974,9 +8991,9 @@ warning@^3.0.0:
dependencies:
loose-envify "^1.0.0"
warning@^4.0.1, warning@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.1.tgz#66ce376b7fbfe8a887c22bdf0e7349d73d397745"
warning@^4.0.1, warning@^4.0.2, warning@~4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607"
dependencies:
loose-envify "^1.0.0"
@ -9117,8 +9134,8 @@ whatwg-fetch@2.0.3:
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
whatwg-fetch@>=0.10.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
whatwg-url@^4.3.0:
version "4.8.0"

View File

@ -13,9 +13,9 @@ import (
// reqImport represents file upload import params.
type reqImport struct {
Delim string `json:"delim"`
OverrideStatus bool `json:"override_status"`
ListIDs []int `json:"lists"`
Mode string `json:"mode"`
Delim string `json:"delim"`
ListIDs []int `json:"lists"`
}
// handleImportSubscribers handles the uploading and bulk importing of
@ -36,6 +36,11 @@ func handleImportSubscribers(c echo.Context) error {
fmt.Sprintf("Invalid `params` field: %v", err))
}
if r.Mode != subimporter.ModeSubscribe && r.Mode != subimporter.ModeBlacklist {
return echo.NewHTTPError(http.StatusBadRequest,
"Invalid `mode`")
}
if len(r.Delim) != 1 {
return echo.NewHTTPError(http.StatusBadRequest,
"`delim` should be a single character")
@ -66,11 +71,10 @@ func handleImportSubscribers(c echo.Context) error {
}
// Start the importer session.
impSess, err := app.Importer.NewSession(file.Filename,
r.OverrideStatus,
r.ListIDs)
impSess, err := app.Importer.NewSession(file.Filename, r.Mode, r.ListIDs)
if err != nil {
return err
return echo.NewHTTPError(http.StatusBadRequest,
fmt.Sprintf("Error starting import session: %v", err))
}
go impSess.Start()
@ -81,7 +85,8 @@ func handleImportSubscribers(c echo.Context) error {
return echo.NewHTTPError(http.StatusInternalServerError,
fmt.Sprintf("Error extracting ZIP file: %v", err))
} else if len(files) == 0 {
return echo.NewHTTPError(http.StatusBadRequest, "No CSV files found to import.")
return echo.NewHTTPError(http.StatusBadRequest,
"No CSV files found to import.")
}
go impSess.LoadCSV(dir+"/"+files[0], rune(r.Delim[0]))
@ -94,7 +99,6 @@ func handleGetImportSubscribers(c echo.Context) error {
app = c.Get("app").(*App)
s = app.Importer.GetStats()
)
return c.JSON(http.StatusOK, okResp{s})
}
@ -110,6 +114,5 @@ func handleGetImportSubscriberLogs(c echo.Context) error {
func handleStopImportSubscribers(c echo.Context) error {
app := c.Get("app").(*App)
app.Importer.Stop()
return c.JSON(http.StatusOK, okResp{app.Importer.GetStats()})
}

View File

@ -122,9 +122,7 @@ func install(app *App, qMap goyesql.Queries) {
uuid.NewV4(),
email,
bytes.Title(name[0]),
models.SubscriberStatusEnabled,
`{"type": "known", "good": true}`,
true,
pq.Int64Array{int64(listID)},
); err != nil {
logger.Fatalf("Error creating subscriber: %v", err)

View File

@ -221,7 +221,7 @@ func main() {
}
app.Queries = q
app.Importer = subimporter.New(q.UpsertSubscriber.Stmt, db.DB)
app.Importer = subimporter.New(q.UpsertSubscriber.Stmt, q.BlacklistSubscriber.Stmt, db.DB)
// Campaign daemon.
r := runner.New(runner.Config{

View File

@ -9,6 +9,7 @@ import (
// Queries contains all prepared SQL queries.
type Queries struct {
UpsertSubscriber *sqlx.Stmt `query:"upsert-subscriber"`
BlacklistSubscriber *sqlx.Stmt `query:"blacklist-subscriber"`
GetSubscriber *sqlx.Stmt `query:"get-subscriber"`
GetSubscribersByEmails *sqlx.Stmt `query:"get-subscribers-by-emails"`
GetSubscriberLists *sqlx.Stmt `query:"get-subscriber-lists"`

View File

@ -40,19 +40,35 @@ SELECT COUNT(subscribers.id) as num FROM subscribers INNER JOIN subscriber_lists
%s;
-- name: upsert-subscriber
-- In case of updates, if $6 (override_status) is true, only then, the existing
-- value is overwritten with the incoming value. This is used for insertions and bulk imports.
-- Upserts a subscriber where existing subscribers get their names and attributes overwritten.
-- The status field is only updated when $6 = 'override_status'.
WITH s AS (
INSERT INTO subscribers (uuid, email, name, status, attribs)
VALUES($1, $2, $3, $4, $5) ON CONFLICT (email) DO UPDATE
SET name=$3, status=(CASE WHEN $6 = true THEN $4 ELSE subscribers.status END),
attribs=$5, updated_at=NOW()
INSERT INTO subscribers (uuid, email, name, attribs)
VALUES($1, $2, $3, $4)
ON CONFLICT (email) DO UPDATE
SET name=$3,
attribs=$4,
updated_at=NOW()
RETURNING id
) INSERT INTO subscriber_lists (subscriber_id, list_id)
VALUES((SELECT id FROM s), UNNEST($7::INT[]) )
ON CONFLICT (subscriber_id, list_id) DO NOTHING
VALUES((SELECT id FROM s), UNNEST($5::INT[]))
ON CONFLICT (subscriber_id, list_id) DO UPDATE
SET updated_at=NOW()
RETURNING subscriber_id;
-- name: blacklist-subscriber
-- Upserts a subscriber where the update will only set the status to blacklisted
-- unlike upsert-subscribers where name and attributes are updated. In addition, all
-- existing subscriptions are marked as 'unsubscribed'.
WITH sub AS (
INSERT INTO subscribers (uuid, email, name, attribs, status)
VALUES($1, $2, $3, $4, 'blacklisted')
ON CONFLICT (email) DO UPDATE SET status='blacklisted', updated_at=NOW()
RETURNING id
)
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
WHERE subscriber_id = (SELECT id FROM sub);
-- name: update-subscriber
-- Updates a subscriber's data, and given a list of list_ids, inserts subscriptions
-- for them while deleting existing subscriptions not in the list.

View File

@ -29,7 +29,7 @@ CREATE TABLE subscribers (
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
attribs JSONB,
status subscriber_status NOT NULL,
status subscriber_status NOT NULL DEFAULT 'enabled',
campaigns INTEGER[],
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

View File

@ -46,11 +46,15 @@ const (
StatusStopping = "stopping"
StatusFinished = "finished"
StatusFailed = "failed"
ModeSubscribe = "subscribe"
ModeBlacklist = "blacklist"
)
// Importer represents the bulk CSV subscriber import system.
type Importer struct {
stmt *sql.Stmt
upsert *sql.Stmt
blacklist *sql.Stmt
db *sql.DB
isImporting bool
stop chan bool
@ -65,8 +69,8 @@ type Session struct {
subQueue chan SubReq
log *log.Logger
overrideStatus bool
listIDs []int
mode string
listIDs []int
}
// Status reporesents statistics from an ongoing import session.
@ -91,17 +95,17 @@ var (
csvHeaders = map[string]bool{"email": true,
"name": true,
"status": true,
"attributes": true}
)
// New returns a new instance of Importer.
func New(stmt *sql.Stmt, db *sql.DB) *Importer {
func New(upsert *sql.Stmt, blacklist *sql.Stmt, db *sql.DB) *Importer {
im := Importer{
stmt: stmt,
stop: make(chan bool, 1),
db: db,
status: &Status{Status: StatusNone, logBuf: bytes.NewBuffer(nil)},
upsert: upsert,
blacklist: blacklist,
stop: make(chan bool, 1),
db: db,
status: &Status{Status: StatusNone, logBuf: bytes.NewBuffer(nil)},
}
return &im
@ -109,7 +113,7 @@ func New(stmt *sql.Stmt, db *sql.DB) *Importer {
// NewSession returns an new instance of Session. It takes the name
// of the uploaded file, but doesn't do anything with it but retains it for stats.
func (im *Importer) NewSession(fName string, overrideStatus bool, listIDs []int) (*Session, error) {
func (im *Importer) NewSession(fName, mode string, listIDs []int) (*Session, error) {
if im.getStatus() != StatusNone {
return nil, errors.New("an import is already running")
}
@ -121,11 +125,11 @@ func (im *Importer) NewSession(fName string, overrideStatus bool, listIDs []int)
im.Unlock()
s := &Session{
im: im,
log: log.New(im.status.logBuf, "", log.Ldate|log.Ltime),
subQueue: make(chan SubReq, commitBatchSize),
overrideStatus: overrideStatus,
listIDs: listIDs,
im: im,
log: log.New(im.status.logBuf, "", log.Ldate|log.Ltime),
subQueue: make(chan SubReq, commitBatchSize),
mode: mode,
listIDs: listIDs,
}
s.log.Printf("processing '%s'", fName)
@ -136,7 +140,6 @@ func (im *Importer) NewSession(fName string, overrideStatus bool, listIDs []int)
func (im *Importer) GetStats() Status {
im.RLock()
defer im.RUnlock()
return Status{
Name: im.status.Name,
Status: im.status.Status,
@ -206,17 +209,20 @@ func (s *Session) Start() {
s.log.Printf("error creating DB transaction: %v", err)
continue
}
stmt = tx.Stmt(s.im.stmt)
if s.mode == ModeSubscribe {
stmt = tx.Stmt(s.im.upsert)
} else {
stmt = tx.Stmt(s.im.blacklist)
}
}
_, err := stmt.Exec(
uuid.NewV4(),
sub.Email,
sub.Name,
sub.Status,
sub.Attribs,
s.overrideStatus,
listIDs)
var err error
if s.mode == ModeSubscribe {
_, err = stmt.Exec(uuid.NewV4(), sub.Email, sub.Name, sub.Attribs, listIDs)
} else if s.mode == ModeBlacklist {
_, err = stmt.Exec(uuid.NewV4(), sub.Email, sub.Name, sub.Attribs)
}
if err != nil {
s.log.Printf("error executing insert: %v", err)
tx.Rollback()
@ -403,8 +409,13 @@ func (s *Session) LoadCSV(srcPath string, delim rune) error {
if err == io.EOF {
break
} else if err != nil {
s.log.Printf("error reading CSV '%s'", err)
return err
if err, ok := err.(*csv.ParseError); ok && err.Err == csv.ErrFieldCount {
s.log.Printf("skipping line %d. %v", i, err)
continue
} else {
s.log.Printf("error reading CSV '%s'", err)
return err
}
}
lnCols := len(cols)
@ -424,13 +435,6 @@ func (s *Session) LoadCSV(srcPath string, delim rune) error {
// Lowercase to ensure uniqueness in the DB.
sub.Email = strings.ToLower(strings.TrimSpace(row["email"]))
sub.Name = row["name"]
if _, ok := row["status"]; ok {
sub.Status = row["status"]
} else {
sub.Status = models.SubscriberStatusEnabled
}
if err := ValidateFields(sub); err != nil {
s.log.Printf("skipping line %d: %v", i, err)
continue
@ -502,10 +506,6 @@ func ValidateFields(s SubReq) error {
if !govalidator.IsByteLength(s.Name, 1, stdInputMaxLen) {
return errors.New("invalid or empty `name`")
}
if s.Status != SubscriberStatusEnabled && s.Status != SubscriberStatusDisabled &&
s.Status != SubscriberStatusBlacklisted {
return errors.New("invalid `status`")
}
return nil
}