├── .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\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 Abandoned Cart\n Products: in cart vs bought\n\n \n \n \n \n\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 Share of Products bought\n\n \n \n \n \n\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 \n\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\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 Select date period\n\n \n \n \n \n\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 Flush Redis\n mdi-delete\n \n\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 Reset Data\n mdi-restart\n \n\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 Product Bought\n\n \n \n \n \n Total Product Bought | \n {{ totalProductBought }} | \n
\n \n \n \n \n\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 |
2 |
3 |
4 |
5 |
6 |
7 | mdi-chart-box-plus-outline
8 |
9 |
Analytics Demo (Via Redis Bitmaps)
10 |
11 |
12 |
13 |
14 |
15 |
16 | © Copyright 2021 | All Rights Reserved to Redis Labs
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheAbandonedCart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Abandoned Cart
4 | Products: in cart vs bought
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
76 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheCohort.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Cohort Analysis
4 |
5 |
6 | % of people who registered in December and then bought some product
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Name |
16 | Count |
17 |
18 |
19 |
20 |
21 | People who registered |
22 | {{ register }} |
23 |
24 |
25 | People who bought |
26 | {{ registerThenBought }} |
27 |
28 |
29 | Dropoff |
30 | {{ dropoff || '0' }}% |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheCustomerRetention.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
53 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheCustomersPerProduct.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
65 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheCustomersWithBothProducts.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
57 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheShareOfProductsBought.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Share of Products bought
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
77 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheTotalProductsBought.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Product Bought
4 |
5 |
6 |
7 |
8 |
9 | Total Product Bought |
10 | {{ totalProductBought }} |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
63 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheTrafficPerPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Traffic
4 |
5 |
6 | Total traffic
7 |
8 |
9 |
10 |
11 |
12 | Name
13 | |
14 |
15 | Visit count
16 | |
17 |
18 |
19 |
20 |
21 | Total traffic |
22 | {{ totalTraffic }} |
23 |
24 |
25 |
26 |
27 | Traffic per page
28 |
29 |
30 |
31 |
32 |
33 | Page name
34 | |
35 |
36 | Visit count
37 | |
38 |
39 |
40 |
41 |
42 | Homepage |
43 | {{ homepageTraffic }} |
44 |
45 |
46 | Product 1 Page |
47 | {{ product1pageTraffic }} |
48 |
49 |
50 | Product 2 Page |
51 | {{ product2pageTraffic }} |
52 |
53 |
54 | Product 3 Page |
55 | {{ product3pageTraffic }} |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
123 |
--------------------------------------------------------------------------------
/client/src/components/DataDisplay/TheTrafficPerSource.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Traffic per Source
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Source name
13 | |
14 |
15 | Visit count
16 | |
17 |
18 |
19 |
20 |
21 | Google Ads |
22 | {{ googleTraffic }} |
23 |
24 |
25 | Facebook Ads |
26 | {{ facebookTraffic }} |
27 |
28 |
29 | Email |
30 | {{ emailTraffic }} |
31 |
32 |
33 | Direct |
34 | {{ directTraffic }} |
35 |
36 |
37 | Referral |
38 | {{ referralTraffic }} |
39 |
40 |
41 | None |
42 | {{ noneTraffic }} |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
147 |
--------------------------------------------------------------------------------
/client/src/components/PeriodSelectCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Select date period
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
23 |
--------------------------------------------------------------------------------
/client/src/components/TheAnalyticsDemoForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
51 |
52 |
53 |
134 |
--------------------------------------------------------------------------------
/client/src/components/UI/BaseCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 |
7 |
8 |
9 | {{ data }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
--------------------------------------------------------------------------------
/client/src/components/UI/BaseCollectionCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 | {{ subtitle }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Name
19 | |
20 |
21 |
22 |
23 |
27 | {{ item }} |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
59 |
--------------------------------------------------------------------------------
/client/src/components/UI/BasePeriodSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 | Flush Redis
4 | mdi-delete
5 |
6 |
7 |
8 |
54 |
--------------------------------------------------------------------------------
/client/src/components/UI/TheResetButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 | Reset Data
12 | mdi-restart
13 |
14 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Below you can select date, source (the page user came from), user and action (register, visit, add
9 | to cart and buy product) performed on a website. When selected, click UPDATE button.
10 |
11 |
12 |
13 | Analytics Demo Form
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
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 |
--------------------------------------------------------------------------------