├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.json ├── client-dist ├── css │ ├── app.577fb7dd.css │ ├── chunk-0b705865.03a16c35.css │ ├── chunk-6056c388.7f735cea.css │ ├── chunk-6065e614.7f735cea.css │ ├── chunk-619b7fb8.bc44b1f0.css │ ├── chunk-c5a431ce.5144c95a.css │ ├── chunk-cfaaa91c.95549bc3.css │ ├── chunk-d4383190.95549bc3.css │ ├── chunk-d466345a.95549bc3.css │ ├── chunk-d4b8c67a.95549bc3.css │ ├── chunk-d4c7e4a2.95549bc3.css │ └── chunk-vendors.08ed5846.css ├── favicon-32x32.png ├── img │ └── RedisLabsIllustration.bebd0eb3.svg ├── index.html └── js │ ├── app.81d04ada.js │ ├── app.81d04ada.js.map │ ├── chunk-0b705865.ed6e3b73.js │ ├── chunk-0b705865.ed6e3b73.js.map │ ├── chunk-2d0a3cb4.0a490a8d.js │ ├── chunk-2d0a3cb4.0a490a8d.js.map │ ├── chunk-2d0a443a.5a3bc57b.js │ ├── chunk-2d0a443a.5a3bc57b.js.map │ ├── chunk-2d0b8ef3.7b3b9b84.js │ ├── chunk-2d0b8ef3.7b3b9b84.js.map │ ├── chunk-2d0e9995.bf3e2570.js │ ├── chunk-2d0e9995.bf3e2570.js.map │ ├── chunk-2d0f089f.b19c1b9a.js │ ├── chunk-2d0f089f.b19c1b9a.js.map │ ├── chunk-2d208337.3659041c.js │ ├── chunk-2d208337.3659041c.js.map │ ├── chunk-2d208a1d.b5b3892b.js │ ├── chunk-2d208a1d.b5b3892b.js.map │ ├── chunk-2d2137a6.fb94b285.js │ ├── chunk-2d2137a6.fb94b285.js.map │ ├── chunk-2d21ed56.a8703d58.js │ ├── chunk-2d21ed56.a8703d58.js.map │ ├── chunk-2d22d5d9.9d162235.js │ ├── chunk-2d22d5d9.9d162235.js.map │ ├── chunk-2d237b51.dbdaaf30.js │ ├── chunk-2d237b51.dbdaaf30.js.map │ ├── chunk-2d237d5d.abb1ebc2.js │ ├── chunk-2d237d5d.abb1ebc2.js.map │ ├── chunk-434cf637.3d43f7a9.js │ ├── chunk-434cf637.3d43f7a9.js.map │ ├── chunk-6056c388.46b43534.js │ ├── chunk-6056c388.46b43534.js.map │ ├── chunk-6065e614.bbfdefe4.js │ ├── chunk-6065e614.bbfdefe4.js.map │ ├── chunk-619b7fb8.51f5dd88.js │ ├── chunk-619b7fb8.51f5dd88.js.map │ ├── chunk-c5a431ce.18a9fbd2.js │ ├── chunk-c5a431ce.18a9fbd2.js.map │ ├── chunk-cfaaa91c.5809266b.js │ ├── chunk-cfaaa91c.5809266b.js.map │ ├── chunk-d4383190.47a87e77.js │ ├── chunk-d4383190.47a87e77.js.map │ ├── chunk-d466345a.dd688bc4.js │ ├── chunk-d466345a.dd688bc4.js.map │ ├── chunk-d4b8c67a.ef703ad0.js │ ├── chunk-d4b8c67a.ef703ad0.js.map │ ├── chunk-d4c7e4a2.eb8a2f2f.js │ ├── chunk-d4c7e4a2.eb8a2f2f.js.map │ ├── chunk-vendors.8ef7a405.js │ └── chunk-vendors.8ef7a405.js.map ├── client ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── README.md ├── babel.config.js ├── package.json ├── public │ ├── favicon-32x32.png │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── RedisLabsIllustration.svg │ ├── components │ │ ├── DataDisplay │ │ │ ├── TheAbandonedCart.vue │ │ │ ├── TheCohort.vue │ │ │ ├── TheCustomerRetention.vue │ │ │ ├── TheCustomersPerProduct.vue │ │ │ ├── TheCustomersWithBothProducts.vue │ │ │ ├── TheShareOfProductsBought.vue │ │ │ ├── TheTotalProductsBought.vue │ │ │ ├── TheTrafficPerPage.vue │ │ │ ├── TheTrafficPerSource.vue │ │ │ └── TheTrend.vue │ │ ├── PeriodSelectCard.vue │ │ ├── TheAnalyticsData.vue │ │ ├── TheAnalyticsDemoForm.vue │ │ └── UI │ │ │ ├── BaseCard.vue │ │ │ ├── BaseCollectionCard.vue │ │ │ ├── BasePeriodSelect.vue │ │ │ ├── Charts │ │ │ ├── BaseHorizontalBarChart.vue │ │ │ ├── BaseLineChart.vue │ │ │ └── BasePieChart.vue │ │ │ ├── TheFlushButton.vue │ │ │ └── TheResetButton.vue │ ├── config │ │ └── index.js │ ├── main.js │ ├── router │ │ └── index.js │ ├── store │ │ └── index.js │ ├── styles │ │ └── styles.scss │ └── views │ │ └── Home.vue └── vue.config.js ├── docs └── YTThumbnail.png ├── heroku.yml ├── images └── app_preview_image.png ├── marketplace.json ├── preview-2-min.png ├── preview-2.png ├── preview-3-min.png ├── preview-3.png ├── preview-min.png ├── preview.png ├── server ├── .env.example ├── .prettierrc.js ├── README.md ├── docker-compose.prod.yml ├── docker-compose.yml ├── package.json ├── pm2.json └── src │ ├── config │ └── index.js │ ├── controllers │ ├── Customers │ │ ├── CohortShowController.js │ │ ├── ProductsIndexController.js │ │ └── RetentionShowController.js │ ├── Data │ │ └── StoreController.js │ ├── FlushController.js │ ├── ResetController.js │ ├── Sales │ │ └── IndexController.js │ └── Traffic │ │ ├── IndexController.js │ │ └── TrendIndexController.js │ ├── di │ ├── controllers.js │ ├── index.js │ └── services.js │ ├── index.js │ ├── plugins │ └── errorHandler.js │ ├── routes │ ├── customers.js │ ├── data.js │ ├── flush.js │ ├── index.js │ ├── reset.js │ ├── sales.js │ └── traffic.js │ ├── sample.json │ ├── scripts │ └── sample.js │ └── services │ ├── PeriodService.js │ ├── data │ ├── SampleDataService.js │ └── StoreDataService.js │ ├── event │ ├── AnalyzerService.js │ ├── EventService.js │ ├── KeyGeneratorService.js │ ├── TimeSpanService.js │ └── types.js │ └── redis │ ├── RedisClientFactory.js │ └── RedisService.js └── vercel.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/node_modules 3 | **/.DS_Store 4 | **/package-lock.json 5 | **/.env 6 | **/test.js 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine as vue-build 2 | WORKDIR /app 3 | COPY ./client/package.json ./ 4 | RUN npm install 5 | COPY ./client/ . 6 | RUN npm run build 7 | 8 | FROM node:lts-alpine AS server-build 9 | WORKDIR /app/server 10 | COPY ./server/package.json ./ 11 | RUN npm install 12 | COPY ./server . 13 | COPY --from=vue-build /app/dist ./../client-dist 14 | EXPOSE ${PORT} 15 | CMD ["node", "./src/index.js"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Redis Developer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Redis Analytics Bitmaps", 3 | "description": "This app demonstrates how we can use Redis bitmap methods to gather analytic statistics. It utilizes such methods as SETBIT, BITCOUNT and BITOP.", 4 | "repository": "https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs.git", 5 | "keywords": [ 6 | "NodeJS", 7 | "ExpressJS", 8 | "Redis", 9 | "Analytics", 10 | "Statistics", 11 | "SETBIT", 12 | "BITCOUNT", 13 | "BITOP" 14 | ], 15 | "env": { 16 | "REDIS_ENDPOINT_URI": { 17 | "description": "Redis server URI", 18 | "required": true 19 | }, 20 | "REDIS_PASSWORD": { 21 | "description": "Redis password", 22 | "required": true 23 | } 24 | }, 25 | "stack": "container" 26 | } 27 | -------------------------------------------------------------------------------- /client-dist/css/app.577fb7dd.css: -------------------------------------------------------------------------------- 1 | body{background-image:url(../img/RedisLabsIllustration.bebd0eb3.svg);background-color:#f8f8fb;background-repeat:no-repeat;background-size:340px;background-position:100% 0}#app{background:none}#app *{word-break:normal!important}.subheader{max-width:800px;font-size:110%}@media screen and (min-width:1264px){.card{height:100%}} -------------------------------------------------------------------------------- /client-dist/css/chunk-6056c388.7f735cea.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-divider{border-color:rgba(0,0,0,.12)}.theme--dark.v-divider{border-color:hsla(0,0%,100%,.12)}.v-divider{display:block;flex:1 1 0px;max-width:100%;height:0;max-height:0;border:solid;border-width:thin 0 0 0;transition:inherit}.v-divider--inset:not(.v-divider--vertical){max-width:calc(100% - 72px)}.v-application--is-ltr .v-divider--inset:not(.v-divider--vertical){margin-left:72px}.v-application--is-rtl .v-divider--inset:not(.v-divider--vertical){margin-right:72px}.v-divider--vertical{align-self:stretch;border:solid;border-width:0 thin 0 0;display:inline-flex;height:inherit;min-height:100%;max-height:100%;max-width:0;width:0;vertical-align:text-bottom}.v-divider--vertical.v-divider--inset{margin-top:8px;min-height:0;max-height:calc(100% - 16px)}.v-item-group{flex:0 1 auto;position:relative;max-width:100%;transition:.3s cubic-bezier(.25,.8,.5,1)} -------------------------------------------------------------------------------- /client-dist/css/chunk-6065e614.7f735cea.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-divider{border-color:rgba(0,0,0,.12)}.theme--dark.v-divider{border-color:hsla(0,0%,100%,.12)}.v-divider{display:block;flex:1 1 0px;max-width:100%;height:0;max-height:0;border:solid;border-width:thin 0 0 0;transition:inherit}.v-divider--inset:not(.v-divider--vertical){max-width:calc(100% - 72px)}.v-application--is-ltr .v-divider--inset:not(.v-divider--vertical){margin-left:72px}.v-application--is-rtl .v-divider--inset:not(.v-divider--vertical){margin-right:72px}.v-divider--vertical{align-self:stretch;border:solid;border-width:0 thin 0 0;display:inline-flex;height:inherit;min-height:100%;max-height:100%;max-width:0;width:0;vertical-align:text-bottom}.v-divider--vertical.v-divider--inset{margin-top:8px;min-height:0;max-height:calc(100% - 16px)}.v-item-group{flex:0 1 auto;position:relative;max-width:100%;transition:.3s cubic-bezier(.25,.8,.5,1)} -------------------------------------------------------------------------------- /client-dist/css/chunk-cfaaa91c.95549bc3.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-data-table{background-color:#fff;color:rgba(0,0,0,.87)}.theme--light.v-data-table .v-data-table__divider{border-right:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table.v-data-table--fixed-header thead th{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:rgba(0,0,0,.6)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#f5f5f5}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#eee}.theme--dark.v-data-table{background-color:#1e1e1e;color:#fff}.theme--dark.v-data-table .v-data-table__divider{border-right:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table.v-data-table--fixed-header thead th{background:#1e1e1e;box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:hsla(0,0%,100%,.7)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#505050}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#616161}.v-data-table{line-height:1.5;max-width:100%}.v-data-table>.v-data-table__wrapper>table{width:100%;border-spacing:0}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{padding:0 16px;transition:height .2s cubic-bezier(.4,0,.6,1)}.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:.75rem;height:48px}.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:left}.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:right}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>td{font-size:.875rem;height:48px}.v-data-table__wrapper{overflow-x:auto;overflow-y:hidden}.v-data-table__progress{height:auto!important}.v-data-table__progress th{height:auto!important;border:none!important;padding:0;position:relative}.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>th{height:32px}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:first-child{border-top-left-radius:0}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:last-child{border-top-right-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:first-child{border-bottom-left-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:last-child{border-bottom-right-radius:0}.v-data-table--fixed-header>.v-data-table__wrapper,.v-data-table--fixed-height .v-data-table__wrapper{overflow-y:auto}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr>th{border-bottom:0!important;position:sticky;top:0;z-index:2}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:48px}.v-application--is-ltr .v-data-table--fixed-header .v-data-footer{margin-right:17px}.v-application--is-rtl .v-data-table--fixed-header .v-data-footer{margin-left:17px}.v-data-table--fixed-header.v-data-table--dense>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:32px} -------------------------------------------------------------------------------- /client-dist/css/chunk-d4383190.95549bc3.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-data-table{background-color:#fff;color:rgba(0,0,0,.87)}.theme--light.v-data-table .v-data-table__divider{border-right:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table.v-data-table--fixed-header thead th{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:rgba(0,0,0,.6)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#f5f5f5}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#eee}.theme--dark.v-data-table{background-color:#1e1e1e;color:#fff}.theme--dark.v-data-table .v-data-table__divider{border-right:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table.v-data-table--fixed-header thead th{background:#1e1e1e;box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:hsla(0,0%,100%,.7)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#505050}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#616161}.v-data-table{line-height:1.5;max-width:100%}.v-data-table>.v-data-table__wrapper>table{width:100%;border-spacing:0}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{padding:0 16px;transition:height .2s cubic-bezier(.4,0,.6,1)}.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:.75rem;height:48px}.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:left}.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:right}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>td{font-size:.875rem;height:48px}.v-data-table__wrapper{overflow-x:auto;overflow-y:hidden}.v-data-table__progress{height:auto!important}.v-data-table__progress th{height:auto!important;border:none!important;padding:0;position:relative}.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>th{height:32px}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:first-child{border-top-left-radius:0}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:last-child{border-top-right-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:first-child{border-bottom-left-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:last-child{border-bottom-right-radius:0}.v-data-table--fixed-header>.v-data-table__wrapper,.v-data-table--fixed-height .v-data-table__wrapper{overflow-y:auto}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr>th{border-bottom:0!important;position:sticky;top:0;z-index:2}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:48px}.v-application--is-ltr .v-data-table--fixed-header .v-data-footer{margin-right:17px}.v-application--is-rtl .v-data-table--fixed-header .v-data-footer{margin-left:17px}.v-data-table--fixed-header.v-data-table--dense>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:32px} -------------------------------------------------------------------------------- /client-dist/css/chunk-d466345a.95549bc3.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-data-table{background-color:#fff;color:rgba(0,0,0,.87)}.theme--light.v-data-table .v-data-table__divider{border-right:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table.v-data-table--fixed-header thead th{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:rgba(0,0,0,.6)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#f5f5f5}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#eee}.theme--dark.v-data-table{background-color:#1e1e1e;color:#fff}.theme--dark.v-data-table .v-data-table__divider{border-right:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table.v-data-table--fixed-header thead th{background:#1e1e1e;box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:hsla(0,0%,100%,.7)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#505050}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#616161}.v-data-table{line-height:1.5;max-width:100%}.v-data-table>.v-data-table__wrapper>table{width:100%;border-spacing:0}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{padding:0 16px;transition:height .2s cubic-bezier(.4,0,.6,1)}.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:.75rem;height:48px}.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:left}.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:right}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>td{font-size:.875rem;height:48px}.v-data-table__wrapper{overflow-x:auto;overflow-y:hidden}.v-data-table__progress{height:auto!important}.v-data-table__progress th{height:auto!important;border:none!important;padding:0;position:relative}.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>th{height:32px}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:first-child{border-top-left-radius:0}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:last-child{border-top-right-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:first-child{border-bottom-left-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:last-child{border-bottom-right-radius:0}.v-data-table--fixed-header>.v-data-table__wrapper,.v-data-table--fixed-height .v-data-table__wrapper{overflow-y:auto}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr>th{border-bottom:0!important;position:sticky;top:0;z-index:2}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:48px}.v-application--is-ltr .v-data-table--fixed-header .v-data-footer{margin-right:17px}.v-application--is-rtl .v-data-table--fixed-header .v-data-footer{margin-left:17px}.v-data-table--fixed-header.v-data-table--dense>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:32px} -------------------------------------------------------------------------------- /client-dist/css/chunk-d4b8c67a.95549bc3.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-data-table{background-color:#fff;color:rgba(0,0,0,.87)}.theme--light.v-data-table .v-data-table__divider{border-right:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table.v-data-table--fixed-header thead th{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:rgba(0,0,0,.6)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#f5f5f5}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#eee}.theme--dark.v-data-table{background-color:#1e1e1e;color:#fff}.theme--dark.v-data-table .v-data-table__divider{border-right:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table.v-data-table--fixed-header thead th{background:#1e1e1e;box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:hsla(0,0%,100%,.7)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#505050}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#616161}.v-data-table{line-height:1.5;max-width:100%}.v-data-table>.v-data-table__wrapper>table{width:100%;border-spacing:0}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{padding:0 16px;transition:height .2s cubic-bezier(.4,0,.6,1)}.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:.75rem;height:48px}.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:left}.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:right}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>td{font-size:.875rem;height:48px}.v-data-table__wrapper{overflow-x:auto;overflow-y:hidden}.v-data-table__progress{height:auto!important}.v-data-table__progress th{height:auto!important;border:none!important;padding:0;position:relative}.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>th{height:32px}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:first-child{border-top-left-radius:0}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:last-child{border-top-right-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:first-child{border-bottom-left-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:last-child{border-bottom-right-radius:0}.v-data-table--fixed-header>.v-data-table__wrapper,.v-data-table--fixed-height .v-data-table__wrapper{overflow-y:auto}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr>th{border-bottom:0!important;position:sticky;top:0;z-index:2}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:48px}.v-application--is-ltr .v-data-table--fixed-header .v-data-footer{margin-right:17px}.v-application--is-rtl .v-data-table--fixed-header .v-data-footer{margin-left:17px}.v-data-table--fixed-header.v-data-table--dense>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:32px} -------------------------------------------------------------------------------- /client-dist/css/chunk-d4c7e4a2.95549bc3.css: -------------------------------------------------------------------------------- 1 | .theme--light.v-data-table{background-color:#fff;color:rgba(0,0,0,.87)}.theme--light.v-data-table .v-data-table__divider{border-right:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table.v-data-table--fixed-header thead th{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:rgba(0,0,0,.6)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--light.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid rgba(0,0,0,.12)}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#f5f5f5}.theme--light.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#eee}.theme--dark.v-data-table{background-color:#1e1e1e;color:#fff}.theme--dark.v-data-table .v-data-table__divider{border-right:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table.v-data-table--fixed-header thead th{background:#1e1e1e;box-shadow:inset 0 -1px 0 hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr>th{color:hsla(0,0%,100%,.7)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>td:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:last-child,.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:not(:last-child)>th:not(.v-data-table__mobile-row),.theme--dark.v-data-table>.v-data-table__wrapper>table>thead>tr:last-child>th{border-bottom:thin solid hsla(0,0%,100%,.12)}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr.active{background:#505050}.theme--dark.v-data-table>.v-data-table__wrapper>table>tbody>tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper){background:#616161}.v-data-table{line-height:1.5;max-width:100%}.v-data-table>.v-data-table__wrapper>table{width:100%;border-spacing:0}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{padding:0 16px;transition:height .2s cubic-bezier(.4,0,.6,1)}.v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table>.v-data-table__wrapper>table>thead>tr>th{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:.75rem;height:48px}.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-ltr .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:left}.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tbody>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>tfoot>tr>th,.v-application--is-rtl .v-data-table>.v-data-table__wrapper>table>thead>tr>th{text-align:right}.v-data-table>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table>.v-data-table__wrapper>table>thead>tr>td{font-size:.875rem;height:48px}.v-data-table__wrapper{overflow-x:auto;overflow-y:hidden}.v-data-table__progress{height:auto!important}.v-data-table__progress th{height:auto!important;border:none!important;padding:0;position:relative}.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tbody>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>tfoot>tr>th,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>td,.v-data-table--dense>.v-data-table__wrapper>table>thead>tr>th{height:32px}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:first-child{border-top-left-radius:0}.v-data-table--has-top>.v-data-table__wrapper>table>tbody>tr:first-child:hover>td:last-child{border-top-right-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:first-child{border-bottom-left-radius:0}.v-data-table--has-bottom>.v-data-table__wrapper>table>tbody>tr:last-child:hover>td:last-child{border-bottom-right-radius:0}.v-data-table--fixed-header>.v-data-table__wrapper,.v-data-table--fixed-height .v-data-table__wrapper{overflow-y:auto}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr>th{border-bottom:0!important;position:sticky;top:0;z-index:2}.v-data-table--fixed-header>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:48px}.v-application--is-ltr .v-data-table--fixed-header .v-data-footer{margin-right:17px}.v-application--is-rtl .v-data-table--fixed-header .v-data-footer{margin-left:17px}.v-data-table--fixed-header.v-data-table--dense>.v-data-table__wrapper>table>thead>tr:nth-child(2)>th{top:32px} -------------------------------------------------------------------------------- /client-dist/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/client-dist/favicon-32x32.png -------------------------------------------------------------------------------- /client-dist/index.html: -------------------------------------------------------------------------------- 1 | Redis Analytics Bitmaps
-------------------------------------------------------------------------------- /client-dist/js/chunk-2d0a3cb4.0a490a8d.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0a3cb4"],{"0460":function(t,e,c){"use strict";c.r(e);var n=function(){var t=this,e=t.$createElement,c=t._self._c||e;return c("base-collection-card",{attrs:{title:"Customers who bought Product1 and Product2",subtitle:"(anytime)",data:t.customers,loading:t.loading}})},r=[],a=c("3835"),o=c("1da1"),u=c("5530"),s=(c("96cf"),c("d3b7"),c("3ca3"),c("ddb0"),c("2f62")),i={components:{baseCollectionCard:function(){return c.e("chunk-d4c7e4a2").then(c.bind(null,"4cc5"))}},data:function(){return{customers:[],loading:!1}},computed:Object(u["a"])({},Object(s["c"])({refreshSignal:"refreshSignal"})),watch:{refreshSignal:function(){this.fetchProductsData()}},created:function(){this.fetchProductsData()},methods:Object(u["a"])(Object(u["a"])({},Object(s["b"])({fetchProducts:"fetchProducts"})),{},{fetchProductsData:function(){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function e(){var c,n,r;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.loading=!0,e.next=3,t.fetchProducts({filter:{products:[]},and:JSON.stringify(["product1","product2"]),period:"anytime"});case 3:c=e.sent,n=Object(a["a"])(c,1),r=n[0],t.customers=r.boughtBy,t.loading=!1;case 8:case"end":return e.stop()}}),e)})))()}})},d=i,l=c("2877"),f=Object(l["a"])(d,n,r,!1,null,null,null);e["default"]=f.exports}}]); 2 | //# sourceMappingURL=chunk-2d0a3cb4.0a490a8d.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0a3cb4.0a490a8d.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/DataDisplay/TheCustomersWithBothProducts.vue?d9ce","webpack:///src/components/DataDisplay/TheCustomersWithBothProducts.vue","webpack:///./src/components/DataDisplay/TheCustomersWithBothProducts.vue?8593","webpack:///./src/components/DataDisplay/TheCustomersWithBothProducts.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","attrs","customers","loading","staticRenderFns","component"],"mappings":"yHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,uBAAuB,CAACE,MAAM,CAAC,MAAQ,6CAA6C,SAAW,YAAY,KAAON,EAAIO,UAAU,QAAUP,EAAIQ,YAC7OC,EAAkB,G,0FCWtB,GACE,WAAF,CACI,mBAAJ,WAAM,OAAN,kDAGE,KALF,WAMI,MAAJ,CACM,UAAN,GACM,SAAN,IAIE,SAAF,kBACA,gBAAI,cAAJ,mBAGE,MAAF,CACI,cADJ,WAEM,KAAN,sBAIE,QAtBF,WAuBI,KAAJ,qBAGE,QAAF,iCACA,gBAAI,cAAJ,mBADA,IAGI,kBAHJ,WAGM,IAAN,OAAM,OAAN,qDAAQ,IAAR,MAAQ,OAAR,iFACA,aADA,SAGA,iBACA,qBACA,4CACA,mBANA,sCAGA,EAHA,KASA,uBACA,aAVA,iDCzCqX,I,YCOjXC,EAAY,eACd,EACAX,EACAU,GACA,EACA,KACA,KACA,MAIa,aAAAC,E","file":"js/chunk-2d0a3cb4.0a490a8d.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('base-collection-card',{attrs:{\"title\":\"Customers who bought Product1 and Product2\",\"subtitle\":\"(anytime)\",\"data\":_vm.customers,\"loading\":_vm.loading}})}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheCustomersWithBothProducts.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheCustomersWithBothProducts.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheCustomersWithBothProducts.vue?vue&type=template&id=61516af4&\"\nimport script from \"./TheCustomersWithBothProducts.vue?vue&type=script&lang=js&\"\nexport * from \"./TheCustomersWithBothProducts.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0a443a.5a3bc57b.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0a443a"],{"0664":function(t,a,e){"use strict";e.r(a);var r=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("v-card",{staticClass:"card",attrs:{loading:t.loading}},[e("v-card-title",{staticClass:"px-4"},[t._v("Abandoned Cart")]),e("v-card-subtitle",[t._v("Products: in cart vs bought")]),e("v-card-text",{staticClass:"px-10"},[e("base-pie-chart",{attrs:{"chart-data":t.chartData}})],1)],1)},c=[],d=e("3835"),n=e("1da1"),o=e("5530"),s=(e("96cf"),e("d3b7"),e("3ca3"),e("ddb0"),e("2f62")),i={components:{basePieChart:function(){return Promise.all([e.e("chunk-434cf637"),e.e("chunk-2d21ed56")]).then(e.bind(null,"d6f4"))}},data:function(){return{productBought:0,productsAddedToCart:0,loading:!1}},computed:Object(o["a"])(Object(o["a"])({},Object(s["c"])({refreshSignal:"refreshSignal",period:"getPeriod"})),{},{chartData:function(){return{labels:["Added to Cart","Bought"],datasets:[{data:[this.productsAddedToCart,this.productBought],backgroundColor:["rgba(75, 192, 192, 1)","rgba(54, 162, 235, 1)"],borderWidth:0}]}}}),created:function(){this.fetchSalesData(this.period)},watch:{refreshSignal:function(){this.fetchSalesData(this.period)},period:function(t){this.fetchSalesData(t)}},methods:Object(o["a"])(Object(o["a"])({},Object(s["b"])({fetchSales:"fetchSales"})),{},{fetchSalesData:function(t){var a=this;return Object(n["a"])(regeneratorRuntime.mark((function e(){var r,c,n;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return a.loading=!0,e.next=3,a.fetchSales({period:t,filter:{total:!0}});case 3:r=e.sent,c=Object(d["a"])(r,1),n=c[0],a.productBought=n.bought,a.productsAddedToCart=n.addedToCart,a.loading=!1;case 9:case"end":return e.stop()}}),e)})))()}})},u=i,l=e("2877"),h=e("6544"),b=e.n(h),f=e("b0af"),p=e("99d9"),g=Object(l["a"])(u,r,c,!1,null,null,null);a["default"]=g.exports;b()(g,{VCard:f["a"],VCardSubtitle:p["b"],VCardText:p["c"],VCardTitle:p["d"]})}}]); 2 | //# sourceMappingURL=chunk-2d0a443a.5a3bc57b.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0a443a.5a3bc57b.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/DataDisplay/TheAbandonedCart.vue?3180","webpack:///src/components/DataDisplay/TheAbandonedCart.vue","webpack:///./src/components/DataDisplay/TheAbandonedCart.vue?6e4d","webpack:///./src/components/DataDisplay/TheAbandonedCart.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","staticClass","attrs","loading","_v","chartData","staticRenderFns","component","VCard","VCardSubtitle","VCardText","VCardTitle"],"mappings":"yHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,SAAS,CAACE,YAAY,OAAOC,MAAM,CAAC,QAAUP,EAAIQ,UAAU,CAACJ,EAAG,eAAe,CAACE,YAAY,QAAQ,CAACN,EAAIS,GAAG,oBAAoBL,EAAG,kBAAkB,CAACJ,EAAIS,GAAG,iCAAiCL,EAAG,cAAc,CAACE,YAAY,SAAS,CAACF,EAAG,iBAAiB,CAACG,MAAM,CAAC,aAAaP,EAAIU,cAAc,IAAI,IACpYC,EAAkB,G,0FCatB,GACE,WAAF,CACI,aAAJ,WAAM,OAAN,uFAGE,KALF,WAMI,MAAJ,CACM,cAAN,EACM,oBAAN,EACM,SAAN,IAIE,SAAF,iCACA,gBACI,cAAJ,gBACI,OAAJ,eAHA,IAMI,UANJ,WAOM,MAAN,CACQ,OAAR,2BACQ,SAAR,CACA,CACU,KAAV,8CACU,gBAAV,kDACU,YAAV,QAOE,QAjCF,WAkCI,KAAJ,6BAGE,MAAF,CACI,cADJ,WAEM,KAAN,6BAEI,OAJJ,SAIA,GACM,KAAN,oBAIE,QAAF,iCACA,gBAAI,WAAJ,gBADA,IAGI,eAHJ,SAGA,GAAM,IAAN,OAAM,OAAN,qDAAQ,IAAR,MAAQ,OAAR,iFACA,aADA,SAGA,2CAHA,sCAGA,EAHA,KAKA,yBACA,oCACA,aAPA,iDC/DyW,I,yDCOrWC,EAAY,eACd,EACAb,EACAY,GACA,EACA,KACA,KACA,MAIa,aAAAC,EAAiB,QAQhC,IAAkBA,EAAW,CAACC,QAAA,KAAMC,cAAA,OAAcC,UAAA,OAAUC,WAAA","file":"js/chunk-2d0a443a.5a3bc57b.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-card',{staticClass:\"card\",attrs:{\"loading\":_vm.loading}},[_c('v-card-title',{staticClass:\"px-4\"},[_vm._v(\"Abandoned Cart\")]),_c('v-card-subtitle',[_vm._v(\"Products: in cart vs bought\")]),_c('v-card-text',{staticClass:\"px-10\"},[_c('base-pie-chart',{attrs:{\"chart-data\":_vm.chartData}})],1)],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheAbandonedCart.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheAbandonedCart.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheAbandonedCart.vue?vue&type=template&id=5bf71caf&\"\nimport script from \"./TheAbandonedCart.vue?vue&type=script&lang=js&\"\nexport * from \"./TheAbandonedCart.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VCard } from 'vuetify/lib/components/VCard';\nimport { VCardSubtitle } from 'vuetify/lib/components/VCard';\nimport { VCardText } from 'vuetify/lib/components/VCard';\nimport { VCardTitle } from 'vuetify/lib/components/VCard';\ninstallComponents(component, {VCard,VCardSubtitle,VCardText,VCardTitle})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0b8ef3.7b3b9b84.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0b8ef3"],{3198:function(t,e,r){"use strict";r.r(e);var a=function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("v-card",{staticClass:"card",attrs:{loading:t.loading}},[r("v-card-title",{staticClass:"px-4"},[t._v("Share of Products bought")]),r("v-card-text",{staticClass:"px-10"},[r("base-pie-chart",{attrs:{"chart-data":t.chartData}})],1)],1)},c=[],n=r("1da1"),o=r("5530"),u=(r("96cf"),r("d3b7"),r("3ca3"),r("ddb0"),r("7db0"),r("2f62")),d={components:{basePieChart:function(){return Promise.all([r.e("chunk-434cf637"),r.e("chunk-2d21ed56")]).then(r.bind(null,"d6f4"))}},data:function(){return{product1Bought:0,product2Bought:0,product3Bought:0,loading:!1}},computed:Object(o["a"])(Object(o["a"])({},Object(u["c"])({refreshSignal:"refreshSignal",period:"getPeriod"})),{},{chartData:function(){return{labels:["Product1","Product2","Product3"],datasets:[{data:[this.product1Bought,this.product2Bought,this.product3Bought],backgroundColor:["rgba(75, 192, 192, 1)","rgba(54, 162, 235, 1)","rgba(200, 250, 5, 90)"],borderWidth:0}]}}}),created:function(){this.fetchSalesData(this.period)},watch:{refreshSignal:function(){this.fetchSalesData(this.period)},period:function(t){this.fetchSalesData(t)}},methods:Object(o["a"])(Object(o["a"])({},Object(u["b"])({fetchSales:"fetchSales"})),{},{fetchSalesData:function(t){var e=this;return Object(n["a"])(regeneratorRuntime.mark((function r(){var a;return regeneratorRuntime.wrap((function(r){while(1)switch(r.prev=r.next){case 0:return e.loading=!0,r.next=3,e.fetchSales({period:t,filter:{products:["product1","product2","product3"]}});case 3:a=r.sent,e.product1Bought=a.find((function(t){return"product1"===t.value})).bought,e.product2Bought=a.find((function(t){return"product2"===t.value})).bought,e.product3Bought=a.find((function(t){return"product3"===t.value})).bought,e.loading=!1;case 8:case"end":return r.stop()}}),r)})))()}})},i=d,s=r("2877"),h=r("6544"),l=r.n(h),f=r("b0af"),p=r("99d9"),b=Object(s["a"])(i,a,c,!1,null,null,null);e["default"]=b.exports;l()(b,{VCard:f["a"],VCardText:p["c"],VCardTitle:p["d"]})}}]); 2 | //# sourceMappingURL=chunk-2d0b8ef3.7b3b9b84.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0b8ef3.7b3b9b84.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/DataDisplay/TheShareOfProductsBought.vue?c252","webpack:///src/components/DataDisplay/TheShareOfProductsBought.vue","webpack:///./src/components/DataDisplay/TheShareOfProductsBought.vue?9df6","webpack:///./src/components/DataDisplay/TheShareOfProductsBought.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","staticClass","attrs","loading","_v","chartData","staticRenderFns","component","VCard","VCardText","VCardTitle"],"mappings":"uHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,SAAS,CAACE,YAAY,OAAOC,MAAM,CAAC,QAAUP,EAAIQ,UAAU,CAACJ,EAAG,eAAe,CAACE,YAAY,QAAQ,CAACN,EAAIS,GAAG,8BAA8BL,EAAG,cAAc,CAACE,YAAY,SAAS,CAACF,EAAG,iBAAiB,CAACG,MAAM,CAAC,aAAaP,EAAIU,cAAc,IAAI,IAChVC,EAAkB,G,wFCYtB,GACE,WAAF,CACI,aAAJ,WAAM,OAAN,uFAGE,KALF,WAMI,MAAJ,CACM,eAAN,EACM,eAAN,EACM,eAAN,EACM,SAAN,IAIE,SAAF,iCACA,gBACI,cAAJ,gBACI,OAAJ,eAHA,IAMI,UANJ,WAOM,MAAN,CACQ,OAAR,mCACQ,SAAR,CACA,CACU,KAAV,8DACU,gBAAV,0EACU,YAAV,QAOE,QAlCF,WAmCI,KAAJ,6BAGE,MAAF,CACI,cADJ,WAEM,KAAN,6BAEI,OAJJ,SAIA,GACM,KAAN,oBAIE,QAAF,iCACA,gBAAI,WAAJ,gBADA,IAGI,eAHJ,SAGA,GAAM,IAAN,OAAM,OAAN,qDAAQ,IAAR,EAAQ,OAAR,iFACA,aADA,SAGA,8EAHA,OAGA,EAHA,OAKA,0EACA,0EACA,0EACA,aARA,iDC/DiX,I,yDCO7WC,EAAY,eACd,EACAb,EACAY,GACA,EACA,KACA,KACA,MAIa,aAAAC,EAAiB,QAOhC,IAAkBA,EAAW,CAACC,QAAA,KAAMC,UAAA,OAAUC,WAAA","file":"js/chunk-2d0b8ef3.7b3b9b84.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-card',{staticClass:\"card\",attrs:{\"loading\":_vm.loading}},[_c('v-card-title',{staticClass:\"px-4\"},[_vm._v(\"Share of Products bought\")]),_c('v-card-text',{staticClass:\"px-10\"},[_c('base-pie-chart',{attrs:{\"chart-data\":_vm.chartData}})],1)],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheShareOfProductsBought.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheShareOfProductsBought.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheShareOfProductsBought.vue?vue&type=template&id=77e72cff&\"\nimport script from \"./TheShareOfProductsBought.vue?vue&type=script&lang=js&\"\nexport * from \"./TheShareOfProductsBought.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VCard } from 'vuetify/lib/components/VCard';\nimport { VCardText } from 'vuetify/lib/components/VCard';\nimport { VCardTitle } from 'vuetify/lib/components/VCard';\ninstallComponents(component, {VCard,VCardText,VCardTitle})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0e9995.bf3e2570.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e9995"],{"8de8":function(e,n,t){"use strict";t.r(n);var a,s,c=t("1fca"),i=c["d"].reactiveProp,r={mixins:[c["a"],i],mounted:function(){this.renderChart(this.chartData,{scales:{xAxes:[{ticks:{beginAtZero:!0}}]}})}},o=r,u=t("2877"),d=Object(u["a"])(o,a,s,!1,null,null,null);n["default"]=d.exports}}]); 2 | //# sourceMappingURL=chunk-2d0e9995.bf3e2570.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0e9995.bf3e2570.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/UI/Charts/BaseHorizontalBarChart.vue","webpack:///src/components/UI/Charts/BaseHorizontalBarChart.vue","webpack:///./src/components/UI/Charts/BaseHorizontalBarChart.vue?97fd"],"names":["render","staticRenderFns","component"],"mappings":"6HAAIA,EAAQC,E,YCEZ,sBAEA,GACE,OAAF,WAEE,QAHF,WAII,KAAJ,4BACM,OAAN,CACQ,MAAR,CACA,CACU,MAAV,CACY,aAAZ,UCb8X,I,YFO1XC,EAAY,eACd,EACAF,EACAC,GACA,EACA,KACA,KACA,MAIa,aAAAC,E","file":"js/chunk-2d0e9995.bf3e2570.js","sourcesContent":["var render, staticRenderFns\nimport script from \"./BaseHorizontalBarChart.vue?vue&type=script&lang=js&\"\nexport * from \"./BaseHorizontalBarChart.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n","import mod from \"-!../../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../../node_modules/thread-loader/dist/cjs.js!../../../../node_modules/babel-loader/lib/index.js!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./BaseHorizontalBarChart.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../../node_modules/thread-loader/dist/cjs.js!../../../../node_modules/babel-loader/lib/index.js!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./BaseHorizontalBarChart.vue?vue&type=script&lang=js&\""],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0f089f.b19c1b9a.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f089f"],{"9d97":function(t,e,c){"use strict";c.r(e);var r=function(){var t=this,e=t.$createElement,c=t._self._c||e;return c("base-collection-card",{attrs:{title:"Customers who bought only product",subtitle:"(anytime)",data:t.customers,loading:t.loading}},[c("v-select",{attrs:{items:t.values,"item-text":"text","item-value":"value",label:"Product",outlined:""},model:{value:t.product,callback:function(e){t.product=e},expression:"product"}})],1)},u=[],n=c("3835"),a=c("1da1"),o=c("5530"),s=(c("96cf"),c("d3b7"),c("3ca3"),c("ddb0"),c("2f62")),d={components:{baseCollectionCard:function(){return c.e("chunk-d4c7e4a2").then(c.bind(null,"4cc5"))}},data:function(){return{customers:[],product:"product1",values:[{text:"Product1",value:"product1"},{text:"Product2",value:"product2"},{text:"Product3",value:"product3"}],loading:!1}},computed:Object(o["a"])({},Object(s["c"])({refreshSignal:"refreshSignal"})),watch:{product:function(){this.fetchProductsData()},refreshSignal:function(){this.fetchProductsData()}},created:function(){this.fetchProductsData()},methods:Object(o["a"])(Object(o["a"])({},Object(s["b"])({fetchProducts:"fetchProducts"})),{},{fetchProductsData:function(){var t=this;return Object(a["a"])(regeneratorRuntime.mark((function e(){var c,r,u;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.loading=!0,e.next=3,t.fetchProducts({filter:{products:[t.product]},period:"anytime"});case 3:c=e.sent,r=Object(n["a"])(c,1),u=r[0],t.customers=u.boughtBy,t.loading=!1;case 8:case"end":return e.stop()}}),e)})))()}})},i=d,l=c("2877"),f=c("6544"),h=c.n(f),p=c("b974"),b=Object(l["a"])(i,r,u,!1,null,null,null);e["default"]=b.exports;h()(b,{VSelect:p["a"]})}}]); 2 | //# sourceMappingURL=chunk-2d0f089f.b19c1b9a.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d0f089f.b19c1b9a.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/DataDisplay/TheCustomersPerProduct.vue?1f10","webpack:///src/components/DataDisplay/TheCustomersPerProduct.vue","webpack:///./src/components/DataDisplay/TheCustomersPerProduct.vue?8ab1","webpack:///./src/components/DataDisplay/TheCustomersPerProduct.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","attrs","customers","loading","values","model","value","callback","$$v","product","expression","staticRenderFns","component","VSelect"],"mappings":"yHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,uBAAuB,CAACE,MAAM,CAAC,MAAQ,oCAAoC,SAAW,YAAY,KAAON,EAAIO,UAAU,QAAUP,EAAIQ,UAAU,CAACJ,EAAG,WAAW,CAACE,MAAM,CAAC,MAAQN,EAAIS,OAAO,YAAY,OAAO,aAAa,QAAQ,MAAQ,UAAU,SAAW,IAAIC,MAAM,CAACC,MAAOX,EAAW,QAAEY,SAAS,SAAUC,GAAMb,EAAIc,QAAQD,GAAKE,WAAW,cAAc,IAC/bC,EAAkB,G,0FCatB,GACE,WAAF,CACI,mBAAJ,WAAM,OAAN,kDAGE,KALF,WAMI,MAAJ,CACM,UAAN,GACM,QAAN,WACM,OAAN,CACA,CAAQ,KAAR,WAAQ,MAAR,YACA,CAAQ,KAAR,WAAQ,MAAR,YACA,CAAQ,KAAR,WAAQ,MAAR,aAEM,SAAN,IAIE,SAAF,kBACA,gBAAI,cAAJ,mBAGE,MAAF,CACI,QADJ,WAEM,KAAN,qBAGI,cALJ,WAMM,KAAN,sBAIE,QAhCF,WAiCI,KAAJ,qBAGE,QAAF,iCACA,gBAAI,cAAJ,mBADA,IAGI,kBAHJ,WAGM,IAAN,OAAM,OAAN,qDAAQ,IAAR,MAAQ,OAAR,iFACA,aADA,SAGA,kEAHA,sCAGA,EAHA,KAKA,uBACA,aANA,iDCrD+W,I,6CCO3WC,EAAY,eACd,EACAlB,EACAiB,GACA,EACA,KACA,KACA,MAIa,aAAAC,EAAiB,QAKhC,IAAkBA,EAAW,CAACC,UAAA","file":"js/chunk-2d0f089f.b19c1b9a.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('base-collection-card',{attrs:{\"title\":\"Customers who bought only product\",\"subtitle\":\"(anytime)\",\"data\":_vm.customers,\"loading\":_vm.loading}},[_c('v-select',{attrs:{\"items\":_vm.values,\"item-text\":\"text\",\"item-value\":\"value\",\"label\":\"Product\",\"outlined\":\"\"},model:{value:(_vm.product),callback:function ($$v) {_vm.product=$$v},expression:\"product\"}})],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheCustomersPerProduct.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheCustomersPerProduct.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheCustomersPerProduct.vue?vue&type=template&id=123f3d4e&\"\nimport script from \"./TheCustomersPerProduct.vue?vue&type=script&lang=js&\"\nexport * from \"./TheCustomersPerProduct.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VSelect } from 'vuetify/lib/components/VSelect';\ninstallComponents(component, {VSelect})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d208337.3659041c.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d208337"],{a47e:function(t,a,e){"use strict";e.r(a);var r=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("v-card",{staticClass:"card",attrs:{loading:t.loading}},[e("v-card-title",{staticClass:"px-4"},[t._v("Trend chart (pages)")]),e("v-card-text",{staticClass:"px-4"},[e("base-line-chart",{staticClass:"px-8",attrs:{"chart-data":t.chartData}})],1)],1)},n=[],c=e("1da1"),o=e("5530"),s=(e("96cf"),e("d3b7"),e("3ca3"),e("ddb0"),e("159b"),e("d81d"),e("4de4"),e("2f62")),u={components:{baseLineChart:function(){return Promise.all([e.e("chunk-434cf637"),e.e("chunk-2d237d5d")]).then(e.bind(null,"fd8a"))}},data:function(){return{loading:!1,datasets:[],labels:[]}},computed:Object(o["a"])(Object(o["a"])({},Object(s["c"])({refreshSignal:"refreshSignal",period:"getPeriod"})),{},{chartData:function(){var t=["rgba(0, 0, 0, 0)","rgba(0, 0, 0, 0)","rgba(0, 0, 0, 0)","rgba(0, 0, 0, 0)","rgba(0, 0, 0, 0)"],a=function(t){for(var a=[],e=0;e<31;e++)a.push(t);return a},e=[a("rgba(255, 99, 132, 1)"),a("rgba(54, 162, 235, 1)"),a("rgba(255, 206, 86, 1)"),a("rgba(75, 192, 192, 1)")],r=1,n=["Homepage","Product1 Page","Product2 Page","Product3 Page"],c={labels:this.labels,datasets:[]};return this.datasets.forEach((function(a,o){c.datasets.push({backgroundColor:t,borderWidth:r,data:a,borderColor:e[o],label:n[o]})})),c}}),created:function(){this.fetchTrafficData(this.period)},watch:{refreshSignal:function(){this.fetchTrafficData(this.period)},period:function(t){this.fetchTrafficData(t)}},methods:Object(o["a"])(Object(o["a"])({},Object(s["b"])({fetchTrend:"fetchTrend"})),{},{fetchTrafficData:function(t){var a=this;return Object(c["a"])(regeneratorRuntime.mark((function e(){var r,n,c,o,s,u,i,d,l;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return r={"2015-12/1":{from:"2015-12-01",to:"2015-12-07",labels:["2015-12-01","2015-12-02","2015-12-03","2015-12-04","2015-12-05","2015-12-06","2015-12-07"]},"2015-12/2":{from:"2015-12-08",to:"2015-12-14",labels:["2015-12-08","2015-12-09","2015-12-10","2015-12-11","2015-12-12","2015-12-13","2015-12-14"]},"2015-12/3":{from:"2015-12-15",to:"2015-12-21",labels:["2015-12-15","2015-12-16","2015-12-17","2015-12-18","2015-12-19","2015-12-20","2015-12-21"]},"2015-12/4":{from:"2015-12-22",to:"2015-12-28",labels:["2015-12-22","2015-12-23","2015-12-24","2015-12-25","2015-12-26","2015-12-27","2015-12-28"]},"2015-12/5":{from:"2015-12-29",to:"2015-12-31",labels:["2015-12-29","2015-12-30","2015-12-31"]}},n=r[t]||{},c=n.from,o=n.to,a.loading=!0,e.next=5,a.fetchTrend({filter:{pages:["homepage","product1","product2","product3"]},period:{from:c,to:o}});case 5:s=e.sent,a.datasets=[],a.labels=r[t]?r[t].labels:["2015-12-01","2015-12-02","2015-12-03","2015-12-04","2015-12-05","2015-12-06","2015-12-07","2015-12-08","2015-12-09","2015-12-10","2015-12-11","2015-12-12","2015-12-13","2015-12-14","2015-12-15","2015-12-16","2015-12-17","2015-12-18","2015-12-19","2015-12-20","2015-12-21","2015-12-22","2015-12-23","2015-12-24","2015-12-25","2015-12-26","2015-12-27","2015-12-28","2015-12-29","2015-12-30","2015-12-31"],u=s.filter((function(t){return"homepage"===t.value})).map((function(t){return t.count})),i=s.filter((function(t){return"product1"===t.value})).map((function(t){return t.count})),d=s.filter((function(t){return"product2"===t.value})).map((function(t){return t.count})),l=s.filter((function(t){return"product3"===t.value})).map((function(t){return t.count})),a.datasets.push(u,i,d,l),a.loading=!1;case 14:case"end":return e.stop()}}),e)})))()}})},i=u,d=e("2877"),l=e("6544"),f=e.n(l),b=e("b0af"),h=e("99d9"),p=Object(d["a"])(i,r,n,!1,null,null,null);a["default"]=p.exports;f()(p,{VCard:b["a"],VCardText:h["c"],VCardTitle:h["d"]})}}]); 2 | //# sourceMappingURL=chunk-2d208337.3659041c.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d208a1d.b5b3892b.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d208a1d"],{a62d:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("base-collection-card",{attrs:{title:"Customer Retention",subtitle:"Customers who bought on the different dates (anytime)",data:t.customers,loading:t.loading}})},c=[],r=n("1da1"),o=n("5530"),i=(n("96cf"),n("d3b7"),n("3ca3"),n("ddb0"),n("2f62")),s={components:{baseCollectionCard:function(){return n.e("chunk-d4c7e4a2").then(n.bind(null,"4cc5"))}},data:function(){return{customers:[],loading:!0}},computed:Object(o["a"])({},Object(i["c"])({refreshSignal:"refreshSignal"})),watch:{refreshSignal:function(){this.fetchProductsData()}},created:function(){this.fetchProductsData()},methods:Object(o["a"])(Object(o["a"])({},Object(i["b"])({fetchRetention:"fetchRetention"})),{},{fetchProductsData:function(){var t=this;return Object(r["a"])(regeneratorRuntime.mark((function e(){var n;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.loading=!0,e.next=3,t.fetchRetention({period:"anytime"});case 3:n=e.sent,t.customers=n,t.loading=!1;case 6:case"end":return e.stop()}}),e)})))()}})},u=s,d=n("2877"),l=Object(d["a"])(u,a,c,!1,null,null,null);e["default"]=l.exports}}]); 2 | //# sourceMappingURL=chunk-2d208a1d.b5b3892b.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d208a1d.b5b3892b.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/DataDisplay/TheCustomerRetention.vue?a635","webpack:///src/components/DataDisplay/TheCustomerRetention.vue","webpack:///./src/components/DataDisplay/TheCustomerRetention.vue?643e","webpack:///./src/components/DataDisplay/TheCustomerRetention.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","attrs","customers","loading","staticRenderFns","component"],"mappings":"uHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,uBAAuB,CAACE,MAAM,CAAC,MAAQ,qBAAqB,SAAW,wDAAwD,KAAON,EAAIO,UAAU,QAAUP,EAAIQ,YACjQC,EAAkB,G,8ECWtB,GACE,WAAF,CACI,mBAAJ,WAAM,OAAN,kDAGE,KALF,WAMI,MAAJ,CACM,UAAN,GACM,SAAN,IAIE,SAAF,kBACA,gBAAI,cAAJ,mBAGE,MAAF,CACI,cADJ,WAEM,KAAN,sBAIE,QAtBF,WAuBI,KAAJ,qBAGE,QAAF,iCACA,gBAAI,eAAJ,oBADA,IAGI,kBAHJ,WAGM,IAAN,OAAM,OAAN,qDAAQ,IAAR,EAAQ,OAAR,iFACA,aADA,SAGA,qCAHA,OAGA,EAHA,OAKA,cACA,aANA,iDCzC6W,I,YCOzWC,EAAY,eACd,EACAX,EACAU,GACA,EACA,KACA,KACA,MAIa,aAAAC,E","file":"js/chunk-2d208a1d.b5b3892b.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('base-collection-card',{attrs:{\"title\":\"Customer Retention\",\"subtitle\":\"Customers who bought on the different dates (anytime)\",\"data\":_vm.customers,\"loading\":_vm.loading}})}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheCustomerRetention.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheCustomerRetention.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheCustomerRetention.vue?vue&type=template&id=2701e8df&\"\nimport script from \"./TheCustomerRetention.vue?vue&type=script&lang=js&\"\nexport * from \"./TheCustomerRetention.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d2137a6.fb94b285.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d2137a6"],{ad9f:function(e,t,a){"use strict";a.r(t);var n=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("v-card",{staticClass:"mb-6"},[a("v-card-title",{staticClass:"px-5"},[e._v("Select date period")]),a("v-card-actions",{staticClass:"px-5"},[a("base-period-select",{on:{onSelect:e.updatePeriod}})],1)],1)},c=[],d=a("5530"),s=(a("d3b7"),a("3ca3"),a("ddb0"),a("2f62")),r={components:{basePeriodSelect:function(){return Promise.all([a.e("chunk-0b705865"),a.e("chunk-6065e614")]).then(a.bind(null,"a8a9"))}},methods:Object(d["a"])({},Object(s["b"])({updatePeriod:"updatePeriod"}))},i=r,l=a("2877"),o=a("6544"),u=a.n(o),b=a("b0af"),p=a("99d9"),f=Object(l["a"])(i,n,c,!1,null,null,null);t["default"]=f.exports;u()(f,{VCard:b["a"],VCardActions:p["a"],VCardTitle:p["d"]})}}]); 2 | //# sourceMappingURL=chunk-2d2137a6.fb94b285.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d2137a6.fb94b285.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/PeriodSelectCard.vue?a855","webpack:///src/components/PeriodSelectCard.vue","webpack:///./src/components/PeriodSelectCard.vue?773f","webpack:///./src/components/PeriodSelectCard.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","staticClass","_v","on","updatePeriod","staticRenderFns","component","VCard","VCardActions","VCardTitle"],"mappings":"uHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,SAAS,CAACE,YAAY,QAAQ,CAACF,EAAG,eAAe,CAACE,YAAY,QAAQ,CAACN,EAAIO,GAAG,wBAAwBH,EAAG,iBAAiB,CAACE,YAAY,QAAQ,CAACF,EAAG,qBAAqB,CAACI,GAAG,CAAC,SAAWR,EAAIS,iBAAiB,IAAI,IAChTC,EAAkB,G,wDCYtB,GACE,WAAF,CACI,iBAAJ,WAAM,OAAN,uFAEE,QAAF,kBACA,gBAAI,aAAJ,mBClB0V,I,yDCOtVC,EAAY,eACd,EACAZ,EACAW,GACA,EACA,KACA,KACA,MAIa,aAAAC,EAAiB,QAOhC,IAAkBA,EAAW,CAACC,QAAA,KAAMC,aAAA,OAAaC,WAAA","file":"js/chunk-2d2137a6.fb94b285.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-card',{staticClass:\"mb-6\"},[_c('v-card-title',{staticClass:\"px-5\"},[_vm._v(\"Select date period\")]),_c('v-card-actions',{staticClass:\"px-5\"},[_c('base-period-select',{on:{\"onSelect\":_vm.updatePeriod}})],1)],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./PeriodSelectCard.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./PeriodSelectCard.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./PeriodSelectCard.vue?vue&type=template&id=3b283ec7&\"\nimport script from \"./PeriodSelectCard.vue?vue&type=script&lang=js&\"\nexport * from \"./PeriodSelectCard.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VCard } from 'vuetify/lib/components/VCard';\nimport { VCardActions } from 'vuetify/lib/components/VCard';\nimport { VCardTitle } from 'vuetify/lib/components/VCard';\ninstallComponents(component, {VCard,VCardActions,VCardTitle})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d21ed56.a8703d58.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d21ed56"],{d6f4:function(n,t,e){"use strict";e.r(t);var a,c,i=e("1fca"),r=i["d"].reactiveProp,u={mixins:[i["c"],r],mounted:function(){this.renderChart(this.chartData)}},d=u,o=e("2877"),s=Object(o["a"])(d,a,c,!1,null,null,null);t["default"]=s.exports}}]); 2 | //# sourceMappingURL=chunk-2d21ed56.a8703d58.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d21ed56.a8703d58.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/UI/Charts/BasePieChart.vue","webpack:///src/components/UI/Charts/BasePieChart.vue","webpack:///./src/components/UI/Charts/BasePieChart.vue?c15c"],"names":["render","staticRenderFns","component"],"mappings":"2HAAIA,EAAQC,E,YCEZ,sBAEA,GACE,OAAF,WAEE,QAHF,WAII,KAAJ,8BCRoX,I,YFOhXC,EAAY,eACd,EACAF,EACAC,GACA,EACA,KACA,KACA,MAIa,aAAAC,E","file":"js/chunk-2d21ed56.a8703d58.js","sourcesContent":["var render, staticRenderFns\nimport script from \"./BasePieChart.vue?vue&type=script&lang=js&\"\nexport * from \"./BasePieChart.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","import mod from \"-!../../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../../node_modules/thread-loader/dist/cjs.js!../../../../node_modules/babel-loader/lib/index.js!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./BasePieChart.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../../node_modules/thread-loader/dist/cjs.js!../../../../node_modules/babel-loader/lib/index.js!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./BasePieChart.vue?vue&type=script&lang=js&\""],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d22d5d9.9d162235.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22d5d9"],{f6d7:function(e,t,n){"use strict";n.r(t);var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("v-btn",{staticClass:"mt-3 mt-xl-0",attrs:{depressed:"",color:"error",large:"",loading:e.redisLoading,disabled:e.redisLoading},on:{click:e.handleFlush}},[e._v(" Flush Redis "),n("v-icon",{attrs:{right:"",dark:""}},[e._v("mdi-delete")])],1)},s=[],a=n("1da1"),i=n("5530"),d=(n("96cf"),n("2f62")),c={computed:Object(i["a"])({},Object(d["c"])({redisLoading:"getRedisLoading"})),methods:Object(i["a"])(Object(i["a"])(Object(i["a"])({},Object(d["b"])({flush:"flush"})),Object(d["d"])({negateRefreshSignal:"NEGATE_REFRESH_SIGNAL",setRedisLoading:"SET_REDIS_LOADING"})),{},{handleFlush:function(){var e=this;return Object(a["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return e.setRedisLoading(!0),t.prev=1,t.next=4,e.flush();case 4:e.negateRefreshSignal(),e.$notify({group:"main",title:"Redis",text:"Redis flushed!",type:"success",duration:400,speed:400}),t.next=12;break;case 8:t.prev=8,t.t0=t["catch"](1),console.error(t.t0),e.$notify({group:"main",title:"Error",text:"Unexpected error occured.",type:"error",duration:400,speed:400});case 12:e.setRedisLoading(!1);case 13:case"end":return t.stop()}}),t,null,[[1,8]])})))()}})},o=c,l=n("2877"),u=n("6544"),h=n.n(u),p=n("8336"),g=n("132d"),f=Object(l["a"])(o,r,s,!1,null,null,null);t["default"]=f.exports;h()(f,{VBtn:p["a"],VIcon:g["a"]})}}]); 2 | //# sourceMappingURL=chunk-2d22d5d9.9d162235.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d22d5d9.9d162235.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/UI/TheFlushButton.vue?69ea","webpack:///src/components/UI/TheFlushButton.vue","webpack:///./src/components/UI/TheFlushButton.vue?a20e","webpack:///./src/components/UI/TheFlushButton.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","staticClass","attrs","redisLoading","on","handleFlush","_v","staticRenderFns","console","error","component","VBtn","VIcon"],"mappings":"uHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,QAAQ,CAACE,YAAY,eAAeC,MAAM,CAAC,UAAY,GAAG,MAAQ,QAAQ,MAAQ,GAAG,QAAUP,EAAIQ,aAAa,SAAWR,EAAIQ,cAAcC,GAAG,CAAC,MAAQT,EAAIU,cAAc,CAACV,EAAIW,GAAG,iBAAiBP,EAAG,SAAS,CAACG,MAAM,CAAC,MAAQ,GAAG,KAAO,KAAK,CAACP,EAAIW,GAAG,iBAAiB,IACvWC,EAAkB,G,gDCStB,GACE,SAAF,kBACA,gBAAI,aAAJ,qBAGE,QAAF,gDACA,gBAAI,MAAJ,WACA,gBAAI,oBAAJ,wBAAI,gBAAJ,uBAFA,IAII,YAJJ,WAIM,IAAN,OAAM,OAAN,qDAAQ,OAAR,iFACA,sBADA,kBAIA,UAJA,OAMgB,EAAhB,sBAEgB,EAAhB,SACkB,MAAlB,OACkB,MAAlB,QACkB,KAAlB,iBACkB,KAAlB,UACkB,SAAlB,IACkB,MAAlB,MAdA,mDAiBgBC,QAAQC,MAAM,EAA9B,IAEgB,EAAhB,SACkB,MAAlB,OACkB,MAAlB,QACkB,KAAlB,4BACkB,KAAlB,QACkB,SAAlB,IACkB,MAAlB,MAzBA,QA6BA,sBA7BA,+DCnBuW,I,yDCOnWC,EAAY,eACd,EACAhB,EACAa,GACA,EACA,KACA,KACA,MAIa,aAAAG,EAAiB,QAMhC,IAAkBA,EAAW,CAACC,OAAA,KAAKC,QAAA","file":"js/chunk-2d22d5d9.9d162235.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-btn',{staticClass:\"mt-3 mt-xl-0\",attrs:{\"depressed\":\"\",\"color\":\"error\",\"large\":\"\",\"loading\":_vm.redisLoading,\"disabled\":_vm.redisLoading},on:{\"click\":_vm.handleFlush}},[_vm._v(\" Flush Redis \"),_c('v-icon',{attrs:{\"right\":\"\",\"dark\":\"\"}},[_vm._v(\"mdi-delete\")])],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheFlushButton.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheFlushButton.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheFlushButton.vue?vue&type=template&id=adbf3476&\"\nimport script from \"./TheFlushButton.vue?vue&type=script&lang=js&\"\nexport * from \"./TheFlushButton.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VBtn } from 'vuetify/lib/components/VBtn';\nimport { VIcon } from 'vuetify/lib/components/VIcon';\ninstallComponents(component, {VBtn,VIcon})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d237b51.dbdaaf30.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d237b51"],{fbed:function(e,t,r){"use strict";r.r(t);var n=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("v-btn",{staticClass:"mt-3 mt-xl-0",attrs:{color:"warning",large:"",tile:"",loading:e.redisLoading,disabled:e.redisLoading},on:{click:e.handleReset}},[e._v(" Reset Data "),r("v-icon",{attrs:{right:"",dark:""}},[e._v("mdi-restart")])],1)},a=[],s=r("1da1"),i=r("5530"),c=(r("96cf"),r("2f62")),o={computed:Object(i["a"])({},Object(c["c"])({redisLoading:"getRedisLoading"})),methods:Object(i["a"])(Object(i["a"])(Object(i["a"])({},Object(c["b"])({reset:"reset"})),Object(c["d"])({negateRefreshSignal:"NEGATE_REFRESH_SIGNAL",setRedisLoading:"SET_REDIS_LOADING"})),{},{handleReset:function(){var e=this;return Object(s["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return e.setRedisLoading(!0),t.prev=1,t.next=4,e.reset();case 4:e.negateRefreshSignal(),e.$notify({group:"main",title:"Data",text:"Data reseted!",type:"success",duration:400,speed:400}),t.next=12;break;case 8:t.prev=8,t.t0=t["catch"](1),console.error(t.t0),e.$notify({group:"main",title:"Error",text:"Unexpected error occured.",type:"error",duration:400,speed:400});case 12:e.setRedisLoading(!1);case 13:case"end":return t.stop()}}),t,null,[[1,8]])})))()}})},d=o,l=r("2877"),u=r("6544"),g=r.n(u),p=r("8336"),b=r("132d"),f=Object(l["a"])(d,n,a,!1,null,null,null);t["default"]=f.exports;g()(f,{VBtn:p["a"],VIcon:b["a"]})}}]); 2 | //# sourceMappingURL=chunk-2d237b51.dbdaaf30.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d237b51.dbdaaf30.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/UI/TheResetButton.vue?38cf","webpack:///src/components/UI/TheResetButton.vue","webpack:///./src/components/UI/TheResetButton.vue?406e","webpack:///./src/components/UI/TheResetButton.vue"],"names":["render","_vm","this","_h","$createElement","_c","_self","staticClass","attrs","redisLoading","on","handleReset","_v","staticRenderFns","console","error","component","VBtn","VIcon"],"mappings":"uHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,QAAQ,CAACE,YAAY,eAAeC,MAAM,CAAC,MAAQ,UAAU,MAAQ,GAAG,KAAO,GAAG,QAAUP,EAAIQ,aAAa,SAAWR,EAAIQ,cAAcC,GAAG,CAAC,MAAQT,EAAIU,cAAc,CAACV,EAAIW,GAAG,gBAAgBP,EAAG,SAAS,CAACG,MAAM,CAAC,MAAQ,GAAG,KAAO,KAAK,CAACP,EAAIW,GAAG,kBAAkB,IACpWC,EAAkB,G,gDCiBtB,GACE,SAAF,kBACA,gBAAI,aAAJ,qBAGE,QAAF,gDACA,gBAAI,MAAJ,WACA,gBAAI,oBAAJ,wBAAI,gBAAJ,uBAFA,IAII,YAJJ,WAIM,IAAN,OAAM,OAAN,qDAAQ,OAAR,iFACA,sBADA,kBAIA,UAJA,OAMgB,EAAhB,sBAEgB,EAAhB,SACkB,MAAlB,OACkB,MAAlB,OACkB,KAAlB,gBACkB,KAAlB,UACkB,SAAlB,IACkB,MAAlB,MAdA,mDAiBgBC,QAAQC,MAAM,EAA9B,IAEgB,EAAhB,SACkB,MAAlB,OACkB,MAAlB,QACkB,KAAlB,4BACkB,KAAlB,QACkB,SAAlB,IACkB,MAAlB,MAzBA,QA6BA,sBA7BA,+DC3BuW,I,yDCOnWC,EAAY,eACd,EACAhB,EACAa,GACA,EACA,KACA,KACA,MAIa,aAAAG,EAAiB,QAMhC,IAAkBA,EAAW,CAACC,OAAA,KAAKC,QAAA","file":"js/chunk-2d237b51.dbdaaf30.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-btn',{staticClass:\"mt-3 mt-xl-0\",attrs:{\"color\":\"warning\",\"large\":\"\",\"tile\":\"\",\"loading\":_vm.redisLoading,\"disabled\":_vm.redisLoading},on:{\"click\":_vm.handleReset}},[_vm._v(\" Reset Data \"),_c('v-icon',{attrs:{\"right\":\"\",\"dark\":\"\"}},[_vm._v(\"mdi-restart\")])],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheResetButton.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheResetButton.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheResetButton.vue?vue&type=template&id=27a1a84e&\"\nimport script from \"./TheResetButton.vue?vue&type=script&lang=js&\"\nexport * from \"./TheResetButton.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VBtn } from 'vuetify/lib/components/VBtn';\nimport { VIcon } from 'vuetify/lib/components/VIcon';\ninstallComponents(component, {VBtn,VIcon})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-2d237d5d.abb1ebc2.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d237d5d"],{fd8a:function(n,t,a){"use strict";a.r(t);var e,c,d=a("1fca"),i=d["d"].reactiveProp,r={mixins:[d["b"],i],mounted:function(){this.renderChart(this.chartData)}},u=r,o=a("2877"),s=Object(o["a"])(u,e,c,!1,null,null,null);t["default"]=s.exports}}]); 2 | //# sourceMappingURL=chunk-2d237d5d.abb1ebc2.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-2d237d5d.abb1ebc2.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/components/UI/Charts/BaseLineChart.vue","webpack:///src/components/UI/Charts/BaseLineChart.vue","webpack:///./src/components/UI/Charts/BaseLineChart.vue?f3b0"],"names":["render","staticRenderFns","component"],"mappings":"2HAAIA,EAAQC,E,YCEZ,sBAEA,GACE,OAAF,WAEE,QAHF,WAII,KAAJ,8BCRqX,I,YFOjXC,EAAY,eACd,EACAF,EACAC,GACA,EACA,KACA,KACA,MAIa,aAAAC,E","file":"js/chunk-2d237d5d.abb1ebc2.js","sourcesContent":["var render, staticRenderFns\nimport script from \"./BaseLineChart.vue?vue&type=script&lang=js&\"\nexport * from \"./BaseLineChart.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n","import mod from \"-!../../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../../node_modules/thread-loader/dist/cjs.js!../../../../node_modules/babel-loader/lib/index.js!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./BaseLineChart.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../../node_modules/thread-loader/dist/cjs.js!../../../../node_modules/babel-loader/lib/index.js!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./BaseLineChart.vue?vue&type=script&lang=js&\""],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-6065e614.bbfdefe4.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6065e614"],{"166a":function(e,t,i){},"604c":function(e,t,i){"use strict";i.d(t,"a",(function(){return u}));var n=i("5530"),a=(i("a9e3"),i("4de4"),i("caad"),i("2532"),i("a434"),i("159b"),i("fb6a"),i("7db0"),i("c740"),i("166a"),i("a452")),r=i("7560"),s=i("58df"),l=i("d9bd"),u=Object(s["a"])(a["a"],r["a"]).extend({name:"base-item-group",props:{activeClass:{type:String,default:"v-item--active"},mandatory:Boolean,max:{type:[Number,String],default:null},multiple:Boolean,tag:{type:String,default:"div"}},data:function(){return{internalLazyValue:void 0!==this.value?this.value:this.multiple?[]:void 0,items:[]}},computed:{classes:function(){return Object(n["a"])({"v-item-group":!0},this.themeClasses)},selectedIndex:function(){return this.selectedItem&&this.items.indexOf(this.selectedItem)||-1},selectedItem:function(){if(!this.multiple)return this.selectedItems[0]},selectedItems:function(){var e=this;return this.items.filter((function(t,i){return e.toggleMethod(e.getValue(t,i))}))},selectedValues:function(){return null==this.internalValue?[]:Array.isArray(this.internalValue)?this.internalValue:[this.internalValue]},toggleMethod:function(){var e=this;if(!this.multiple)return function(t){return e.internalValue===t};var t=this.internalValue;return Array.isArray(t)?function(e){return t.includes(e)}:function(){return!1}}},watch:{internalValue:"updateItemsState",items:"updateItemsState"},created:function(){this.multiple&&!Array.isArray(this.internalValue)&&Object(l["c"])("Model must be bound to an array if the multiple property is true.",this)},methods:{genData:function(){return{class:this.classes}},getValue:function(e,t){return null==e.value||""===e.value?t:e.value},onClick:function(e){this.updateInternalValue(this.getValue(e,this.items.indexOf(e)))},register:function(e){var t=this,i=this.items.push(e)-1;e.$on("change",(function(){return t.onClick(e)})),this.mandatory&&!this.selectedValues.length&&this.updateMandatory(),this.updateItem(e,i)},unregister:function(e){if(!this._isDestroyed){var t=this.items.indexOf(e),i=this.getValue(e,t);this.items.splice(t,1);var n=this.selectedValues.indexOf(i);if(!(n<0)){if(!this.mandatory)return this.updateInternalValue(i);this.multiple&&Array.isArray(this.internalValue)?this.internalValue=this.internalValue.filter((function(e){return e!==i})):this.internalValue=void 0,this.selectedItems.length||this.updateMandatory(!0)}}},updateItem:function(e,t){var i=this.getValue(e,t);e.isActive=this.toggleMethod(i)},updateItemsState:function(){var e=this;this.$nextTick((function(){if(e.mandatory&&!e.selectedItems.length)return e.updateMandatory();e.items.forEach(e.updateItem)}))},updateInternalValue:function(e){this.multiple?this.updateMultiple(e):this.updateSingle(e)},updateMandatory:function(e){if(this.items.length){var t=this.items.slice();e&&t.reverse();var i=t.find((function(e){return!e.disabled}));if(i){var n=this.items.indexOf(i);this.updateInternalValue(this.getValue(i,n))}}},updateMultiple:function(e){var t=Array.isArray(this.internalValue)?this.internalValue:[],i=t.slice(),n=i.findIndex((function(t){return t===e}));this.mandatory&&n>-1&&i.length-1<1||null!=this.max&&n<0&&i.length+1>this.max||(n>-1?i.splice(n,1):i.push(e),this.internalValue=i)},updateSingle:function(e){var t=e===this.internalValue;this.mandatory&&t||(this.internalValue=t?void 0:e)}},render:function(e){return e(this.tag,this.genData(),this.$slots.default)}});u.extend({name:"v-item-group",provide:function(){return{itemGroup:this}}})},"8ce9":function(e,t,i){},"9d65":function(e,t,i){"use strict";var n=i("d9bd"),a=i("2b0e");t["a"]=a["default"].extend().extend({name:"bootable",props:{eager:Boolean},data:function(){return{isBooted:!1}},computed:{hasContent:function(){return this.isBooted||this.eager||this.isActive}},watch:{isActive:function(){this.isBooted=!0}},created:function(){"lazy"in this.$attrs&&Object(n["e"])("lazy",this)},methods:{showLazyContent:function(e){return this.hasContent&&e?e():[this.$createElement()]}}})},a434:function(e,t,i){"use strict";var n=i("23e7"),a=i("23cb"),r=i("a691"),s=i("50c4"),l=i("7b0b"),u=i("65f0"),o=i("8418"),c=i("1dde"),d=c("splice"),h=Math.max,f=Math.min,m=9007199254740991,v="Maximum allowed length exceeded";n({target:"Array",proto:!0,forced:!d},{splice:function(e,t){var i,n,c,d,p,g,b=l(this),y=s(b.length),x=a(e,y),V=arguments.length;if(0===V?i=n=0:1===V?(i=0,n=y-x):(i=V-2,n=f(h(r(t),0),y-x)),y+i-n>m)throw TypeError(v);for(c=u(b,n),d=0;dy-n+i;d--)delete b[d-1]}else if(i>n)for(d=y-n;d>x;d--)p=d+n-1,g=d+i-1,p in b?b[g]=b[p]:delete b[g];for(d=0;d1?arguments[1]:void 0)}}),r(s)},ce7e:function(e,t,i){"use strict";var n=i("5530"),a=(i("8ce9"),i("7560"));t["a"]=a["a"].extend({name:"v-divider",props:{inset:Boolean,vertical:Boolean},render:function(e){var t;return this.$attrs.role&&"separator"!==this.$attrs.role||(t=this.vertical?"vertical":"horizontal"),e("hr",{class:Object(n["a"])({"v-divider":!0,"v-divider--inset":this.inset,"v-divider--vertical":this.vertical},this.themeClasses),attrs:Object(n["a"])({role:"separator","aria-orientation":t},this.$attrs),on:this.$listeners})}})},dc22:function(e,t,i){"use strict";function n(e,t){var i=t.value,n=t.options||{passive:!0};window.addEventListener("resize",i,n),e._onResize={callback:i,options:n},t.modifiers&&t.modifiers.quiet||i()}function a(e){if(e._onResize){var t=e._onResize,i=t.callback,n=t.options;window.removeEventListener("resize",i,n),delete e._onResize}}var r={inserted:n,unbind:a};t["a"]=r}}]); 2 | //# sourceMappingURL=chunk-6065e614.bbfdefe4.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-cfaaa91c.5809266b.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-cfaaa91c"],{"1f4f":function(t,e,a){"use strict";var r=a("5530"),i=(a("a9e3"),a("8b37"),a("80d2")),c=a("7560"),f=a("58df");e["a"]=Object(f["a"])(c["a"]).extend({name:"v-simple-table",props:{dense:Boolean,fixedHeader:Boolean,height:[Number,String]},computed:{classes:function(){return Object(r["a"])({"v-data-table--dense":this.dense,"v-data-table--fixed-height":!!this.height&&!this.fixedHeader,"v-data-table--fixed-header":this.fixedHeader,"v-data-table--has-top":!!this.$slots.top,"v-data-table--has-bottom":!!this.$slots.bottom},this.themeClasses)}},methods:{genWrapper:function(){return this.$slots.wrapper||this.$createElement("div",{staticClass:"v-data-table__wrapper",style:{height:Object(i["d"])(this.height)}},[this.$createElement("table",this.$slots.default)])}},render:function(t){return t("div",{staticClass:"v-data-table",class:this.classes},[this.$slots.top,this.genWrapper(),this.$slots.bottom])}})},"8b37":function(t,e,a){},d3e4:function(t,e,a){"use strict";a.r(e);var r=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("v-card",{attrs:{loading:t.loading}},[a("v-card-title",{staticClass:"px-4"},[t._v("Traffic per Source")]),a("v-card-text",{staticClass:"px-4"},[a("v-row",[a("v-col",{attrs:{cols:"12",lg:"8"}},[a("v-simple-table",[a("thead",[a("tr",[a("th",{staticClass:"text-left"},[t._v(" Source name ")]),a("th",{staticClass:"text-left"},[t._v(" Visit count ")])])]),a("tbody",[a("tr",[a("td",[t._v("Google Ads")]),a("td",[t._v(t._s(t.googleTraffic))])]),a("tr",[a("td",[t._v("Facebook Ads")]),a("td",[t._v(t._s(t.facebookTraffic))])]),a("tr",[a("td",[t._v("Email")]),a("td",[t._v(t._s(t.emailTraffic))])]),a("tr",[a("td",[t._v("Direct")]),a("td",[t._v(t._s(t.directTraffic))])]),a("tr",[a("td",[t._v("Referral")]),a("td",[t._v(t._s(t.referralTraffic))])]),a("tr",[a("td",[t._v("None")]),a("td",[t._v(t._s(t.noneTraffic))])])])])],1),a("v-col",{attrs:{cols:"12",lg:"4"}},[a("div",{staticStyle:{position:"relative"}},[a("base-pie-chart",{attrs:{"chart-data":t.chartData}})],1)])],1)],1)],1)},i=[],c=a("1da1"),f=a("5530"),n=(a("96cf"),a("d3b7"),a("3ca3"),a("ddb0"),a("7db0"),a("2f62")),o={components:{basePieChart:function(){return Promise.all([a.e("chunk-434cf637"),a.e("chunk-2d21ed56")]).then(a.bind(null,"d6f4"))}},data:function(){return{googleTraffic:0,facebookTraffic:0,emailTraffic:0,directTraffic:0,referralTraffic:0,noneTraffic:0,loading:!1}},computed:Object(f["a"])(Object(f["a"])({},Object(n["c"])({refreshSignal:"refreshSignal",period:"getPeriod"})),{},{chartData:function(){return{labels:["Google Ads","Facebook Ads","Email","Direct","Referral","None"],datasets:[{data:[this.googleTraffic,this.facebookTraffic,this.emailTraffic,this.directTraffic,this.referralTraffic,this.noneTraffic],backgroundColor:["rgba(255, 99, 132, 1)","rgba(54, 162, 235, 1)","rgba(255, 206, 86, 1)","rgba(75, 192, 192, 1)","rgba(60, 192, 1, 192)","rgba(200, 250, 5, 90)"],borderWidth:0}]}}}),created:function(){this.fetchTrafficData(this.period)},watch:{refreshSignal:function(){this.fetchTrafficData(this.period)},period:function(t){this.fetchTrafficData(t)}},methods:Object(f["a"])(Object(f["a"])({},Object(n["b"])({fetchTraffic:"fetchTraffic"})),{},{fetchTrafficData:function(t){var e=this;return Object(c["a"])(regeneratorRuntime.mark((function a(){var r;return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return e.loading=!0,a.next=3,e.fetchTraffic({filter:{sources:["google","facebook","email","direct","referral","none"]},period:t});case 3:r=a.sent,e.googleTraffic=r.find((function(t){return"google"===t.value})).count,e.facebookTraffic=r.find((function(t){return"facebook"===t.value})).count,e.emailTraffic=r.find((function(t){return"email"===t.value})).count,e.directTraffic=r.find((function(t){return"direct"===t.value})).count,e.referralTraffic=r.find((function(t){return"referral"===t.value})).count,e.noneTraffic=r.find((function(t){return"none"===t.value})).count,e.loading=!1;case 11:case"end":return a.stop()}}),a)})))()}})},s=o,d=a("2877"),l=a("6544"),u=a.n(l),h=a("b0af"),b=a("99d9"),v=a("62ad"),g=a("0fd9"),p=a("1f4f"),T=Object(d["a"])(s,r,i,!1,null,null,null);e["default"]=T.exports;u()(T,{VCard:h["a"],VCardText:b["c"],VCardTitle:b["d"],VCol:v["a"],VRow:g["a"],VSimpleTable:p["a"]})}}]); 2 | //# sourceMappingURL=chunk-cfaaa91c.5809266b.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-d4383190.47a87e77.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-d4383190"],{"1f4f":function(t,e,a){"use strict";var s=a("5530"),r=(a("a9e3"),a("8b37"),a("80d2")),i=a("7560"),n=a("58df");e["a"]=Object(n["a"])(i["a"]).extend({name:"v-simple-table",props:{dense:Boolean,fixedHeader:Boolean,height:[Number,String]},computed:{classes:function(){return Object(s["a"])({"v-data-table--dense":this.dense,"v-data-table--fixed-height":!!this.height&&!this.fixedHeader,"v-data-table--fixed-header":this.fixedHeader,"v-data-table--has-top":!!this.$slots.top,"v-data-table--has-bottom":!!this.$slots.bottom},this.themeClasses)}},methods:{genWrapper:function(){return this.$slots.wrapper||this.$createElement("div",{staticClass:"v-data-table__wrapper",style:{height:Object(r["d"])(this.height)}},[this.$createElement("table",this.$slots.default)])}},render:function(t){return t("div",{staticClass:"v-data-table",class:this.classes},[this.$slots.top,this.genWrapper(),this.$slots.bottom])}})},"8b37":function(t,e,a){},"9fe0":function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("v-card",{staticClass:"card",attrs:{loading:t.loading}},[a("v-card-title",{staticClass:"px-4"},[t._v("Product Bought")]),a("v-card-text",{staticClass:"px-4"},[a("v-simple-table",[a("tbody",[a("tr",[a("td",[t._v("Total Product Bought")]),a("td",[t._v(t._s(t.totalProductBought))])])])])],1)],1)},r=[],i=a("3835"),n=a("1da1"),o=a("5530"),c=(a("96cf"),a("2f62")),l={data:function(){return{totalProductBought:0,loading:!1}},computed:Object(o["a"])({},Object(c["c"])({refreshSignal:"refreshSignal",period:"getPeriod"})),created:function(){this.fetchSalesData(this.period)},watch:{refreshSignal:function(){this.fetchSalesData(this.period)},period:function(t){this.fetchSalesData(t)}},methods:Object(o["a"])(Object(o["a"])({},Object(c["b"])({fetchSales:"fetchSales"})),{},{fetchSalesData:function(t){var e=this;return Object(n["a"])(regeneratorRuntime.mark((function a(){var s,r,n;return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return e.loading=!0,a.next=3,e.fetchSales({period:t,filter:{total:!0}});case 3:s=a.sent,r=Object(i["a"])(s,1),n=r[0],e.totalProductBought=n.bought,e.loading=!1;case 8:case"end":return a.stop()}}),a)})))()}})},d=l,h=a("2877"),u=a("6544"),f=a.n(u),b=a("b0af"),p=a("99d9"),g=a("1f4f"),v=Object(h["a"])(d,s,r,!1,null,null,null);e["default"]=v.exports;f()(v,{VCard:b["a"],VCardText:p["c"],VCardTitle:p["d"],VSimpleTable:g["a"]})}}]); 2 | //# sourceMappingURL=chunk-d4383190.47a87e77.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-d4383190.47a87e77.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///../../../src/components/VDataTable/VSimpleTable.ts","webpack:///./src/components/DataDisplay/TheTotalProductsBought.vue?b381","webpack:///src/components/DataDisplay/TheTotalProductsBought.vue","webpack:///./src/components/DataDisplay/TheTotalProductsBought.vue?077e","webpack:///./src/components/DataDisplay/TheTotalProductsBought.vue"],"names":["name","props","dense","fixedHeader","height","Number","String","computed","classes","this","$slots","themeClasses","methods","genWrapper","wrapper","$createElement","staticClass","style","render","h","class","_vm","_h","_c","_self","attrs","loading","_v","_s","totalProductBought","staticRenderFns","component","VCard","VCardText","VCardTitle","VSimpleTable"],"mappings":"4LAOe,qCAAyB,CACtCA,KADsC,iBAGtCC,MAAO,CACLC,MADK,QAELC,YAFK,QAGLC,OAAQ,CAACC,OAAQC,SAGnBC,SAAU,CACRC,QADQ,WAEN,uBACE,sBAAuBC,KADlB,MAEL,+BAAgCA,KAAF,SAAkBA,KAF3C,YAGL,6BAA8BA,KAHzB,YAIL,0BAA2BA,KAAKC,OAJ3B,IAKL,6BAA8BD,KAAKC,OAL9B,QAMFD,KAAKE,gBAKdC,QAAS,CACPC,WADO,WAEL,OAAOJ,KAAKC,OAAOI,SAAWL,KAAKM,eAAe,MAAO,CACvDC,YADuD,wBAEvDC,MAAO,CACLb,OAAQ,eAAcK,KAAD,UAEtB,CACDA,KAAKM,eAAe,QAASN,KAAKC,OANpC,aAWJQ,OAnCsC,SAmChC,GACJ,OAAOC,EAAE,MAAO,CACdH,YADc,eAEdI,MAAOX,KAAKD,SACX,CACDC,KAAKC,OADJ,IAEDD,KAFC,aAGDA,KAAKC,OANP,a,oEC3CJ,IAAIQ,EAAS,WAAa,IAAIG,EAAIZ,KAASa,EAAGD,EAAIN,eAAmBQ,EAAGF,EAAIG,MAAMD,IAAID,EAAG,OAAOC,EAAG,SAAS,CAACP,YAAY,OAAOS,MAAM,CAAC,QAAUJ,EAAIK,UAAU,CAACH,EAAG,eAAe,CAACP,YAAY,QAAQ,CAACK,EAAIM,GAAG,oBAAoBJ,EAAG,cAAc,CAACP,YAAY,QAAQ,CAACO,EAAG,iBAAiB,CAACA,EAAG,QAAQ,CAACA,EAAG,KAAK,CAACA,EAAG,KAAK,CAACF,EAAIM,GAAG,0BAA0BJ,EAAG,KAAK,CAACF,EAAIM,GAAGN,EAAIO,GAAGP,EAAIQ,8BAA8B,IAAI,IACvZC,EAAkB,G,4DCmBtB,GACE,KADF,WAEI,MAAJ,CACM,mBAAN,EACM,SAAN,IAIE,SAAF,kBACA,gBACI,cAAJ,gBACI,OAAJ,eAIE,QAfF,WAgBI,KAAJ,6BAGE,MAAF,CACI,cADJ,WAEM,KAAN,6BAEI,OAJJ,SAIA,GACM,KAAN,oBAIE,QAAF,iCACA,gBAAI,WAAJ,gBADA,IAGI,eAHJ,SAGA,GAAM,IAAN,OAAM,OAAN,qDAAQ,IAAR,MAAQ,OAAR,iFACA,aADA,SAGA,2CAHA,sCAGA,EAHA,KAKA,8BACA,aANA,iDCnD+W,I,qECO3WC,EAAY,eACd,EACAb,EACAY,GACA,EACA,KACA,KACA,MAIa,aAAAC,EAAiB,QAQhC,IAAkBA,EAAW,CAACC,QAAA,KAAMC,UAAA,OAAUC,WAAA,OAAWC,eAAA","file":"js/chunk-d4383190.47a87e77.js","sourcesContent":["import './VSimpleTable.sass'\n\nimport { convertToUnit } from '../../util/helpers'\nimport Themeable from '../../mixins/themeable'\nimport mixins from '../../util/mixins'\nimport { VNode } from 'vue'\n\nexport default mixins(Themeable).extend({\n name: 'v-simple-table',\n\n props: {\n dense: Boolean,\n fixedHeader: Boolean,\n height: [Number, String],\n },\n\n computed: {\n classes (): Record {\n return {\n 'v-data-table--dense': this.dense,\n 'v-data-table--fixed-height': !!this.height && !this.fixedHeader,\n 'v-data-table--fixed-header': this.fixedHeader,\n 'v-data-table--has-top': !!this.$slots.top,\n 'v-data-table--has-bottom': !!this.$slots.bottom,\n ...this.themeClasses,\n }\n },\n },\n\n methods: {\n genWrapper () {\n return this.$slots.wrapper || this.$createElement('div', {\n staticClass: 'v-data-table__wrapper',\n style: {\n height: convertToUnit(this.height),\n },\n }, [\n this.$createElement('table', this.$slots.default),\n ])\n },\n },\n\n render (h): VNode {\n return h('div', {\n staticClass: 'v-data-table',\n class: this.classes,\n }, [\n this.$slots.top,\n this.genWrapper(),\n this.$slots.bottom,\n ])\n },\n})\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('v-card',{staticClass:\"card\",attrs:{\"loading\":_vm.loading}},[_c('v-card-title',{staticClass:\"px-4\"},[_vm._v(\"Product Bought\")]),_c('v-card-text',{staticClass:\"px-4\"},[_c('v-simple-table',[_c('tbody',[_c('tr',[_c('td',[_vm._v(\"Total Product Bought\")]),_c('td',[_vm._v(_vm._s(_vm.totalProductBought))])])])])],1)],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheTotalProductsBought.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??ref--12-0!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./TheTotalProductsBought.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./TheTotalProductsBought.vue?vue&type=template&id=2ff7b1f4&\"\nimport script from \"./TheTotalProductsBought.vue?vue&type=script&lang=js&\"\nexport * from \"./TheTotalProductsBought.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports\n\n/* vuetify-loader */\nimport installComponents from \"!../../../node_modules/vuetify-loader/lib/runtime/installComponents.js\"\nimport { VCard } from 'vuetify/lib/components/VCard';\nimport { VCardText } from 'vuetify/lib/components/VCard';\nimport { VCardTitle } from 'vuetify/lib/components/VCard';\nimport { VSimpleTable } from 'vuetify/lib/components/VDataTable';\ninstallComponents(component, {VCard,VCardText,VCardTitle,VSimpleTable})\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client-dist/js/chunk-d466345a.dd688bc4.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-d466345a"],{"1f4f":function(t,e,a){"use strict";var r=a("5530"),i=(a("a9e3"),a("8b37"),a("80d2")),c=a("7560"),s=a("58df");e["a"]=Object(s["a"])(c["a"]).extend({name:"v-simple-table",props:{dense:Boolean,fixedHeader:Boolean,height:[Number,String]},computed:{classes:function(){return Object(r["a"])({"v-data-table--dense":this.dense,"v-data-table--fixed-height":!!this.height&&!this.fixedHeader,"v-data-table--fixed-header":this.fixedHeader,"v-data-table--has-top":!!this.$slots.top,"v-data-table--has-bottom":!!this.$slots.bottom},this.themeClasses)}},methods:{genWrapper:function(){return this.$slots.wrapper||this.$createElement("div",{staticClass:"v-data-table__wrapper",style:{height:Object(i["d"])(this.height)}},[this.$createElement("table",this.$slots.default)])}},render:function(t){return t("div",{staticClass:"v-data-table",class:this.classes},[this.$slots.top,this.genWrapper(),this.$slots.bottom])}})},"8b37":function(t,e,a){},9534:function(t,e,a){"use strict";a.r(e);var r=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("v-card",{staticClass:"card",attrs:{loading:t.loading}},[a("v-card-title",{staticClass:"px-4"},[t._v("Traffic")]),a("v-card-text",{staticClass:"px-4"},[a("h2",{staticClass:"font-weight-regular my-5"},[t._v("Total traffic")]),a("v-simple-table",[a("thead",[a("tr",[a("th",{staticClass:"text-left"},[t._v(" Name ")]),a("th",{staticClass:"text-left"},[t._v(" Visit count ")])])]),a("tbody",[a("tr",[a("td",[t._v("Total traffic")]),a("td",[t._v(t._s(t.totalTraffic))])])])]),a("h2",{staticClass:"font-weight-regular my-5"},[t._v("Traffic per page")]),a("v-simple-table",[a("thead",[a("tr",[a("th",{staticClass:"text-left"},[t._v(" Page name ")]),a("th",{staticClass:"text-left"},[t._v(" Visit count ")])])]),a("tbody",[a("tr",[a("td",[t._v("Homepage")]),a("td",[t._v(t._s(t.homepageTraffic))])]),a("tr",[a("td",[t._v("Product 1 Page")]),a("td",[t._v(t._s(t.product1pageTraffic))])]),a("tr",[a("td",[t._v("Product 2 Page")]),a("td",[t._v(t._s(t.product2pageTraffic))])]),a("tr",[a("td",[t._v("Product 3 Page")]),a("td",[t._v(t._s(t.product3pageTraffic))])])])])],1)],1)},i=[],c=a("3835"),s=a("1da1"),f=a("5530"),n=(a("96cf"),a("7db0"),a("2f62")),o={data:function(){return{totalTraffic:0,homepageTraffic:0,product1pageTraffic:0,product2pageTraffic:0,product3pageTraffic:0,loading:!1}},computed:Object(f["a"])({},Object(n["c"])({refreshSignal:"refreshSignal",traffic:"getTraffic",period:"getPeriod"})),created:function(){this.fetchTrafficData(this.period)},watch:{refreshSignal:function(){this.fetchTrafficData(this.period)},period:function(t){this.fetchTrafficData(t)}},methods:Object(f["a"])(Object(f["a"])({},Object(n["b"])({fetchTraffic:"fetchTraffic"})),{},{fetchTrafficData:function(t){var e=this;return Object(s["a"])(regeneratorRuntime.mark((function a(){var r,i,s,f;return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return e.loading=!0,a.next=3,e.fetchTraffic({filter:{pages:["homepage","product1","product2","product3"]},period:t});case 3:return r=a.sent,a.next=6,e.fetchTraffic({filter:{total:!0},period:t});case 6:i=a.sent,s=Object(c["a"])(i,1),f=s[0],e.totalTraffic=f.count,e.homepageTraffic=r.find((function(t){return"homepage"===t.value})).count,e.product1pageTraffic=r.find((function(t){return"product1"===t.value})).count,e.product2pageTraffic=r.find((function(t){return"product2"===t.value})).count,e.product3pageTraffic=r.find((function(t){return"product3"===t.value})).count,e.loading=!1;case 15:case"end":return a.stop()}}),a)})))()}})},d=o,l=a("2877"),u=a("6544"),h=a.n(u),p=a("b0af"),g=a("99d9"),v=a("1f4f"),b=Object(l["a"])(d,r,i,!1,null,null,null);e["default"]=b.exports;h()(b,{VCard:p["a"],VCardText:g["c"],VCardTitle:g["d"],VSimpleTable:v["a"]})}}]); 2 | //# sourceMappingURL=chunk-d466345a.dd688bc4.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-d4b8c67a.ef703ad0.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-d4b8c67a"],{"1f4f":function(t,e,a){"use strict";var r=a("5530"),s=(a("a9e3"),a("8b37"),a("80d2")),o=a("7560"),n=a("58df");e["a"]=Object(n["a"])(o["a"]).extend({name:"v-simple-table",props:{dense:Boolean,fixedHeader:Boolean,height:[Number,String]},computed:{classes:function(){return Object(r["a"])({"v-data-table--dense":this.dense,"v-data-table--fixed-height":!!this.height&&!this.fixedHeader,"v-data-table--fixed-header":this.fixedHeader,"v-data-table--has-top":!!this.$slots.top,"v-data-table--has-bottom":!!this.$slots.bottom},this.themeClasses)}},methods:{genWrapper:function(){return this.$slots.wrapper||this.$createElement("div",{staticClass:"v-data-table__wrapper",style:{height:Object(s["d"])(this.height)}},[this.$createElement("table",this.$slots.default)])}},render:function(t){return t("div",{staticClass:"v-data-table",class:this.classes},[this.$slots.top,this.genWrapper(),this.$slots.bottom])}})},"677c":function(t,e,a){"use strict";a.r(e);var r=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("v-card",{staticClass:"card"},[a("v-card-title",{staticClass:"px-4"},[t._v(" Cohort Analysis ")]),a("v-card-subtitle",{staticClass:"px-4"},[t._v(" % of people who registered in December and then bought some product ")]),a("v-card-text",{staticClass:"px-4"},[a("v-row",[a("v-col",{attrs:{cols:"12",lg:"7"}},[a("v-simple-table",[a("thead",[a("tr",[a("th",{staticClass:"text-left"},[t._v(" Name ")]),a("th",{staticClass:"text-left"},[t._v(" Count ")])])]),a("tbody",[a("tr",[a("td",[t._v("People who registered")]),a("td",[t._v(t._s(t.register))])]),a("tr",[a("td",[t._v("People who bought")]),a("td",[t._v(t._s(t.registerThenBought))])]),a("tr",[a("td",[t._v("Dropoff")]),a("td",[t._v(t._s(t.dropoff||"0")+"%")])])])])],1),a("v-col",{attrs:{cols:"12",lg:"5"}},[a("base-horizontal-bar-chart",{attrs:{"chart-data":t.chartData}})],1)],1)],1)],1)},s=[],o=a("1da1"),n=a("5530"),i=(a("96cf"),a("d3b7"),a("3ca3"),a("ddb0"),a("2f62")),c={components:{baseHorizontalBarChart:function(){return Promise.all([a.e("chunk-434cf637"),a.e("chunk-2d0e9995")]).then(a.bind(null,"8de8"))}},data:function(){return{register:0,registerThenBought:0,dropoff:0,loading:!1}},computed:Object(n["a"])(Object(n["a"])({},Object(i["c"])({refreshSignal:"refreshSignal"})),{},{chartData:function(){return{labels:["Registered","Bought"],datasets:[{barPercentage:.5,barThickness:6,maxBarThickness:8,minBarLength:2,data:[this.register,this.registerThenBought],label:"December",borderColor:"rgba(225, 225, 60, 50)",backgroundColor:"rgba(225, 225, 60, 50)"}]}}}),watch:{refreshSignal:function(){this.fetchCohortData()}},created:function(){this.fetchCohortData()},methods:Object(n["a"])(Object(n["a"])({},Object(i["b"])({fetchCohort:"fetchCohort"})),{},{fetchCohortData:function(){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function e(){var a,r,s,o;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.loading=!0,e.next=3,t.fetchCohort();case 3:a=e.sent,r=a.register,s=a.registerThenBought,o=a.dropoff,t.register=r,t.registerThenBought=s,t.dropoff=o,t.loading=!1;case 11:case"end":return e.stop()}}),e)})))()}})},h=c,d=a("2877"),l=a("6544"),u=a.n(l),b=a("b0af"),f=a("99d9"),g=a("62ad"),p=a("0fd9"),v=a("1f4f"),m=Object(d["a"])(h,r,s,!1,null,null,null);e["default"]=m.exports;u()(m,{VCard:b["a"],VCardSubtitle:f["b"],VCardText:f["c"],VCardTitle:f["d"],VCol:g["a"],VRow:p["a"],VSimpleTable:v["a"]})},"8b37":function(t,e,a){}}]); 2 | //# sourceMappingURL=chunk-d4b8c67a.ef703ad0.js.map -------------------------------------------------------------------------------- /client-dist/js/chunk-d4c7e4a2.eb8a2f2f.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-d4c7e4a2"],{"1f4f":function(t,e,a){"use strict";var s=a("5530"),i=(a("a9e3"),a("8b37"),a("80d2")),r=a("7560"),l=a("58df");e["a"]=Object(l["a"])(r["a"]).extend({name:"v-simple-table",props:{dense:Boolean,fixedHeader:Boolean,height:[Number,String]},computed:{classes:function(){return Object(s["a"])({"v-data-table--dense":this.dense,"v-data-table--fixed-height":!!this.height&&!this.fixedHeader,"v-data-table--fixed-header":this.fixedHeader,"v-data-table--has-top":!!this.$slots.top,"v-data-table--has-bottom":!!this.$slots.bottom},this.themeClasses)}},methods:{genWrapper:function(){return this.$slots.wrapper||this.$createElement("div",{staticClass:"v-data-table__wrapper",style:{height:Object(i["d"])(this.height)}},[this.$createElement("table",this.$slots.default)])}},render:function(t){return t("div",{staticClass:"v-data-table",class:this.classes},[this.$slots.top,this.genWrapper(),this.$slots.bottom])}})},"4cc5":function(t,e,a){"use strict";a.r(e);var s=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("v-card",{staticClass:"card",attrs:{loading:t.loading}},[a("v-card-title",{staticClass:"px-4"},[t._v(" "+t._s(t.title)+" ")]),t.subtitle&&t.subtitle.length?a("v-card-subtitle",{staticClass:"px-4"},[t._v(" "+t._s(t.subtitle)+" ")]):t._e(),a("v-card-actions",{staticClass:"px-4"},[t._t("default")],2),a("v-card-text",{staticClass:"px-4"},[a("v-simple-table",{attrs:{"fixed-header":"",height:"300px"}},[a("thead",[a("tr",[a("th",{staticClass:"text-left"},[t._v(" Name ")])])]),a("tbody",t._l(t.data,(function(e){return a("tr",{key:e},[a("td",[t._v(t._s(e))])])})),0)])],1)],1)},i=[],r={props:{title:{type:String,required:!0},subtitle:{type:String,required:!1},data:{type:Array,required:!1,default:function(){return[]}},loading:{type:Boolean,required:!1,default:!1}}},l=r,d=a("2877"),n=a("6544"),c=a.n(n),o=a("b0af"),h=a("99d9"),u=a("1f4f"),p=Object(d["a"])(l,s,i,!1,null,null,null);e["default"]=p.exports;c()(p,{VCard:o["a"],VCardActions:h["a"],VCardSubtitle:h["b"],VCardText:h["c"],VCardTitle:h["d"],VSimpleTable:u["a"]})},"8b37":function(t,e,a){}}]); 2 | //# sourceMappingURL=chunk-d4c7e4a2.eb8a2f2f.js.map -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=http://localhost:3000 2 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ['plugin:vue/essential', 'eslint:recommended', '@vue/prettier'], 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | overrides: [ 15 | { 16 | files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'], 17 | env: { 18 | jest: true 19 | } 20 | } 21 | ] 22 | }; 23 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /client/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | tabWidth: 4, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'none', 8 | bracketSpacing: true, 9 | jsxBracketSameLine: false, 10 | proseWrap: 'never', 11 | htmlWhitespaceSensitivity: 'strict', 12 | endOfLine: 'lf', 13 | arrowParens: 'avoid' 14 | }; 15 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Redis Analytics Bitmaps UI 2 | 3 | ``` 4 | # copy file and set proper data inside 5 | cp .env.example .env 6 | 7 | # install dependencies 8 | npm install 9 | 10 | # run development mode 11 | npm run serve 12 | ``` 13 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'] 3 | }; 4 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-analytics-bitmaps-ui", 3 | "version": "0.1.0", 4 | "description": "This app demonstrates how we can use Redis bitmap methods to gather analytic statistics. It utilizes such methods as SETBIT, BITCOUNT and BITOP.", 5 | "keywords": [ 6 | "VueJS", 7 | "Analytics", 8 | "Statistics", 9 | "vue-chartjs" 10 | ], 11 | "author": "RemoteCraftsmen.com", 12 | "scripts": { 13 | "serve": "vue-cli-service serve", 14 | "build": "vue-cli-service build", 15 | "lint": "vue-cli-service lint", 16 | "prettier-fix": "prettier --write ." 17 | }, 18 | "dependencies": { 19 | "axios": "^0.21.1", 20 | "chart.js": "^2.9.4", 21 | "core-js": "^3.6.5", 22 | "dayjs": "^1.10.2", 23 | "vue": "^2.6.11", 24 | "vue-chartjs": "^3.5.1", 25 | "vue-notification": "^1.3.20", 26 | "vue-router": "^3.2.0", 27 | "vuetify": "^2.2.11", 28 | "vuex": "^3.4.0" 29 | }, 30 | "devDependencies": { 31 | "@vue/cli-plugin-babel": "~4.5.0", 32 | "@vue/cli-plugin-eslint": "^4.5.10", 33 | "@vue/cli-plugin-router": "^4.5.10", 34 | "@vue/cli-plugin-vuex": "^4.5.10", 35 | "@vue/cli-service": "~4.5.0", 36 | "@vue/eslint-config-prettier": "^6.0.0", 37 | "babel-eslint": "^10.1.0", 38 | "eslint": "^6.8.0", 39 | "eslint-plugin-prettier": "^3.3.1", 40 | "eslint-plugin-vue": "^6.2.2", 41 | "prettier": "^2.2.1", 42 | "sass": "^1.19.0", 43 | "sass-loader": "^8.0.0", 44 | "vue-cli-plugin-vuetify": "~2.0.9", 45 | "vue-template-compiler": "^2.6.11", 46 | "vuetify-loader": "^1.3.0" 47 | }, 48 | "eslintConfig": { 49 | "root": true, 50 | "env": { 51 | "node": true 52 | }, 53 | "extends": [ 54 | "plugin:vue/essential", 55 | "eslint:recommended" 56 | ], 57 | "parserOptions": { 58 | "parser": "babel-eslint" 59 | }, 60 | "rules": {} 61 | }, 62 | "browserslist": [ 63 | "> 1%", 64 | "last 2 versions", 65 | "not dead" 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Redis Analytics Bitmaps 9 | 10 | 11 | 12 | 13 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheAbandonedCart.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 76 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheCohort.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheCustomerRetention.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 53 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheCustomersPerProduct.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 65 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheCustomersWithBothProducts.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 57 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheShareOfProductsBought.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 77 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheTotalProductsBought.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 63 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheTrafficPerPage.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 123 | -------------------------------------------------------------------------------- /client/src/components/DataDisplay/TheTrafficPerSource.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 147 | -------------------------------------------------------------------------------- /client/src/components/PeriodSelectCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /client/src/components/TheAnalyticsDemoForm.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 134 | -------------------------------------------------------------------------------- /client/src/components/UI/BaseCard.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | -------------------------------------------------------------------------------- /client/src/components/UI/BaseCollectionCard.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 59 | -------------------------------------------------------------------------------- /client/src/components/UI/BasePeriodSelect.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | -------------------------------------------------------------------------------- /client/src/components/UI/Charts/BaseHorizontalBarChart.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /client/src/components/UI/Charts/BaseLineChart.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /client/src/components/UI/Charts/BasePieChart.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /client/src/components/UI/TheFlushButton.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 54 | -------------------------------------------------------------------------------- /client/src/components/UI/TheResetButton.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 62 | -------------------------------------------------------------------------------- /client/src/config/index.js: -------------------------------------------------------------------------------- 1 | const apiUrl = process.env.VUE_APP_API_URL; 2 | 3 | export { apiUrl }; 4 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify/lib/framework'; 3 | import Notifications from 'vue-notification'; 4 | import App from './App.vue'; 5 | import router from './router'; 6 | import store from './store'; 7 | import './styles/styles.scss'; 8 | 9 | Vue.config.productionTip = false; 10 | 11 | Vue.use(Vuetify); 12 | Vue.use(Notifications); 13 | 14 | new Vue({ 15 | vuetify: new Vuetify({}), 16 | router, 17 | store, 18 | render: h => h(App) 19 | }).$mount('#app'); 20 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | 4 | Vue.use(VueRouter); 5 | 6 | const routes = [ 7 | { 8 | path: '*', 9 | name: 'Home', 10 | component: () => import('@/views/Home.vue') 11 | } 12 | ]; 13 | 14 | const router = new VueRouter({ 15 | mode: 'history', 16 | base: process.env.BASE_URL, 17 | routes 18 | }); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import globalAxios from 'axios'; 4 | 5 | import { apiUrl } from '@/config'; 6 | 7 | const axios = globalAxios.create({ 8 | baseURL: apiUrl 9 | }); 10 | 11 | Vue.use(Vuex); 12 | 13 | export default new Vuex.Store({ 14 | state: () => ({ 15 | refreshSignal: true, 16 | period: null, 17 | redisLoading: false 18 | }), 19 | 20 | getters: { 21 | refreshSignal: state => state.refreshSignal, 22 | getPeriod: state => state.period, 23 | getRedisLoading: state => state.redisLoading 24 | }, 25 | 26 | mutations: { 27 | NEGATE_REFRESH_SIGNAL: state => (state.refreshSignal = !state.refreshSignal), 28 | SET_PERIOD: (state, period) => (state.period = period), 29 | SET_REDIS_LOADING: (state, loading) => (state.redisLoading = loading) 30 | }, 31 | 32 | actions: { 33 | async flush() { 34 | await axios.delete('/api/flush'); 35 | }, 36 | 37 | async reset() { 38 | await axios.post('/api/reset'); 39 | }, 40 | 41 | async fetchCohort(vuexContext, params) { 42 | const { data } = await axios.get('/api/customers/cohort', { params }); 43 | 44 | return data; 45 | }, 46 | 47 | async fetchProducts(vuexContext, params) { 48 | const { data } = await axios.get('/api/customers/products', { params }); 49 | 50 | return data; 51 | }, 52 | 53 | async fetchRetention(vuexContext, params) { 54 | const { data } = await axios.get('/api/customers/retention', { params }); 55 | 56 | return data; 57 | }, 58 | 59 | async fetchSales(vuexContext, params) { 60 | const { data } = await axios.get('/api/sales', { params }); 61 | 62 | return data; 63 | }, 64 | 65 | async fetchTraffic(vuexContext, params) { 66 | const { data } = await axios.get('/api/traffic', { params }); 67 | 68 | return data; 69 | }, 70 | 71 | async fetchTrend(vuexContext, params) { 72 | const { data } = await axios.get('/api/traffic/trend', { params }); 73 | 74 | return data; 75 | }, 76 | 77 | updatePeriod({ commit }, period) { 78 | commit('SET_PERIOD', period); 79 | }, 80 | 81 | saveData(vuexContext, data) { 82 | return axios.post('/api/data', data); 83 | } 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /client/src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url('../assets/RedisLabsIllustration.svg'); 3 | background-color:#F8F8FB; 4 | background-repeat: no-repeat; 5 | background-size: 340px; 6 | background-position: right top; 7 | } 8 | 9 | #app { 10 | background:none; 11 | 12 | * { 13 | word-break: normal !important; 14 | } 15 | } 16 | 17 | .subheader { 18 | max-width: 800px; 19 | font-size: 110%; 20 | } 21 | 22 | .card { 23 | @media screen and (min-width: 1264px) { 24 | height: 100%; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 44 | -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transpileDependencies: ['vuetify'] 3 | }; 4 | -------------------------------------------------------------------------------- /docs/YTThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/docs/YTThumbnail.png -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile 4 | -------------------------------------------------------------------------------- /images/app_preview_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/images/app_preview_image.png -------------------------------------------------------------------------------- /marketplace.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": "Redis Analytics Bitmaps demo", 3 | "description": "Showcases how to implement analytics system using Redis Bitmaps (and other data types) in NodeJS", 4 | "type": "Building Block", 5 | "contributed_by": "Redis", 6 | "repo_url": "https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs", 7 | "preview_image_url": "https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/master/images/app_preview_image.png", 8 | "download_url": "https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/archive/main.zip", 9 | "hosted_url": "", 10 | "quick_deploy": "true", 11 | "deploy_buttons": [ 12 | { 13 | "heroku": "https://heroku.com/deploy?template=https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs" 14 | }, 15 | { 16 | "vercel": "https://vercel.com/new/git/external?repository-url=https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs&env=REDIS_ENDPOINT_URI,REDIS_PASSWORD&envDescription=REDIS_ENDPOINT_URI%20is%20required%20at%20least%20to%20connect%20to%20Redis%20clouding%20server" 17 | }, 18 | { 19 | "Google": "https://deploy.cloud.run/?git_repo=https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs.git" 20 | } 21 | ], 22 | "language": [ 23 | "JavaScript" 24 | ], 25 | "redis_commands": [ 26 | "SETBIT", 27 | "BITCOUNT", 28 | "GET", 29 | "SET", 30 | "INCR", 31 | "SADD", 32 | "SMEMBERS", 33 | "SINTER", 34 | "SCAN", 35 | "DEL" 36 | ], 37 | "redis_use_cases": [ 38 | "Bitmap" 39 | ], 40 | "redis_features": [], 41 | "app_image_urls": [ 42 | "https://github.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/blob/main/preview.png?raw=true" 43 | ], 44 | "youtube_url": "https://www.youtube.com/watch?v=Ugym4yUeIhA", 45 | "special_tags": [], 46 | "verticals": [ 47 | "Others" 48 | ], 49 | "markdown": "https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/main/README.md" 50 | } -------------------------------------------------------------------------------- /preview-2-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/preview-2-min.png -------------------------------------------------------------------------------- /preview-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/preview-2.png -------------------------------------------------------------------------------- /preview-3-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/preview-3-min.png -------------------------------------------------------------------------------- /preview-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/preview-3.png -------------------------------------------------------------------------------- /preview-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/preview-min.png -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-developer/basic-analytics-dashboard-redis-bitmaps-nodejs/5fafe0fc6890d853a85df49c7c5d3770d393800d/preview.png -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | 3 | # Host and a port. Can be with `redis://` or without. 4 | # Host and a port encoded in redis uri take precedence over other environment variable. 5 | # preferable 6 | REDIS_ENDPOINT_URI=redis://127.0.0.1:6379 7 | 8 | # Or you can set it here (ie. for docker development) 9 | REDIS_HOST=127.0.0.1 10 | REDIS_PORT=6379 11 | 12 | # You can set password here 13 | REDIS_PASSWORD= 14 | 15 | COMPOSE_PROJECT_NAME=redis-analytics-bitmaps 16 | -------------------------------------------------------------------------------- /server/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | tabWidth: 4, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'none', 8 | bracketSpacing: true, 9 | jsxBracketSameLine: false, 10 | proseWrap: 'never', 11 | htmlWhitespaceSensitivity: 'strict', 12 | endOfLine: 'lf', 13 | arrowParens: 'avoid' 14 | }; 15 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Redis Analytics Bitmaps API 2 | 3 | ## Prerequisites 4 | 5 | - Node - v12.19.0 6 | - NPM - v6.14.8 7 | - Docker - v19.03.13 (optional) 8 | 9 | ## Development 10 | 11 | ``` 12 | # copy file and set proper data inside 13 | cp .env.example .env 14 | 15 | # install dependencies 16 | npm install 17 | 18 | # run docker compose or install redis manually 19 | docker network create global 20 | docker-compose up -d --build 21 | 22 | npm run dev 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /server/docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | redis: 5 | image: redislabs/rejson:latest 6 | container_name: redis.redisanalyticsbitmaps.docker 7 | restart: unless-stopped 8 | ports: 9 | - '127.0.0.1:${REDIS_PORT}:6379' 10 | networks: 11 | - global 12 | 13 | networks: 14 | global: 15 | external: true 16 | -------------------------------------------------------------------------------- /server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | redis: 5 | image: redislabs/rejson:latest 6 | container_name: redis.redisanalyticsbitmaps.docker 7 | restart: unless-stopped 8 | ports: 9 | - '127.0.0.1:${REDIS_PORT}:6379' 10 | networks: 11 | - global 12 | links: 13 | - redis-commander 14 | 15 | redis-commander: 16 | container_name: redis-commander.redisanalyticsbitmaps.docker 17 | image: rediscommander/redis-commander:latest 18 | restart: always 19 | environment: 20 | - REDIS_HOST=redis 21 | ports: 22 | - '127.0.0.1:8081:8081' 23 | networks: 24 | - global 25 | networks: 26 | global: 27 | external: true 28 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-analytics-bitmaps-api", 3 | "version": "1.0.0", 4 | "description": "This app demonstrates how we can use Redis bitmap methods to gather analytic statistics. It utilizes such methods as SETBIT, BITCOUNT and BITOP.", 5 | "keywords": [ 6 | "NodeJS", 7 | "ExpressJS", 8 | "Redis", 9 | "Analytics", 10 | "Statistics", 11 | "SETBIT", 12 | "BITCOUNT", 13 | "BITOP" 14 | ], 15 | "main": "./src/index.js", 16 | "scripts": { 17 | "start": "node ./src/index.js", 18 | "dev": "nodemon ./src/index.js", 19 | "prettier-fix": "prettier --write .", 20 | "generateData": "node ./src/scripts/sample.js" 21 | }, 22 | "author": "RemoteCraftsmen.com", 23 | "license": "MIT", 24 | "dependencies": { 25 | "body-parser": "^1.19.0", 26 | "cors": "^2.8.5", 27 | "dayjs": "^1.10.2", 28 | "dotenv": "^8.2.0", 29 | "express": "^4.17.1", 30 | "express-async-errors": "^3.1.1", 31 | "getenv": "^1.0.0", 32 | "helmet": "^4.3.1", 33 | "http-status-codes": "^2.1.4", 34 | "node-dependency-injection": "^2.6.8", 35 | "redis": "^3.0.2" 36 | }, 37 | "devDependencies": { 38 | "faker": "^5.4.0", 39 | "nodemon": "^2.0.7", 40 | "prettier": "^2.2.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "RedisAnaBitApp", 5 | "script": "./src/index.js", 6 | "watch": false, 7 | "env_production": { 8 | "NODE_ENV": "production" 9 | }, 10 | "exp_backoff_restart_delay": 250, 11 | "max_restarts": 10, 12 | "min_uptime": 5000, 13 | "max_memory_restart": "150M", 14 | "log_date_format": "YYYY-MM-DD HH:mm" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /server/src/config/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const sanitizeRedisUrl = url => url.replace(/^(redis\:\/\/)/, ''); 4 | 5 | const { PORT, REDIS_HOST, REDIS_PORT, REDIS_ENDPOINT_URI, REDIS_PASSWORD } = process.env; 6 | 7 | const redisEndpointUri = REDIS_ENDPOINT_URI 8 | ? sanitizeRedisUrl(REDIS_ENDPOINT_URI) 9 | : `${sanitizeRedisUrl(REDIS_HOST)}:${REDIS_PORT}`; 10 | 11 | module.exports = { 12 | server: { 13 | port: PORT || 3000 14 | }, 15 | redis: { 16 | uri: `redis://${redisEndpointUri}`, 17 | password: REDIS_PASSWORD || undefined 18 | }, 19 | analytics: { 20 | prefix: 'rab' 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /server/src/controllers/Customers/CohortShowController.js: -------------------------------------------------------------------------------- 1 | const { BITMAP } = require('../../services/event/types'); 2 | 3 | class CustomersCohortShowController { 4 | constructor(analyzerService) { 5 | this.analyzerService = analyzerService; 6 | } 7 | 8 | async invoke(req, res) { 9 | const { period = '2015-12' } = req.query; 10 | 11 | const registerThenBought = await this.analyzerService.analyze(BITMAP, period, { customName: 'cohort-buy' }); 12 | const register = await this.analyzerService.analyze(BITMAP, period, { action: 'register' }); 13 | const buy = await this.analyzerService.analyze(BITMAP, period, { action: 'buy' }); 14 | const dropoff = 100 - ((registerThenBought / register) * 100).toFixed(2); 15 | 16 | return res.send({ 17 | registerThenBought, 18 | register, 19 | buy, 20 | dropoff 21 | }); 22 | } 23 | } 24 | 25 | module.exports = CustomersCohortShowController; 26 | -------------------------------------------------------------------------------- /server/src/controllers/Customers/ProductsIndexController.js: -------------------------------------------------------------------------------- 1 | const { SET, SET_AND } = require('../../services/event/types'); 2 | 3 | class CustomersProductIndexController { 4 | constructor(analyzerService) { 5 | this.analyzerService = analyzerService; 6 | } 7 | 8 | async invoke(req, res) { 9 | const { filter, and, period = '2015-12' } = req.query; 10 | 11 | const defaultFilter = { products: ['product1', 'product2', 'product3'] }; 12 | 13 | const { products = [] } = filter ? JSON.parse(filter) : defaultFilter; 14 | 15 | const [firstProduct, secondProduct] = and ? JSON.parse(and) : []; 16 | 17 | const results = []; 18 | 19 | for (const product of products) { 20 | const boughtBy = await this.analyzerService 21 | .analyze(SET, period, { action: 'buy', page: product }) 22 | .then(usersIds => usersIds.map(userId => `User${parseInt(userId) + 1}`)); 23 | 24 | results.push({ 25 | type: 'product', 26 | value: product, 27 | boughtBy 28 | }); 29 | } 30 | 31 | if (firstProduct && secondProduct) { 32 | const boughtBy = await this.analyzerService 33 | .analyze(SET_AND, period, { 34 | first: { action: 'buy', page: firstProduct }, 35 | second: { action: 'buy', page: secondProduct } 36 | }) 37 | .then(usersIds => usersIds.map(userId => `User${parseInt(userId) + 1}`)); 38 | 39 | results.push({ 40 | type: 'products_join', 41 | value: `${firstProduct}_${secondProduct}`, 42 | boughtBy 43 | }); 44 | } 45 | 46 | return res.send(results); 47 | } 48 | } 49 | 50 | module.exports = CustomersProductIndexController; 51 | -------------------------------------------------------------------------------- /server/src/controllers/Customers/RetentionShowController.js: -------------------------------------------------------------------------------- 1 | const { SET } = require('../../services/event/types'); 2 | 3 | class CustomersRetentionShowController { 4 | constructor(analyzerService) { 5 | this.analyzerService = analyzerService; 6 | } 7 | 8 | async invoke(req, res) { 9 | const { period = '2015-12' } = req.query; 10 | 11 | const users = await this.analyzerService 12 | .analyze(SET, period, { customName: 'retention-buy' }) 13 | .then(usersIds => usersIds.map(userId => `User${parseInt(userId) + 1}`)); 14 | 15 | return res.send(users); 16 | } 17 | } 18 | 19 | module.exports = CustomersRetentionShowController; 20 | -------------------------------------------------------------------------------- /server/src/controllers/Data/StoreController.js: -------------------------------------------------------------------------------- 1 | const { StatusCodes } = require('http-status-codes'); 2 | 3 | class DataStoreController { 4 | constructor(storeDataService) { 5 | this.storeDataService = storeDataService; 6 | } 7 | 8 | async invoke(req, res) { 9 | await this.storeDataService.store(req.body); 10 | 11 | return res.sendStatus(StatusCodes.CREATED); 12 | } 13 | } 14 | 15 | module.exports = DataStoreController; 16 | -------------------------------------------------------------------------------- /server/src/controllers/FlushController.js: -------------------------------------------------------------------------------- 1 | const { StatusCodes } = require('http-status-codes'); 2 | 3 | class FlushController { 4 | constructor(redisService) { 5 | this.redisService = redisService; 6 | } 7 | 8 | async invoke(req, res) { 9 | await this.redisService.flush(); 10 | 11 | return res.sendStatus(StatusCodes.NO_CONTENT); 12 | } 13 | } 14 | 15 | module.exports = FlushController; 16 | -------------------------------------------------------------------------------- /server/src/controllers/ResetController.js: -------------------------------------------------------------------------------- 1 | const { StatusCodes } = require('http-status-codes'); 2 | 3 | class ResetController { 4 | constructor(sampleDataService) { 5 | this.sampleDataService = sampleDataService; 6 | } 7 | 8 | async invoke(req, res) { 9 | await this.sampleDataService.generate(); 10 | 11 | return res.sendStatus(StatusCodes.NO_CONTENT); 12 | } 13 | } 14 | 15 | module.exports = ResetController; 16 | -------------------------------------------------------------------------------- /server/src/controllers/Sales/IndexController.js: -------------------------------------------------------------------------------- 1 | const { COUNT } = require('../../services/event/types'); 2 | 3 | class SalesIndexController { 4 | constructor(analyzerService) { 5 | this.analyzerService = analyzerService; 6 | } 7 | 8 | async invoke(req, res) { 9 | const { filter, period = '2015-12' } = req.query; 10 | 11 | const defaultFilter = { products: ['product1', 'product2', 'product3'], total: true }; 12 | 13 | const { products = [], total = false } = filter ? JSON.parse(filter) : defaultFilter; 14 | 15 | const results = []; 16 | 17 | if (total) { 18 | const addedToCart = await this.analyzerService.analyze(COUNT, period, { action: 'addToCart' }); 19 | const bought = await this.analyzerService.analyze(COUNT, period, { action: 'buy' }); 20 | 21 | results.push({ 22 | type: 'total', 23 | addedToCart, 24 | bought 25 | }); 26 | } 27 | 28 | for (const product of products) { 29 | const addedToCart = await this.analyzerService.analyze(COUNT, period, { 30 | action: 'addToCart', 31 | page: product 32 | }); 33 | 34 | const bought = await this.analyzerService.analyze(COUNT, period, { action: 'buy', page: product }); 35 | 36 | results.push({ 37 | type: 'product', 38 | value: product, 39 | addedToCart, 40 | bought 41 | }); 42 | } 43 | 44 | return res.send(results); 45 | } 46 | } 47 | 48 | module.exports = SalesIndexController; 49 | -------------------------------------------------------------------------------- /server/src/controllers/Traffic/IndexController.js: -------------------------------------------------------------------------------- 1 | const { BITMAP } = require('../../services/event/types'); 2 | 3 | class TrafficIndexController { 4 | constructor(analyzerService) { 5 | this.analyzerService = analyzerService; 6 | } 7 | 8 | async invoke(req, res) { 9 | const { filter, period = '2015-12' } = req.query; 10 | 11 | const defaultFilter = { 12 | sources: ['facebook', 'google', 'direct', 'email', 'referral', 'none'], 13 | pages: ['homepage', 'product1', 'product2', 'product3'], 14 | total: true 15 | }; 16 | 17 | const { sources = [], pages = [], total = false } = filter ? JSON.parse(filter) : defaultFilter; 18 | 19 | const results = []; 20 | 21 | if (total) { 22 | const count = await this.analyzerService.analyze(BITMAP, period, { customName: 'global' }); 23 | 24 | results.push({ 25 | count, 26 | type: 'total' 27 | }); 28 | } 29 | 30 | for (const source of sources) { 31 | const count = await this.analyzerService.analyze(BITMAP, period, { source }); 32 | 33 | results.push({ 34 | count, 35 | type: 'source', 36 | value: source 37 | }); 38 | } 39 | 40 | for (const page of pages) { 41 | const count = await this.analyzerService.analyze(BITMAP, period, { 42 | action: 'visit', 43 | page 44 | }); 45 | 46 | results.push({ 47 | count, 48 | type: 'page', 49 | value: page 50 | }); 51 | } 52 | 53 | return res.send(results); 54 | } 55 | } 56 | 57 | module.exports = TrafficIndexController; 58 | -------------------------------------------------------------------------------- /server/src/controllers/Traffic/TrendIndexController.js: -------------------------------------------------------------------------------- 1 | const { BITMAP } = require('../../services/event/types'); 2 | 3 | class TrafficTrendIndexController { 4 | constructor(periodService, analyzerService) { 5 | this.periodService = periodService; 6 | this.analyzerService = analyzerService; 7 | } 8 | 9 | async invoke(req, res) { 10 | const { filter, period } = req.query; 11 | 12 | const defaultFilter = { 13 | sources: ['facebook', 'google', 'direct', 'email', 'referral', 'none'], 14 | pages: ['homepage', 'product1', 'product2', 'product3'] 15 | }; 16 | 17 | const defaultPeriod = { from: '2015-12-01', to: '2015-12-31' }; 18 | 19 | const { sources = [], pages = [] } = filter ? JSON.parse(filter) : defaultFilter; 20 | 21 | const { from = '2015-12-01', to = '2015-12-31' } = period ? JSON.parse(period) : defaultPeriod; 22 | 23 | const dates = this.periodService.getRangeOfDates(from, to, 'day'); 24 | 25 | const results = []; 26 | 27 | for (const date of dates) { 28 | const dateFormatted = date.format('YYYY-MM-DD'); 29 | 30 | for (const source of sources) { 31 | const count = await this.analyzerService.analyze(BITMAP, dateFormatted, { 32 | source 33 | }); 34 | 35 | results.push({ 36 | count, 37 | date: dateFormatted, 38 | type: 'source', 39 | value: source 40 | }); 41 | } 42 | 43 | for (const page of pages) { 44 | const count = await this.analyzerService.analyze(BITMAP, dateFormatted, { 45 | action: 'visit', 46 | page 47 | }); 48 | 49 | results.push({ 50 | count, 51 | date: dateFormatted, 52 | type: 'page', 53 | value: page 54 | }); 55 | } 56 | } 57 | 58 | return res.send(results); 59 | } 60 | } 61 | 62 | module.exports = TrafficTrendIndexController; 63 | -------------------------------------------------------------------------------- /server/src/di/controllers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | services: { 3 | 'controllers.flush': { 4 | class: 'controllers/FlushController', 5 | arguments: ['@services.redis'] 6 | }, 7 | 'controllers.reset': { 8 | class: 'controllers/ResetController', 9 | arguments: ['@services.data.sample'] 10 | }, 11 | 'controllers.data.storeController': { 12 | class: 'controllers/Data/StoreController', 13 | arguments: ['@services.data.store'] 14 | }, 15 | 'controllers.customers.cohort.showController': { 16 | class: 'controllers/Customers/CohortShowController', 17 | arguments: ['@services.event.analyzer'] 18 | }, 19 | 'controllers.customers.products.indexController': { 20 | class: 'controllers/Customers/ProductsIndexController', 21 | arguments: ['@services.event.analyzer'] 22 | }, 23 | 'controllers.customers.retention.showController': { 24 | class: 'controllers/Customers/RetentionShowController', 25 | arguments: ['@services.event.analyzer'] 26 | }, 27 | 'controllers.sales.indexController': { 28 | class: 'controllers/Sales/IndexController', 29 | arguments: ['@services.event.analyzer'] 30 | }, 31 | 'controllers.traffic.indexController': { 32 | class: 'controllers/Traffic/IndexController', 33 | arguments: ['@services.event.analyzer'] 34 | }, 35 | 'controllers.traffic.trend.indexController': { 36 | class: 'controllers/Traffic/TrendIndexController', 37 | arguments: ['@services.period', '@services.event.analyzer'] 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /server/src/di/index.js: -------------------------------------------------------------------------------- 1 | const { ContainerBuilder, JsFileLoader } = require('node-dependency-injection'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const srcDir = path.join(__dirname, '..'); 6 | 7 | const container = new ContainerBuilder(true, srcDir); 8 | const loader = new JsFileLoader(container); 9 | 10 | const loadFiles = (currentDirectory = '') => { 11 | const currentLocation = path.join(__dirname, currentDirectory); 12 | const allFiles = fs.readdirSync(currentLocation); 13 | const directories = allFiles.filter(file => !file.includes('.')); 14 | const files = allFiles.filter(file => file.includes('.')); 15 | 16 | for (const file of files) { 17 | const fileName = file.split('.')[0]; 18 | 19 | if (fileName === 'index' && !currentDirectory) { 20 | continue; 21 | } 22 | 23 | loader.load(path.join(currentLocation, `${fileName}.js`)); 24 | } 25 | 26 | for (const directory of directories) { 27 | loadFiles(path.join(currentDirectory, directory)); 28 | } 29 | }; 30 | 31 | loadFiles(); 32 | 33 | module.exports = container; 34 | -------------------------------------------------------------------------------- /server/src/di/services.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | 3 | module.exports = { 4 | parameters: { 5 | 'config.redis': config.redis 6 | }, 7 | services: { 8 | redis: { 9 | factory: { 10 | class: 'services/redis/RedisClientFactory', 11 | method: 'create' 12 | }, 13 | arguments: ['%config.redis%'] 14 | }, 15 | 'services.redis': { 16 | class: 'services/redis/RedisService', 17 | arguments: ['@redis'] 18 | }, 19 | 'services.event.keyGenerator': { 20 | class: 'services/event/KeyGeneratorService', 21 | arguments: [config.analytics.prefix] 22 | }, 23 | 'services.event.analyzer': { 24 | class: 'services/event/AnalyzerService', 25 | arguments: ['@services.redis', '@services.event.keyGenerator'] 26 | }, 27 | 'services.event.timeSpan': { 28 | class: 'services/event/TimeSpanService', 29 | arguments: ['%dayjs'] 30 | }, 31 | 'services.event.event': { 32 | class: 'services/event/EventService', 33 | arguments: ['@services.redis', '@services.event.timeSpan', '@services.event.keyGenerator'] 34 | }, 35 | 'services.data.store': { 36 | class: 'services/data/StoreDataService', 37 | arguments: ['@services.event.event', '@services.event.analyzer', '@services.event.timeSpan'] 38 | }, 39 | 'services.data.sample': { 40 | class: 'services/data/SampleDataService', 41 | arguments: ['@services.data.store', '@services.redis'] 42 | }, 43 | 'services.period': { 44 | class: 'services/PeriodService', 45 | arguments: ['%dayjs'] 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const helmet = require('helmet'); 3 | const cors = require('cors'); 4 | const bodyParser = require('body-parser'); 5 | const path = require('path'); 6 | const di = require('./di'); 7 | const errorHandler = require('./plugins/errorHandler'); 8 | const config = require('./config'); 9 | 10 | const app = express(); 11 | 12 | require('express-async-errors'); 13 | 14 | app.use(helmet()); 15 | app.use( 16 | cors({ 17 | origin(origin, callback) { 18 | callback(null, true); 19 | }, 20 | credentials: true 21 | }) 22 | ); 23 | app.use(bodyParser.json()); 24 | 25 | const router = require('./routes')(di); 26 | 27 | app.use('/', express.static(path.join(__dirname, '../../client-dist'))); 28 | 29 | app.use('/api', router); 30 | 31 | app.use(errorHandler); 32 | 33 | const { port } = config.server; 34 | 35 | di.get('services.data.sample') 36 | .generate() 37 | .then(() => { 38 | console.log('Sample data loaded...'); 39 | 40 | app.listen(port, () => { 41 | console.log(`Server is listening on port ${port}...`); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /server/src/plugins/errorHandler.js: -------------------------------------------------------------------------------- 1 | const { StatusCodes } = require('http-status-codes'); 2 | 3 | module.exports = (err, req, res, next) => { 4 | if (err instanceof SyntaxError) { 5 | return res.sendStatus(StatusCodes.BAD_REQUEST); 6 | } 7 | 8 | console.error(err); 9 | 10 | return res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR); 11 | }; 12 | -------------------------------------------------------------------------------- /server/src/routes/customers.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | module.exports = di => { 5 | const cohortShowController = di.get('controllers.customers.cohort.showController'); 6 | const productsIndexController = di.get('controllers.customers.products.indexController'); 7 | const retentionShowController = di.get('controllers.customers.retention.showController'); 8 | 9 | router.get('/cohort', (...args) => cohortShowController.invoke(...args)); 10 | router.get('/products', (...args) => productsIndexController.invoke(...args)); 11 | router.get('/retention', (...args) => retentionShowController.invoke(...args)); 12 | 13 | return router; 14 | }; 15 | -------------------------------------------------------------------------------- /server/src/routes/data.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | module.exports = di => { 5 | const storeController = di.get('controllers.data.storeController'); 6 | 7 | router.post('/', (...args) => storeController.invoke(...args)); 8 | 9 | return router; 10 | }; 11 | -------------------------------------------------------------------------------- /server/src/routes/flush.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | module.exports = di => { 5 | const flushController = di.get('controllers.flush'); 6 | 7 | router.delete('/', (...args) => flushController.invoke(...args)); 8 | 9 | return router; 10 | }; 11 | -------------------------------------------------------------------------------- /server/src/routes/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | module.exports = di => { 6 | fs.readdirSync(__dirname).forEach(function (route) { 7 | route = route.split('.')[0]; 8 | 9 | if (route === 'index') { 10 | return; 11 | } 12 | 13 | router.use(`/${route}`, require(`./${route}`)(di)); 14 | }); 15 | 16 | return router; 17 | }; 18 | -------------------------------------------------------------------------------- /server/src/routes/reset.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | module.exports = di => { 5 | const resetController = di.get('controllers.reset'); 6 | 7 | router.post('/', (...args) => resetController.invoke(...args)); 8 | 9 | return router; 10 | }; 11 | -------------------------------------------------------------------------------- /server/src/routes/sales.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | module.exports = di => { 5 | const indexController = di.get('controllers.sales.indexController'); 6 | 7 | router.get('/', (...args) => indexController.invoke(...args)); 8 | 9 | return router; 10 | }; 11 | -------------------------------------------------------------------------------- /server/src/routes/traffic.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | module.exports = di => { 5 | const indexController = di.get('controllers.traffic.indexController'); 6 | const trendIndexController = di.get('controllers.traffic.trend.indexController'); 7 | 8 | router.get('/', (...args) => indexController.invoke(...args)); 9 | router.get('/trend', (...args) => trendIndexController.invoke(...args)); 10 | 11 | return router; 12 | }; 13 | -------------------------------------------------------------------------------- /server/src/scripts/sample.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const faker = require('faker'); 4 | const di = require('../di'); 5 | 6 | const periodService = di.get('services.period'); 7 | 8 | const samplePath = path.join(__dirname, '..', 'sample.json'); 9 | 10 | const actionHelpers = { 11 | visit: (data, date, source, userId, page) => { 12 | data.push({ date, source, userId, actionParams: { action: 'visit', page } }); 13 | }, 14 | 15 | register: (data, date, source, userId) => { 16 | data.push({ date, source, userId, actionParams: { action: 'register' } }); 17 | }, 18 | 19 | addToCart: (data, date, source, userId, page) => { 20 | data.push({ date, source, userId, actionParams: { action: 'addToCart', page } }); 21 | }, 22 | 23 | registerAndBuy: (data, date, source, userId, page) => { 24 | actionHelpers.register(data, date, source, userId); 25 | 26 | actionHelpers.addToCart(data, date, source, userId, page); 27 | 28 | data.push({ date, source, userId, actionParams: { action: 'buy', page } }); 29 | }, 30 | 31 | getPage: action => { 32 | if (action === 'register') { 33 | return; 34 | } 35 | 36 | if (action === 'visit') { 37 | return faker.random.arrayElement(['product1', 'product2', 'product3', 'homepage']); 38 | } 39 | 40 | return faker.random.arrayElement(['product1', 'product2', 'product3']); 41 | } 42 | }; 43 | 44 | const actions = ['visit', 'register', 'addToCart', 'registerAndBuy']; 45 | 46 | const dates = periodService.getRangeOfDates('2015-12-01', '2015-12-31', 'day').map(date => date.format('YYYY-MM-DD')); 47 | 48 | const sources = ['google', 'facebook', 'email', 'direct', 'referral', 'none']; 49 | 50 | const users = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; 51 | 52 | const maxUsersPerDay = 8; 53 | 54 | const data = []; 55 | 56 | for (const date of dates) { 57 | const _users = faker.random.arrayElements(users, faker.random.number(maxUsersPerDay - 1) + 1); 58 | 59 | for (const userId of _users) { 60 | const action = faker.random.arrayElement(actions); 61 | const source = faker.random.arrayElement(sources); 62 | const page = actionHelpers.getPage(action); 63 | 64 | actionHelpers[action](data, date, source, userId, page); 65 | } 66 | } 67 | 68 | fs.writeFileSync(samplePath, JSON.stringify(data, null, 4).concat('\n')); 69 | -------------------------------------------------------------------------------- /server/src/services/PeriodService.js: -------------------------------------------------------------------------------- 1 | class PeriodService { 2 | constructor(dayjs) { 3 | this.dayjs = dayjs; 4 | } 5 | 6 | getRangeOfDates(start, end, key, arr = []) { 7 | const _start = this.dayjs(start); 8 | 9 | if (!arr.length) { 10 | arr.push(_start); 11 | } 12 | 13 | if (_start.isAfter(end)) { 14 | throw new Error('start must precede end'); 15 | } 16 | 17 | const next = _start.add(1, key).startOf(key); 18 | 19 | if (next.isAfter(end, key)) { 20 | return arr; 21 | } 22 | 23 | return this.getRangeOfDates(next, end, key, arr.concat(next)); 24 | } 25 | } 26 | 27 | module.exports = PeriodService; 28 | -------------------------------------------------------------------------------- /server/src/services/data/SampleDataService.js: -------------------------------------------------------------------------------- 1 | class SampleDataService { 2 | constructor(storeDataService, redisService) { 3 | this.storeDataService = storeDataService; 4 | this.redisService = redisService; 5 | } 6 | 7 | async generate() { 8 | try { 9 | await this.redisService.flush(); 10 | 11 | const events = require('../../sample.json'); 12 | 13 | for (const event of events) { 14 | await this.storeDataService.store(event); 15 | } 16 | } catch (err) { 17 | console.error(err); 18 | } 19 | } 20 | } 21 | 22 | module.exports = SampleDataService; 23 | -------------------------------------------------------------------------------- /server/src/services/data/StoreDataService.js: -------------------------------------------------------------------------------- 1 | const { COUNT, SET, BITMAP } = require('../event/types'); 2 | 3 | class StoreDataService { 4 | constructor(eventService, analyzerService, timeSpanService) { 5 | this.eventService = eventService; 6 | this.analyzerService = analyzerService; 7 | this.timeSpanService = timeSpanService; 8 | } 9 | 10 | async store({ userId, date, actionParams, source }) { 11 | if (actionParams.action === 'buy') { 12 | const monthRegisterCount = await this.analyzerService.analyze(COUNT, this.timeSpanService.month(date), { 13 | action: 'register', 14 | userId 15 | }); 16 | 17 | const todayBuyCount = await this.analyzerService.analyze(COUNT, this.timeSpanService.day(date), { 18 | action: 'buy', 19 | userId 20 | }); 21 | 22 | const anytimeBuyCount = await this.analyzerService.analyze(COUNT, this.timeSpanService.anytime(), { 23 | action: 'buy', 24 | userId 25 | }); 26 | 27 | if (anytimeBuyCount > todayBuyCount) { 28 | await this.eventService.store(SET, 'retention-buy', userId, this.timeSpanService.all(date)); 29 | } 30 | 31 | if (monthRegisterCount > 0) { 32 | await this.eventService.store(BITMAP, 'cohort-buy', userId, this.timeSpanService.all(date)); 33 | } 34 | } 35 | 36 | await this.eventService.storeAll(userId, date, { source, ...actionParams }); 37 | } 38 | } 39 | 40 | module.exports = StoreDataService; 41 | -------------------------------------------------------------------------------- /server/src/services/event/AnalyzerService.js: -------------------------------------------------------------------------------- 1 | const { BITMAP, COUNT, SET, SET_AND } = require('./types'); 2 | 3 | class AnalyzerService { 4 | constructor(redisService, keyGeneratorService) { 5 | this.redisService = redisService; 6 | this.keyGeneratorService = keyGeneratorService; 7 | } 8 | 9 | analyze(type, timeSpan, args) { 10 | const key = this.keyGeneratorService.generate({ type, timeSpan, ...args }); 11 | 12 | switch (type) { 13 | case BITMAP: 14 | return this.redisService.countBit(key); 15 | 16 | case COUNT: 17 | return this.redisService.get(key).then(value => (value ? parseInt(value) : 0)); 18 | 19 | case SET: 20 | return this.redisService.getSetValues(key); 21 | 22 | case SET_AND: 23 | return this.redisService.getSetIntersection( 24 | this.keyGeneratorService.generate({ 25 | ...args.first, 26 | type: SET, 27 | timeSpan 28 | }), 29 | this.keyGeneratorService.generate({ 30 | ...args.second, 31 | type: SET, 32 | timeSpan 33 | }) 34 | ); 35 | } 36 | } 37 | } 38 | 39 | module.exports = AnalyzerService; 40 | -------------------------------------------------------------------------------- /server/src/services/event/EventService.js: -------------------------------------------------------------------------------- 1 | const { BITMAP, COUNT, SET } = require('./types'); 2 | 3 | class EventService { 4 | constructor(redisService, timeSpanService, keyGeneratorService) { 5 | this.redisService = redisService; 6 | this.timeSpanService = timeSpanService; 7 | this.keyGeneratorService = keyGeneratorService; 8 | } 9 | 10 | storeBitmap(key, userId) { 11 | return this.redisService.setBit(key, userId, 1); 12 | } 13 | 14 | storeCount(key) { 15 | return this.redisService.increment(key); 16 | } 17 | 18 | storeSet(key, userId) { 19 | return this.redisService.addToSet(key, userId); 20 | } 21 | 22 | get scopes() { 23 | return [ 24 | ({ source }) => { 25 | return { source }; 26 | }, 27 | 28 | ({ action }) => { 29 | return { action }; 30 | }, 31 | 32 | ({ source, action }) => { 33 | return { action, source }; 34 | }, 35 | 36 | ({ action, page }) => { 37 | return { action, page }; 38 | }, 39 | 40 | ({ userId, action }) => { 41 | return { userId, action }; 42 | }, 43 | 44 | () => { 45 | return { customName: 'global' }; 46 | } 47 | ]; 48 | } 49 | 50 | async storeAll(userId, date, args = {}) { 51 | const timeSpans = this.timeSpanService.all(date); 52 | 53 | for (const timeSpan of timeSpans) { 54 | for (const scope of this.scopes) { 55 | const scopedArgs = scope({ ...args, userId }); 56 | 57 | await this.storeCount(this.keyGeneratorService.generate({ type: COUNT, timeSpan, ...scopedArgs })); 58 | 59 | if (scopedArgs.hasOwnProperty('userId')) { 60 | continue; 61 | } 62 | 63 | await this.storeBitmap( 64 | this.keyGeneratorService.generate({ type: BITMAP, timeSpan, ...scopedArgs }), 65 | userId 66 | ); 67 | 68 | await this.storeSet(this.keyGeneratorService.generate({ type: SET, timeSpan, ...scopedArgs }), userId); 69 | } 70 | } 71 | } 72 | 73 | async store(type, customName, userId, timeSpans = []) { 74 | for (const timeSpan of timeSpans) { 75 | const key = this.keyGeneratorService.generate({ type, customName, timeSpan }); 76 | 77 | switch (type) { 78 | case BITMAP: 79 | await this.storeBitmap(key, userId); 80 | break; 81 | case COUNT: 82 | await this.storeCount(key); 83 | break; 84 | case SET: 85 | await this.storeSet(key, userId); 86 | break; 87 | } 88 | } 89 | } 90 | } 91 | 92 | module.exports = EventService; 93 | -------------------------------------------------------------------------------- /server/src/services/event/KeyGeneratorService.js: -------------------------------------------------------------------------------- 1 | class KeyGeneratorService { 2 | constructor(prefix) { 3 | this.prefix = prefix || 'analytics'; 4 | } 5 | 6 | generate({ type, customName, userId, source, action, page, timeSpan }) { 7 | let key = this.prefix; 8 | 9 | if (type) { 10 | key += `:${type}`; 11 | } 12 | 13 | if (customName) { 14 | key += `:custom:${customName}`; 15 | } 16 | 17 | if (userId) { 18 | key += `:user:${userId}`; 19 | } 20 | 21 | if (source) { 22 | key += `:source:${source}`; 23 | } 24 | 25 | if (action) { 26 | key += `:action:${action}`; 27 | } 28 | 29 | if (page && action !== 'register') { 30 | key += `:page:${page}`; 31 | } 32 | 33 | if (timeSpan) { 34 | key += `:timeSpan:${timeSpan}`; 35 | } 36 | 37 | return key; 38 | } 39 | } 40 | 41 | module.exports = KeyGeneratorService; 42 | -------------------------------------------------------------------------------- /server/src/services/event/TimeSpanService.js: -------------------------------------------------------------------------------- 1 | class TimeSpanService { 2 | constructor(dayjs) { 3 | this.dayjs = dayjs; 4 | } 5 | 6 | year(date) { 7 | const _date = this.dayjs(date); 8 | 9 | return _date.year(); 10 | } 11 | 12 | month(date) { 13 | const _date = this.dayjs(date); 14 | 15 | return `${_date.year()}-${(_date.month() + 1).toString().padStart(2, '0')}`; 16 | } 17 | 18 | day(date) { 19 | const _date = this.dayjs(date); 20 | 21 | return _date.format('YYYY-MM-DD'); 22 | } 23 | 24 | weekOfMonth(date) { 25 | const _date = this.dayjs(date); 26 | 27 | const week = Math.ceil(_date.date() / 7); 28 | 29 | return `${this.month(date)}/${week}`; 30 | } 31 | 32 | anytime() { 33 | return 'anytime'; 34 | } 35 | 36 | all(date) { 37 | return [this.year(date), this.month(date), this.day(date), this.weekOfMonth(date), this.anytime()]; 38 | } 39 | } 40 | 41 | module.exports = TimeSpanService; 42 | -------------------------------------------------------------------------------- /server/src/services/event/types.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | BITMAP: 'bitmap', 3 | SET: 'set', 4 | COUNT: 'count', 5 | SET_AND: 'set_and' 6 | }; 7 | -------------------------------------------------------------------------------- /server/src/services/redis/RedisClientFactory.js: -------------------------------------------------------------------------------- 1 | const redis = require('redis'); 2 | 3 | class RedisClientFactory { 4 | static create({ uri, password }) { 5 | return redis.createClient(uri, { password }); 6 | } 7 | } 8 | 9 | module.exports = RedisClientFactory; 10 | -------------------------------------------------------------------------------- /server/src/services/redis/RedisService.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util'); 2 | const { analytics } = require('../../config'); 3 | 4 | class RedisService { 5 | constructor(redis) { 6 | this.redis = redis; 7 | 8 | ['SETBIT', 'BITCOUNT', 'GET', 'SET', 'INCR', 'SADD', 'SMEMBERS', 'SINTER', 'DEL', 'SCAN'].forEach( 9 | method => (this.redis[method] = promisify(this.redis[method])) 10 | ); 11 | } 12 | 13 | get(key) { 14 | return this.redis.GET(key); 15 | } 16 | 17 | set(key, value) { 18 | if (!key || !value) { 19 | return; 20 | } 21 | 22 | return this.redis.SET(key, value); 23 | } 24 | 25 | setBit(key, bit, value) { 26 | return this.redis.SETBIT(key, bit, value); 27 | } 28 | 29 | countBit(key) { 30 | return this.redis.BITCOUNT(key); 31 | } 32 | 33 | increment(key) { 34 | return this.redis.INCR(key); 35 | } 36 | 37 | addToSet(key, member) { 38 | return this.redis.SADD(key, member); 39 | } 40 | 41 | getSetValues(key) { 42 | return this.redis.SMEMBERS(key); 43 | } 44 | 45 | getSetIntersection(key1, key2) { 46 | return this.redis.SINTER(key1, key2); 47 | } 48 | 49 | async flush() { 50 | const keys = await this.scan(`${analytics.prefix}:*`); 51 | 52 | for (const key of keys) { 53 | await this.redis.del(key); 54 | } 55 | } 56 | 57 | async scan(pattern) { 58 | let matchingKeysCount = 0; 59 | let keys = []; 60 | 61 | const recursiveScan = async (cursor = '0') => { 62 | const [newCursor, matchingKeys] = await this.redis.SCAN(cursor, 'MATCH', pattern); 63 | cursor = newCursor; 64 | 65 | matchingKeysCount += matchingKeys.length; 66 | keys = keys.concat(matchingKeys); 67 | 68 | if (cursor === '0') { 69 | return keys; 70 | } else { 71 | return await recursiveScan(cursor); 72 | } 73 | }; 74 | 75 | return await recursiveScan(); 76 | } 77 | } 78 | 79 | module.exports = RedisService; 80 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [{ "src": "server/src/index.js", "use": "@now/node" }], 4 | "routes": [{ "src": "(.*)", "dest": "server/src/index.js" }] 5 | } 6 | --------------------------------------------------------------------------------