From f2c09e716ce9c887803f744eedfba6ea44b566a3 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Mon, 5 Nov 2018 16:59:09 +0530 Subject: [PATCH] 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 --- frontend/my/src/App.js | 5 +- frontend/my/src/Campaigns.js | 6 +- frontend/my/src/Import.js | 66 +++--- frontend/my/src/index.css | 3 + frontend/my/yarn.lock | 431 ++++++++++++++++++----------------- import.go | 23 +- install.go | 2 - main.go | 2 +- queries.go | 1 + queries.sql | 32 ++- schema.sql | 2 +- subimporter/importer.go | 76 +++--- 12 files changed, 352 insertions(+), 297 deletions(-) diff --git a/frontend/my/src/App.js b/frontend/my/src/App.js index 7426f92..04777f5 100644 --- a/frontend/my/src/App.js +++ b/frontend/my/src/App.js @@ -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. diff --git a/frontend/my/src/Campaigns.js b/frontend/my/src/Campaigns.js index 30cc40a..dee6172 100644 --- a/frontend/my/src/Campaigns.js +++ b/frontend/my/src/Campaigns.js @@ -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` }) diff --git a/frontend/my/src/Import.js b/frontend/my/src/Import.js index 85b06bc..b456fc3 100644 --- a/frontend/my/src/Import.js +++ b/frontend/my/src/Import.js @@ -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 (
- - {getFieldDecorator("lists", { rules: [{ required: true }] })( - + {[...this.props.lists].map((v, i) => + {v["name"]} + )} + )} - - )} - - - {getFieldDecorator("override_status", )( - - )} - + + + } + { this.state.mode === "blacklist" && + +

+ 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'. +

+
+ } {getFieldDecorator("delim", { initialValue: "," @@ -113,14 +127,14 @@ class TheFormDef extends React.PureComponent {

-

Click or drag file here

+

Click or drag the ZIP file here

)}
-

For existing subscribers, the names and attributes will be overwritten with the values in the CSV.

- +

For existing subscribers, the names and attributes will be overwritten with the values in the CSV.

+
@@ -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 }) }) } diff --git a/frontend/my/src/index.css b/frontend/my/src/index.css index a2dcf49..8ad42de 100644 --- a/frontend/my/src/index.css +++ b/frontend/my/src/index.css @@ -69,6 +69,9 @@ body { display: none; } +/* Form */ + + /* Table actions */ td .actions a { display: inline-block; diff --git a/frontend/my/yarn.lock b/frontend/my/yarn.lock index a594012..ababcad 100644 --- a/frontend/my/yarn.lock +++ b/frontend/my/yarn.lock @@ -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" diff --git a/import.go b/import.go index 9125047..c23df14 100644 --- a/import.go +++ b/import.go @@ -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()}) } diff --git a/install.go b/install.go index c16fd04..2759c42 100644 --- a/install.go +++ b/install.go @@ -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) diff --git a/main.go b/main.go index f13d054..4071a89 100644 --- a/main.go +++ b/main.go @@ -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{ diff --git a/queries.go b/queries.go index 7d71ca0..f6acd3b 100644 --- a/queries.go +++ b/queries.go @@ -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"` diff --git a/queries.sql b/queries.sql index 988ac34..e57431d 100644 --- a/queries.sql +++ b/queries.sql @@ -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. diff --git a/schema.sql b/schema.sql index 37d3bf4..a7946e9 100644 --- a/schema.sql +++ b/schema.sql @@ -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(), diff --git a/subimporter/importer.go b/subimporter/importer.go index d1de6b7..45fdf00 100644 --- a/subimporter/importer.go +++ b/subimporter/importer.go @@ -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 }