From 24af6798959750a7bcab8f909b8644c0044ff235 Mon Sep 17 00:00:00 2001 From: "Koniew, Marek" Date: Thu, 1 Jun 2017 11:38:47 +0200 Subject: [PATCH 01/23] SAP Hybris Profile Quickstart --- Readme.md | 320 +------------- gruntfile.js | 238 ++--------- package.json | 16 - public/img/products/1.jpg | Bin 0 -> 46147 bytes public/img/products/2.jpg | Bin 0 -> 51942 bytes public/img/products/3.jpg | Bin 0 -> 45109 bytes public/img/products/4.jpg | Bin 0 -> 53309 bytes public/index.html | 31 +- public/js/app/cart/services/cart-service.js | 390 +++++------------- public/js/app/cart/templates/cart.html | 45 -- .../controllers/browse-products-ctrl.js | 2 +- .../app/products/services/category-service.js | 20 +- public/js/app/products/services/price-svc.js | 6 +- .../app/products/services/product-service.js | 47 ++- .../products/templates/product-detail.html | 8 +- public/js/app/shared/app-config.js | 4 +- .../site-selector/site-selector-service.js | 13 +- public/js/app/shared/router.js | 49 +-- .../shared/services/configuration-service.js | 37 +- .../app/shared/templates/top-navigation.html | 28 +- .../app/shipping/services/shipping-service.js | 10 +- public/prices.json | 49 +++ public/products.json | 137 ++++++ public/sites.json | 22 + 24 files changed, 470 insertions(+), 1002 deletions(-) create mode 100644 public/img/products/1.jpg create mode 100644 public/img/products/2.jpg create mode 100644 public/img/products/3.jpg create mode 100644 public/img/products/4.jpg create mode 100644 public/prices.json create mode 100644 public/products.json create mode 100644 public/sites.json diff --git a/Readme.md b/Readme.md index dfa95676a..9fbefb6bf 100644 --- a/Readme.md +++ b/Readme.md @@ -1,304 +1,24 @@ -# hybris Store Template for the Cloud-based Commerce Services - -This project is an e-commerce front-end written in AngularJS that is meant to showcase the available commerce services and -serve as starting point for creating a customized store front. - - -## Installation - -The following steps will demonstrate how to install and run the code on localhost. At the end, you will be able to browse and "shop" in -a pre-configured store. Feel free to take items through checkout, using any Stripe test credit card number (https://stripe.com/docs/testing). - -### 1. System requirements - -Install node and npm: - - $ brew install node # on MacOS - -[Or download from the Node site](http://nodejs.org/) - -Install grunt: - - $ npm install -g grunt-cli - -Install bower: - - $ npm install -g bower - -### 2. Project requirements - -Create a fork of the repository, clone it to your machine, and ensure you are on the 'master' branch. **Master** will be kept in sync with service dependencies deployed to prod. - -To locally install the project, execute: - - $ npm install - $ npm update - $ bower install - - -### 3. Project startup - -Start the project on localhost:9000 by executing: - - $ npm start - -This will launch a storefront for an existing project against the "prod" environment. Later, we will modify the application to go against your own project. - - -## Customization - -Now, let's create a new project for your specific site so that you can modify your store and product offering as you see fit. - -### 1. Sign up for your a new store and configure it -If you haven't done so already, create a new storefront project and obtain subscriptions to all the commerce packages in order to enable and ensure that the storefront works end to end. -The commerce packages are: - -- **Cart** -- **Checkout** -- **Coupon Management** -- **Customer Accounts** -- **Order Management** -- **Product Content** -- **Site Management** - -Follow the steps outlined in the [Dev Portal](https://devportal.yaas.io/gettingstarted/setupastorefront/index.html). - -### 2. Replace the default project id in the code base with your own (see project adminstration settings in the Builder). -In gruntfile.js, set the **PROJECT_ID** to your own project ID. When you build the project, the default project id in bootstrap.js will be replaced with your project-id. At this time you will need to also configure the **CLIENT_ID** and **REDIRECT_URI** gruntfile variables with the values set in the application associated with your project. - -### 3. Launch a new session -Execute command **"npm start"** and open your browser at http://localhost:9000. You should now see your customized store. - -### 4. Customize the style or logic of your storefront as desired -You can now modify the style or logic of your storefront. Any new JS scripts or CSS files need to be added to index.html. - -### 5. Project deployment - -Preparing project for deployment (concatenation/minification/revisioning): - - $ grunt build:prod - - or - - $ grunt build - - -The :prod parameter specifies which dynamic domain to connect with the api services. If this domain is not specified with the parameter, a warning will appear in the build output and the default setting will be applied for the api url's, which is also set to the prod api domain in the gruntfile. - -**npm start** is configured to run **grunt build:prod**. Other option is **:stage** and can be configured in the gruntfile. - -Credential parameters also exist for automated build environments. With NPM 2.0, it is possible to pass in a Client_Id, Project_Id and Redirect_URI from npm run-script command line. For example, we can further automate the build system with these parameters (pid, cid and ruri) like this: - - $ npm run-script singleProd -- --pid=abc --cid=123 --ruri=http://example.com - -This allows for many different projects with many different clients to be configured. But remember that a minimum version of NPM 2.0 is required to pass the parameters, otherwise the Client_Id, Project_Id and Redirect_URI will be set by default to the build configuration variables in the gruntfile. - -Additional parameter that can be provided is `--https` which will force https instead of http. Below this you can find an example: - - $ npm run-script singleProd -- --pid=abc --cid=123 --ruri=http://example.com --https - -Store can be used in different regions. Parameter that should be provided is `--region` which will run store in choosen region. Currently there is a default region (US) and optional region eu (EU). Below this you can find an example: - - $ npm run-script singleProd -- --pid=abc --cid=123 --ruri=http://example.com --region=eu - -**grunt build** will also optimize js and css in public/index.html. See the optimization section for more specific information. - - -### 6. Deploy application to server - -You can deploy your web application to any server desired. If you have access to a CloudFoundry environment and you're running the app in single project mode (default), -you can easily deploy your project using a [static buildpack](https://github.com/cloudfoundry-community/staticfile-buildpack) that utilizes [ngnix](http://nginx.org). The configuration for this deployment is determined by settings in file **static-manifest.yml** (see [http://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html](http://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html)). You must change the name and domain of your store to match the domain given to your project. Attempting to push as is will result in error. - - cf push -f static-manifest.yml - -# About This Project - - -## Project Organization - -The project has been structured into domain modules under public/js/app. Within each module, files are organized into - controllers, directives, services, and templates. API REST calls are made from the domain services. - -Third party dependencies are copied to public/js/vendor via the bower-installer command as part of the npm postinstall target -(see package.json file). - -The angular bootstrapping takes places in file public/js/bootstrap.js. - -From here, angular will load the "ds.app" module which comprises file public/js/app/app.js. It is here that the application -is further configured before it is loaded. - -The app-config.js file provides dynamic configuration for the application which allows you to set application variables without persisting -them in your git repository. For example, if you are running with multiple project id's changing between these environments will cause git to indicate an update. This scenario is avoided in app-config as it is included in .gitignore. Another example of non-persisted dynamic configuration is in the URL path to the API. Should this need to change, you will not need to persist it in git. - - -## Application Events - -The following application events are used to communicate state changes that affect the entire application overall. Interested controllers can subscribe to these events. - -- **'cart:updated'** - fired when new cart information has been acquired from the service; - - event object: - - cart - current cart instance - - source - source event of the update (manual | currency | language | merge | reset) -- **'cart:closeNow'** - when fired will close cart that is shown on the right side -- **'authtoken:obtained'** - fired when auth token is obtained -- **'language:updated'** - fired when the store's language has changed - - event object: - - languageCode: new language code - - source: source of the event -- **'categories:updated'** - fired when categories have been reloaded - - event object: - - categories: new category tree - - source: reason for category update -- **'user:signedin'** - signals that a user has been authenticated -- **'user:signedout'** - signals that a user has logged off -- **'user:socialLogIn'** - signals change via social login (both the external authentication as well as the subsequent yaas authentication) - - event object: - - loggedIn: true/false -- **'category:selected'** - signals that a given category was navigated to. Event object has property 'category' to indicate selection. -- **'product:opened'** - signals that a given product was navigated to. Event object has properties 'product', 'category' and 'price'. -- **'checkout:opened'** - signals that checkout page is navigated to. Event object has property 'cart'. -- **'order:placed'** - signals that order is placed. Event object has properties 'orderId' and 'cart'. - -## Running Against Different Environments - -The service endpoint domains can be configured for a specific environment by specifying the desired target in the npm/grunt commands. The endpoint URLs are configured in public/js/app/shared/site-config.js. When running grunt, the domain is injected into this configuration file via String replacement. The default environment is "prod", so you can simply invoke **grunt build** and **npm start** to build or build and run the application against the services in the **prod** environment. - -Additional api environment supported by the script is **stage**. - -To build against an environment other than **prod**, append **:[env]** to the grunt task you're calling. To build and run the app via NPM against **stage**, you'll call **grunt build:stage**. - -## Testing - -Tests are grouped by unit tests, end-to-end tests and styling tests under the "test" folder. Unit tests can be run via -the scripts/test.sh; end-to-end tests can be run via scripts/e2e-test.sh. Unit test code coverage is published to folder "coverage" -after running the unit test suite. - -## Adding Locales - -There are two distinct localization settings related to the store: there are the language preferences that are configured in Builder, and then there are translations for all static information that's displayed in a store. The Builder settings determine the language preferences for data retrieved through services, as well as the available language options that shoppers can select in the store. The static information (button labels, instructions, etc) are provided in constant files in the code base - see **public/js/app/shared/i18/lang**. Out of the box, the project currently only provides data in English and in German. If the preferred language is supported by the app localization settings, it will be selected; otherwise, the static localization will be presented in English. To support additional languages, provide your own localized constants and load the data in **public/js/app/shared/i18n/providers/translation-providers.js**. - -## Multi-Project Mode - -This project contains the capability to run the same deployed store template against multiple configured storefronts. In order to do so, start the server by calling "npm run-script multiProd". This will start up the Express.JS server configured in file multi-tenant/multi-tenant-service.js. The multi-project mode is provided for development and test purposes only. - -In the multi-project setup, instead of reading the project ID from bootstrap.js, the project-id is the first path segment in the URL. For example, to run the store against project "myproject" you would use the URL: - - http://localhost:9000/myproject - -## Security - -### DevPortal Security Documentation - -A variety of precautions have been taken to ensure information security in the demostore. For a full list of those capabilities, please see the DevPortal Security Documentation at [https://devportal.yaas.io/overview/security#StorefrontSecurity](https://devportal.yaas.io/overview/security#StorefrontSecurity) Below is a brief on a few or our recomendations. - -### y-input - -One personalized security choice you have is for the custom data wrapper directive called y-input. It gives you the ability to finely tune regular expression input checking types for specific fields, like email, password, id, etc... as added ensurance against XSS. - -### Angular Version - -It is a good idea to ensure your Angular version dependencies are above 1.2 to gain the $SCE (strict contextual escaping) that is added by default in that version. For example, you should specify your angular build version in some variety(latest) greater than 1.2 like so: -``` -"dependencies": { - "angular": "~1.4.0" -} +# Profile QuickStart storefront +## Description +This project aims to introduce the user to working with the Profile system by showing the user how actions such as page enter, or product view are processed by the system. This project is based on the [YaaS Storefront](https://github.com/SAP/yaas-storefront), but it does not use the commerce packages, so it does not need any additional configuration before start. The Tracing Debug mode is enabled by default, which makes it possible to track the events sent to the system. +## Documentation +To learn more about how to start working with the Profile system, follow this guide: https://devportal.yaas.io/solutions/saphybrisprofile/index.html#Quickstartguide +## Running +First, install the project dependencies. ``` - -### Click-Jacking - -It is recommended that you configure your deployment HTTP server to send the X-FRAME-OPTIONS header to restrict others from hosting your site inside an IFrame. -See [OWASP Click-Jacking](https://www.owasp.org/index.php/Clickjacking). - -### HTTPS - -We strongly recommend domains that are encrypted into a Secure Socket Layer (SSL) HTTPS session. Possibly also a forced redirect is optimal to ensure that all http users are converted into encrypted streams. For example of this see the configuration in the NodeJS server file located at: server/singleProdServer.js - - -### OWASP - -For more information on any of these topics see OWASP. For starters, here is a good checklist of guidlines and an industry resource for Information Security best practices: - -[https://www.owasp.org/index.php/Web_Service_Security_Cheat_Sheet](https://www.owasp.org/index.php/Web_Service_Security_Cheat_Sheet) - -## Optimization - -Performance optimizations are included in the gruntfile to improve the initial load time of the site. There is an 'optimizeCode' grunt task which concatenates and minifies JavaScript and CSS to reduce HTTP requests and the overall page size of the application. It works by pulling all the code from the development files into the .tmp directory, where it then concatenates and minifies before moving it to a final destination in the /dist directory. The 'optimizeCode' build task conducts all operations required with producing an optimized run-time, including: cleaning of /dist, copying of dependencies, and the replacement of the concatenated and minified resources. The code within the /dist directory then contains everything needed to run an optimized store front. To deploy those optimized resources, first run a build command that will populate the /dist directory with the optimized files (like **grunt build** for example) then deploy /dist to your server of choice and run **grunt startServer**. The startServer build task gives you the choice to run either a multi-project site or a single-project site with the flags --single or --multiple. It will start the server with only the minimum build steps necessary (including server-side optimizations), excluding unecessary build tasks like linting. - -A short list of the performance optimizations available are: JS & CSS minification, file revisioning, http template caching, and GZip in the NodeJS server. - - -## Modularity - -At the core of what AngularJS is getting right, right now, are modules and components. The storefront architecture was designed in best-practice manner to allow extensibility along any folder in the physical architecture. This is done by grouping files by Component first, then by language Type. So for example, a 'SomeFeature' folder that contains controllers, directives, services, templates. This structure allows reuse in the ability to add and differentiate any number of similar but different html, controllers, directives, etc. All the way up to the very top of the application in the bootstrap.js file. Where it is possible to partition and run in parallel separate variations of the codebase through manual variation of the base module in the angular.bootstrap parameter: - - angular.bootstrap( document, ['ds.app'] ); - -### Extensibility Plan - -The nice thing about the structure of AngularJS is that it gives you the ability to differentiate your business requirements into the architecture without ever having to touch the original codebase. This is possible if you are replicating the pieces that you need in parallel with the existing structure - like a scaffolding. Those topics and more are discussed at the following DevPortal location: - -[https://devportal.yaas.io/overview/extensibility/extendingthestorefront.html](https://devportal.yaas.io/overview/extensibility/extendingthestorefront.html) - - -# Resources - -For in-depth API documentation, please visit [Dev Portal](https://devportal.yaas.io/). - -## About this Project - -### Why AngularJS? - -With the rapid growth in JS frameworks, it is not feasible to evaluate all options, and it is still difficult to make a choice -among a small subset of options. -We were looking for a single-page framework with industry and community traction, support for unit testing, -decoupling of DOM manipulation and application logic, and terseness. AngularJS fit these requirements very nicely. -Ultimately, the hybris commerce services support all needed business logic, and new client applications can be created with ease as needed. - -### Why Node? - -We wanted to take advantage of the Node package manger, NPM, for resolving our infrastructure dependencies. In addition, -the Node based HTTP server turned out to be very quick to start up and thus ideal for development. - -The multi-project mode relies on NodeJS with Express so that dynamic routes can be created for multiple storefronts. -The single-project distribution generated by this project, however, can be deployed on any HTTP server, without NodeJS. - -### Infrastructure Tools - -- [Bower](http://bower.io/) - package manager for all dependent libraries on the browser side -- [Grunt](http://gruntjs.com/) - JS task runner -- [Karma](http://karma-runner.github.io/) - test runner for AngularJS -- [Jasmine](http://jasmine.github.io/) - JS unit test framework -- [Protractor](https://github.com/angular/protractor) End-to-End (E2E) test framework for AngularJS -- [PhantomJS](http://phantomjs.org/) headless WebKit used for our E2E tests -- [Less](http://lesscss.org/) CSS pre-processor - - -### AngularJS And Other UI Packages -For a complete and up-to-date list of UI dependencies, please examine file bower.json. -- [Angular.js](http://angularjs.org/) AngularJS framework -- [Angular UI router](https://github.com/angular-ui/ui-router) State-based routing framework that helps achieve loose coupling -between modules. -- [Bootstrap](http://getbootstrap.com/) Enables responsiveness using the Bootstrap responsive grid. -- [Angular Bootstrap](http://angular-ui.github.io/bootstrap/) AngularJS implementation for Bootstrap widgets (for instance, pagination). -- [Restangular](https://github.com/mgonto/restangular) Library simplifying access of REST services through Angular's $resource service. -- [ngInfiniteScroll](http://binarymuse.github.io/ngInfiniteScroll/) Infinite scrolling in AngularJS. Used for the "browse product" pages. - -### About Contributions - -We encourage contributions in the form of pull requests against the master branch of this repository. Your pull request will be reviewed by a member of the hybris organization. - -### License - -See the License.md file for complete license information. - - - - - - - - - +npm install +``` +Next, assuming that you have a tenant created, start the application with the following command: +``` +npm start -- --pid= +``` +where `` is your tenant identifier. +If your tenant is created in the EU region, you should start the app with: +``` +npm run start-eu -- --pid= --region=eu + ``` +## Using +Tracing debug bar should show a link to the Tracing UI. Click a product to create a "Product View" event, and get the new link. The generated links are also visible in the browser developer console. \ No newline at end of file diff --git a/gruntfile.js b/gruntfile.js index 461cafbfb..e5a9d5f4f 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -30,8 +30,8 @@ module.exports = function (grunt) { //--Set Parameters for Server Configuration---------------------------------------------------- // Read npm argument and set the dynamic server environment or use default configuration. // Syntax example for npm 2.0 parameters: $ npm run-script singleProd -- --pid=xxx --cid=123 --ruri=http://example.com - PROJECT_ID = grunt.option('pid') || 'saphybriscaas', - CLIENT_ID = grunt.option('cid') || 'hkpWzlQnCIe4MSTi1Ud94Q7O36aRrRrO', + PROJECT_ID = grunt.option('pid'), + CLIENT_ID = 'NmIaB67D5XXMv9YzPUXT32X4TKQwdCM2', REDIRECT_URI = grunt.option('ruri') || 'http://example.com', USE_HTTPS = grunt.option('https') || false, REGION_CODE = grunt.option('region') || '', @@ -39,6 +39,7 @@ module.exports = function (grunt) { SERVER_FILES = ['./server.js', './server/singleProdServer.js', './multi-tenant/multi-tenant-server.js'], PROJECT_ID_PATH = './public/js/app/shared/app-config.js', + INDEX_PATH = './public/index.html', PROD_DOMAIN = 'api{0}.yaas.io', STAGE_DOMAIN = 'api.stage.yaas.io', API_DOMAIN_PATH = './public/js/app/shared/app-config.js', @@ -60,6 +61,15 @@ module.exports = function (grunt) { } }; + var getPiwikUrl = function (env, regionCode) { + if (env === 'PROD') { + return 'https://' + getProdBaseUrl(regionCode) + '/hybris/profile-edge/v1' + '/events'; + } + else if (env === 'STAGE') { + return 'https://' + STAGE_DOMAIN + '/hybris/profile-edge/v1' + '/events'; + } + }; + require('load-grunt-tasks')(grunt); grunt.loadNpmTasks('grunt-text-replace'); grunt.loadNpmTasks('grunt-angular-templates'); //combines templates into cache @@ -87,14 +97,6 @@ module.exports = function (grunt) { port: port, hostname: host }, - singleProdServer: { - options: { - server: path.resolve('./server/singleProdServer.js'), - livereload: 35730, // use different port to avoid collision with client 'watch' operation - serverreload: true, // this will keep the server running, but may restart at a different port!!! - bases: [path.resolve('./server/singleProdServer.js')] - } - }, singleTenant: { options: { server: path.resolve('./server.js'), @@ -102,20 +104,6 @@ module.exports = function (grunt) { serverreload: true, // this will keep the server running, but may restart at a different port!!! bases: [path.resolve('./server.js')] } - }, - multiTenant: { // with livereload - options: { - server: path.resolve('./multi-tenant/multi-tenant-server.js'), - livereload: 35730, // use different port to avoid collision with client 'watch' operation - serverreload: true, // this will keep the server running, but may restart at a different port!!! - bases: [path.resolve('./multi-tenant/multi-tenant-server.js')] - } - }, - production: { - options: { - server: path.resolve('./server.js'), - bases: [path.resolve('./server.js')] - } } }, jshint: { @@ -141,16 +129,6 @@ module.exports = function (grunt) { files: { 'public/css/app/style.css': 'public/less/main.less' } - }, - dist: { - options: { - compress: true, - strictImports: false, - sourceMap: false - }, - files: { - 'public/css/app/style.css': 'public/less/main.less' - } } }, @@ -160,20 +138,7 @@ module.exports = function (grunt) { options: { logConcurrentOutput: true } - }, - multiProject: { - tasks: ['express:multiTenant', 'watch'], //multi-tenant-server.js - options: { - logConcurrentOutput: true - } - }, - singleProdServer: { - tasks: ['express:singleProdServer', 'watch'], - options: { - logConcurrentOutput: true - } - }, - dist: [] + } }, clean: { @@ -188,17 +153,6 @@ module.exports = function (grunt) { }, copy: { - main: { - dot: true, - expand: true, - cwd: 'public/', - src: [ - '**', 'js/**', '!scss/**', '!css/app/**', '!less/**', '!stylesheets/**', - '../.buildpacks', '../.jshintrc', '../.bowerrc', '../bower.json', - '../gruntfile.js', '../License.md', '../package.json', '../products.json', - '../multi-tenant/**', '../server/**', '../server.js'], - dest: 'dist/public/' - }, translations: { files: [ { @@ -214,42 +168,25 @@ module.exports = function (grunt) { } }, - rev: { - files: { - src: ['dist/public/**/*.{js,css}'] - } - }, - karma: { - unit: { configFile: 'config/karma.conf.js', keepalive: true } - }, - - useminPrepare: { - html: './public/index.html', //concat and minify all script tags in html build blocks. - options: { //concats in .tmp - dest: 'dist/public' //minifies result at path in html block under this directory - } - }, - - usemin: { - html: ['dist/public/index.html'] //runs replacement tasks on index. + unit: {configFile: 'config/karma.conf.js', keepalive: true} }, replace: { - stage: { - src: [API_DOMAIN_PATH], - overwrite: true, - replacements: [{ - from: /StartDynamicDomain(.*)EndDynamicDomain/g, - to: 'StartDynamicDomain*/ \'' + STAGE_DOMAIN + '\' /*EndDynamicDomain' - }] - }, prod: { - src: [API_DOMAIN_PATH], + src: [API_DOMAIN_PATH, INDEX_PATH], overwrite: true, replacements: [{ from: /StartDynamicDomain(.*)EndDynamicDomain/g, to: 'StartDynamicDomain*/ \'' + getProdBaseUrl(REGION_CODE) + '\' /*EndDynamicDomain' + }, + { + from: /StartBuilderUrl(.*)EndBuilderUrl/g, + to: 'StartBuilderUrl*/ \'' + 'https://' + PROD_DOMAIN.replace('api', 'builder').replace('{0}', '') + '/\' /*EndBuilderUrl' + }, + { + from: /StartPiwikUrl(.*)EndPiwikUrl/g, + to: 'StartPiwikUrl*/ \'' + getPiwikUrl('PROD', REGION_CODE) + '\' /*EndPiwikUrl' }] }, projectId: { @@ -283,39 +220,16 @@ module.exports = function (grunt) { from: /StartUseHTTPS(.*)EndUseHTTPS/g, to: 'StartUseHTTPS*/ ' + !!USE_HTTPS + ' /*EndUseHTTPS' }] - } - }, - - ngtemplates: { - app: { //compile html templates into angular min.js concatenation. - cwd: './public/', - src: [ - 'js/app/home/templates/home.html', - 'js/app/shared/templates/top-navigation.html', - 'js/app/shared/templates/sidebar-navigation.html', - 'js/app/cart/templates/cart.html', - 'js/app/auth/templates/signup.html', - 'js/app/auth/templates/signin.html' - //too many slows down time to render. - //'js/app/auth/templates/auth.html', - //'js/app/shared/templates/language-selector.html', - //'js/app/shared/templates/currency-selector.html', - ], - dest: '.tmp/concat/js/template.js', //temp concatenation location. - htmlmin: { // minify html configuration. - collapseBooleanAttributes: true, - collapseWhitespace: true, - removeAttributeQuotes: true, - removeComments: true, - removeEmptyAttributes: true, - removeRedundantAttributes: true, - removeScriptTypeAttributes: true, - removeStyleLinkTypeAttributes: true - }, - options: { - usemin: 'js/storefront.js', //concat temp with usemin output. - module: 'ds.app' //module to append templateCache code. - } + }, + tracing: { + src: INDEX_PATH, + overwrite: true, + replacements: [ + { + from: /StartProjectId(.*)EndProjectId/g, + to: 'StartProjectId*/ \'' + PROJECT_ID + '\' /*EndProjectId' + } + ] } }, @@ -329,28 +243,6 @@ module.exports = function (grunt) { grunt.option('force', true); - //--Convenience-Tasks----------------------------------------------- - // Wrap dev build task with parameters and dynamic domain warnings. - grunt.registerTask('default', 'Build parameters for default', - function () { - grunt.task.run('build'); - }); - - // Wrap build task with parameters and dynamic domain warnings. - grunt.registerTask('build', 'Build parameters for build', - function (domainParam) { - - grunt.task.run('replace:projectId'); - grunt.task.run('replace:clientId'); - grunt.task.run('replace:redirectURI'); - grunt.task.run('replace:useHttps'); - - runDomainReplace(domainParam); - - grunt.task.run('jshint'); - grunt.task.run('less:dev'); - grunt.task.run('optimizeCode'); - }); //--Tasks-With-Environment-Parameters---------------------------------------------- // Wrap build task with parameters and dynamic domain warnings. @@ -361,54 +253,13 @@ module.exports = function (grunt) { grunt.task.run('replace:clientId'); grunt.task.run('replace:redirectURI'); grunt.task.run('replace:useHttps'); + grunt.task.run('replace:tracing'); runDomainReplace(domainParam); grunt.task.run('singleProjectTask'); }); - // Wrap build task with parameters and dynamic domain warnings. - grunt.registerTask('multiProject', 'Build parameters for multiProject build', - function (domainParam) { - - grunt.task.run('replace:projectId'); - grunt.task.run('replace:clientId'); - grunt.task.run('replace:redirectURI'); - grunt.task.run('replace:useHttps'); - - runDomainReplace(domainParam); - - grunt.task.run('multiProjectTask'); - }); - - // Wrap build task with parameters and dynamic domain warnings. - grunt.registerTask('prepareBuild', 'Build parameters for optimized build', - function (domainParam) { - - grunt.task.run('replace:projectId'); - grunt.task.run('replace:clientId'); - grunt.task.run('replace:redirectURI'); - grunt.task.run('replace:useHttps'); - - runDomainReplace(domainParam); - - grunt.task.run('optimizeCode'); - }); - - // Wrap build task with parameters and dynamic domain warnings. - grunt.registerTask('startServer', 'Start server within deploy environment', - function () { - if (grunt.option('single')) { - grunt.task.run('concurrent:singleProdServer'); // start a single server in deployed environment. - - } else if (grunt.option('multiple')) { - grunt.task.run('concurrent:multiProject'); // start a multi-project server in deployed environment. - - } else { - grunt.task.run('concurrent:multiProject'); // default server if none is specified. - } - }); - //---Specialized-Build-Behaviors-------------------------------------------------------- grunt.registerTask('singleProjectTask', [ 'jshint', @@ -417,32 +268,11 @@ module.exports = function (grunt) { 'concurrent:singleProject' //server.js ]); - grunt.registerTask('multiProjectTask', [ - 'jshint', - 'compileTranslations', - 'less:dev', - 'concurrent:multiProject' //multi-tenant-server.js - ]); - grunt.registerTask('compileTranslations', [ 'copy:translations', //copies translation files from dev folder to lang folder 'json-minify:translations' //minifies JSON translation files ]); - grunt.registerTask('optimizeCode', [ - 'clean:dist', //deletes contents in the dist folder and the .tmp folder - 'compileTranslations', //removes comments from dev-*.json translation files and minifies them - 'less:dev', //generate style.css - 'copy', //moves dev files to dist - 'useminPrepare', //starts usemin process - 'ngtemplates', //compile html templates into ng. - 'concat', - 'uglify', - 'cssmin', - //'rev', //cachebusts css and js. //be careful was introducing first load latency. - 'usemin' //completes usemin process. - ]); - //--Dynamic-Replacement-Build-Behaviors---------------------------------------------------- // Read build parameter and set the dynamic domain for environment or give warning message. diff --git a/package.json b/package.json index 868e62144..407bf1a0b 100644 --- a/package.json +++ b/package.json @@ -10,22 +10,6 @@ "scripts": { "start": "node node_modules/grunt-cli/bin/grunt singleProject:prod", "test": "node node_modules/karma/bin/karma start config/karma.conf.js", - "singleTest": "node node_modules/grunt-cli/bin/grunt singleProject:test", - "singleStage": "node node_modules/grunt-cli/bin/grunt singleProject:stage", - "singleProd": "node node_modules/grunt-cli/bin/grunt singleProject:prod", - "singleProdEU": "node node_modules/grunt-cli/bin/grunt singleProject:prod --region=eu", - "multiTest": "node node_modules/grunt-cli/bin/grunt multiProject:test", - "multiStage": "node node_modules/grunt-cli/bin/grunt multiProject:stage --force", - "multiProd": "node node_modules/grunt-cli/bin/grunt multiProject:prod", - "multiProdHttps": "node node_modules/grunt-cli/bin/grunt multiProject:prod --https", - "multiProdHttpsEU": "node node_modules/grunt-cli/bin/grunt multiProject:prod --https --region=eu", - "multiStageHttps": "node node_modules/grunt-cli/bin/grunt multiProject:stage --https --force", - "prepareProd": "node node_modules/grunt-cli/bin/grunt prepareBuild:prod", - "prepareProdEU": "node node_modules/grunt-cli/bin/grunt prepareBuild:prod --region=eu", - "prepareTest": "node node_modules/grunt-cli/bin/grunt prepareBuild:test", - "prepareStage": "node node_modules/grunt-cli/bin/grunt prepareBuild:stage", - "startServer": "node node_modules/grunt-cli/bin/grunt startServer --single", - "startServerMulti": "node node_modules/grunt-cli/bin/grunt startServer --multiple", "postinstall": "bower install" }, "dependencies": { diff --git a/public/img/products/1.jpg b/public/img/products/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab97fdaf14a8fc9257195725efd1fcb889c5375a GIT binary patch literal 46147 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 literal 0 HcmV?d00001 diff --git a/public/img/products/2.jpg b/public/img/products/2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a720c8d69501f5f8d081a05e28ed1468a8966997 GIT binary patch literal 51942 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 &349kE=H9qpQeZz&OjoEYqWM^b8P+$=5ig9#(P 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 Ol6Zv8U^3hO|4jfKIV!yX literal 0 HcmV?d00001 diff --git a/public/img/products/3.jpg b/public/img/products/3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aee2537950e822df866a8a0cb2181e95edb030ff GIT binary patch literal 45109 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
%H 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 z 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 RuL_0#tAP0bx(;7s{{yD0s2>0T literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html index 5d39e099a..3aca70c07 100644 --- a/public/index.html +++ b/public/index.html @@ -22,6 +22,30 @@ + + +

@@ -106,10 +130,10 @@ - + - + @@ -217,5 +241,8 @@ + +
+ diff --git a/public/js/app/cart/services/cart-service.js b/public/js/app/cart/services/cart-service.js index 50b84c9a9..614c0d9b0 100644 --- a/public/js/app/cart/services/cart-service.js +++ b/public/js/app/cart/services/cart-service.js @@ -14,7 +14,7 @@ angular.module('ds.cart') - .factory('CartSvc', ['$rootScope', 'CartREST','ProductSvc', 'AccountSvc', '$q', 'GlobalData', '$location', + .factory('CartSvc', ['$rootScope', 'CartREST', 'ProductSvc', 'AccountSvc', '$q', 'GlobalData', '$location', function ($rootScope, CartREST, ProductSvc, AccountSvc, $q, GlobalData) { // Prototype for outbound "update cart item" call @@ -27,6 +27,7 @@ angular.module('ds.cart') if (product.mixins && product.mixins.taxCodes && product.mixins.taxCodes[currentSiteCode]) { this.taxCode = product.mixins.taxCodes[currentSiteCode]; } + this.product = product; this.price = price; this.quantity = qty; }; @@ -43,184 +44,35 @@ angular.module('ds.cart') }; // application scope cart instance - var cart = {}; + var cart = new Cart(); + cart.id = new Date().getMilliseconds(); /** Ensure there is a cart associated with the current session. * Returns a promise for the existing or newly created cart. Cart will only contain the id. * (Will create a new cart if the current cart hasn't been persisted yet). */ function getOrCreateCart() { - var deferredCart = $q.defer(); - // Use copy of cart from local scope if it exists - don't want to use same instance because we don't want - // data binding - if (cart.id) { - deferredCart.resolve({ cartId: cart.id }); - } else { - - var newCart = {}; - var accPromise = AccountSvc.getCurrentAccount(); - accPromise.then(function (successAccount) { - newCart.customerId = successAccount.id; - }); - accPromise.finally(function () { - newCart.currency = GlobalData.getCurrencyId(); - newCart.siteCode = GlobalData.getSiteCode(); - newCart.channel = GlobalData.getChannel(); - - CartREST.Cart.all('carts').post(newCart).then(function (response) { - cart.id = response.cartId; - deferredCart.resolve({ cartId: cart.id }); - - }, function () { - deferredCart.reject(); - }); - }); - } - return deferredCart.promise; + return $q.resolve(cart) } /** Retrieves the current cart state from the service, updates the local instance * and fires the 'cart:updated' event.*/ function refreshCart(cartId, updateSource, closeCartAfterTimeout) { - var defCart = $q.defer(); - var defCartTemp = $q.defer(); - - var params = { siteCode: GlobalData.getSiteCode() }; - - CartREST.Cart.one('carts', cartId).get(params).then(function (response) { - cart = response.plain(); - if (cart.siteCode !== GlobalData.getSiteCode()) { - CartREST.Cart.one('carts', cart.id).one('changeSite').customPOST({ siteCode: GlobalData.getSiteCode() }).finally(function () { - if (!!GlobalData.customerAccount) { - - params = angular.extend(params, { customerId: GlobalData.customerAccount.customerNumber }); - - CartREST.Cart.one('carts', cartId).get(params).then(function (response) { - cart = response.plain(); - defCartTemp.resolve(cart); - }, function () { - defCartTemp.reject(); - }); - } - else { - CartREST.Cart.one('carts', cartId).get(params).then(function (response) { - cart = response.plain(); - defCartTemp.resolve(cart); - }, function () { - defCartTemp.reject(); - }); - } - }); - - } else { - defCartTemp.resolve(cart); - } - defCartTemp.promise.then(function (curCart) { - defCart.resolve(curCart); - - }, function () { - cart.error = true; - }); - - }, function (response) { - cart = {}; - if (!response || response.status !== 404) { - cart.error = true; - } - else { - console.warn('Could not find cart. A new cart will be created when the user adds an item.'); - } - defCart.resolve(cart); + $rootScope.$emit('cart:updated', { + cart: cart, + source: updateSource, + closeAfterTimeout: closeCartAfterTimeout }); - defCart.promise.then(function () { - - var items = (cart.items ? cart.items : []); - - if(!_.isEmpty(items)){ - var productList = getProductIdsFromCart(items); - - ProductSvc.queryProductList({q:'id:('+productList+')'}).then(function(res){ - var products = res.plain(); - _.forEach(items, function(item){ - if(item.itemYrn){ - - var split = item.itemYrn.split(';'); - - var prod = _.find(products, {id:split[1]}); - - item.product = { - id:prod.id, - name:prod.name, - images:prod.media, - sku:prod.code - }; - - if(_.contains(item.itemYrn, 'product-variant')){ - ProductSvc.getProductVariant({productId:split[1],variantId:split[2]}).then(function(variant){ - - item.variants=[]; - _.forEach(variant.options, function(ele){ - for (var key in ele) { - item.variants.push(key+': '+ ele[key] ); - } - }); - - if(_.isArray(variant.media) && _.size(variant.media) > 0){ - item.product.images = variant.media; - item.product.code = variant.code; - } - if(variant.name) { - item.product.name = variant.name; - } - }); - } - } - }); - }); - } - $rootScope.$emit('cart:updated', { cart: cart, source: updateSource, closeAfterTimeout: closeCartAfterTimeout }); - }); - return defCart.promise; + return $q.resolve(cart); } function mergeAnonymousCartIntoCurrent(anonCart) { var deferred = $q.defer(); - if (anonCart && anonCart.id) { - // merge anon cart into user cart - CartREST.Cart.one('carts', cart.id).one('merge').customPOST({ carts: [anonCart.id] }).then(function () { - // merge anonymous cart - will change currency if needed - refreshCart(cart.id, 'merge').then( - function () { - deferred.resolve(); - }, - function () { - deferred.reject(); - } - ); - }, function () { - cart.error = true; - deferred.reject(); - }); - } else { - // scope is already equivalent to latest user cart - if (cart.siteCode !== GlobalData.getSiteCode()) { - if (cart.id) { - refreshCart(cart.id, 'site').then( - function () { - deferred.resolve(); - }, - function () { - deferred.reject(); - } - ); - } - } else { - $rootScope.$emit('cart:updated', { cart: cart }); - deferred.resolve(); - } - } + cart.items = cart.items.concat(anonCart.items); + $rootScope.$emit('cart:updated', {cart: cart}); + return deferred.promise; } @@ -235,20 +87,25 @@ angular.module('ds.cart') var createItemDef = $q.defer(); getOrCreateCart().then(function (cartResult) { - var price = {'priceId': prices[0].priceId, 'effectiveAmount': prices[0].effectiveAmount, 'originalAmount': prices[0].originalAmount, 'currency': prices[0].currency}; + var price = { + 'priceId': prices[0].priceId, + 'effectiveAmount': prices[0].effectiveAmount, + 'originalAmount': prices[0].originalAmount, + 'currency': prices[0].currency + }; - if(prices[0].measurementUnit) { - price.measurementUnit = {'unit' : prices[0].measurementUnit.unitCode, 'quantity' : prices[0].measurementUnit.quantity}; + if (prices[0].measurementUnit) { + price.measurementUnit = { + 'unit': prices[0].measurementUnit.unitCode, + 'quantity': prices[0].measurementUnit.quantity + }; } var item = new Item(product, price, qty); - CartREST.Cart.one('carts', cartResult.cartId).all('items').post(item).then(function () { - refreshCart(cartResult.cartId, cartUpdateMode, closeCartAfterTimeout); - createItemDef.resolve(); - }, function () { - refreshCart(cart.id, cartUpdateMode, closeCartAfterTimeout); - createItemDef.reject(); - }); + cart.items.push(item); + + //refreshCart(cart.id, cartUpdateMode, closeCartAfterTimeout); + createItemDef.resolve() }, function () { createItemDef.reject(); @@ -257,26 +114,26 @@ angular.module('ds.cart') } - function getProductInCart(cart, product){ - return _.find((cart.items ? cart.items : []),function(item){ - if(item.itemYrn === product.itemYrn){ - return item; - } + function getProductInCart(cart, product) { + return _.find((cart.items ? cart.items : []), function (item) { + if (item.itemYrn === product.itemYrn) { + return item; + } }); } - function getIdFromItemYrn(itemYrn){ - if(_.contains(itemYrn, 'product:product')){ - return itemYrn.split(';')[1]; - } else if(_.contains(itemYrn, 'product:product-variant')){ - return itemYrn.split(';')[2]; - } + function getIdFromItemYrn(itemYrn) { + if (_.contains(itemYrn, 'product:product')) { + return itemYrn.split(';')[1]; + } else if (_.contains(itemYrn, 'product:product-variant')) { + return itemYrn.split(';')[2]; + } } - function getProductIdsFromCart(items){ - return _.map(items, function(item){ - return item.itemYrn ? getIdFromItemYrn(item.itemYrn) : ''; - }).join(','); + function getProductIdsFromCart(items) { + return _.map(items, function (item) { + return item.itemYrn ? getIdFromItemYrn(item.itemYrn) : ''; + }).join(','); } function reformatCartItems(cart) { @@ -287,11 +144,11 @@ angular.module('ds.cart') itemYrn: cart.items[i].itemYrn, productId: cart.items[i].product ? cart.items[i].product.id : '', quantity: cart.items[i].quantity, - unitPrice:{ + unitPrice: { amount: cart.items[i].price.effectiveAmount, currency: cart.items[i].price.currency }, - taxCode:cart.items[i].taxCode + taxCode: cart.items[i].taxCode }; items.push(item); } @@ -325,7 +182,8 @@ angular.module('ds.cart') */ resetCart: function () { cart = new Cart(); - $rootScope.$emit('cart:updated', { cart: cart, source: 'reset' }); + cart.id = new Date().getMilliseconds(); + $rootScope.$emit('cart:updated', {cart: cart, source: 'reset'}); }, /** Returns the cart as stored in the local scope - no GET is issued.*/ @@ -344,58 +202,27 @@ angular.module('ds.cart') * Retrieve any existing cart that there might be for an authenticated user, and merges it with * any content in the current cart. */ - refreshCartAfterLogin: function (customerId) { - var deferred = $q.defer(); - // store existing anonymous cart + refreshCartAfterLogin: function () { var anonCart = cart; - // retrieve any cart associated with the authenticated user - CartREST.Cart.one('carts', null).get({ customerId: customerId, siteCode: GlobalData.getSiteCode() }).then(function (authUserCart) { - // there is an existing cart - update scope instance - cart = authUserCart.plain(); - mergeAnonymousCartIntoCurrent(anonCart).then( - function () { - deferred.resolve(); - }, - function () { - deferred.reject(); - } - ); - }, function () { - // no existing user cart - if (anonCart && anonCart.id) { - // create new cart for customer so anon cart can be merged into it - cart = { - customerId: customerId, - currency: GlobalData.getCurrencyId(), - siteCode: GlobalData.getSiteCode(), - channel: GlobalData.getChannel() - }; - - CartREST.Cart.all('carts').post(cart).then(function (newCartResponse) { - cart.id = newCartResponse.cartId; - mergeAnonymousCartIntoCurrent(anonCart).then( - function () { - deferred.resolve(); - }, - function () { - deferred.reject(); - } - ); - }, function () { - cart.error = true; - console.error('new cart creation failed'); - deferred.reject(); - }); - } else { // anonymous cart was never created - // just use empty cart - customer-specific cart will be created once first item is added - cart = {}; - cart.currency = GlobalData.getCurrencyId(); - cart.siteCode = GlobalData.getSiteCode(); - deferred.resolve(); - } - }); - return deferred.promise; + cart = new Cart(); + cart.id = new Date().getMilliseconds(); + + if (anonCart && anonCart.id) { + // create new cart for customer so anon cart can be merged into it + + _.extend(cart, { + customerId: customerId, + currency: GlobalData.getCurrencyId(), + siteCode: GlobalData.getSiteCode(), + channel: GlobalData.getChannel() + }); + ; + } else { // anonymous cart was never created + cart.currency = GlobalData.getCurrencyId(); + cart.siteCode = GlobalData.getSiteCode(); + } + return $q.resolve(); }, // Exposed for use in mixin services, like cart-note-mixin-service.js @@ -413,17 +240,8 @@ angular.module('ds.cart') var cartItem = { quantity: qty }; - CartREST.Cart.one('carts', cart.id).all('items').customPUT(cartItem, item.id + '?partial=true').then(function () { - refreshCart(cart.id, cartUpdateMode, closeCartAfterTimeout); - updateDef.resolve(); - }, function () { - angular.forEach(cart.items, function (it) { - if (item.id === it.id) { - item.error = true; - } - }); - updateDef.reject(); - }); + refreshCart(cart.id, 'manual'); + updateDef.resolve(); } return updateDef.promise; }, @@ -433,15 +251,10 @@ angular.module('ds.cart') * @param productId */ removeProductFromCart: function (itemId) { - CartREST.Cart.one('carts', cart.id).one('items', itemId).customDELETE().then(function () { - refreshCart(cart.id, 'manual'); - }, function () { - angular.forEach(cart.items, function (item) { - if (item.id === itemId) { - item.error = true; - } - }); + cart.items = _.filter(cart.items, function (item) { + return item.id !== itemId; }); + refreshCart(cart.id, 'manual'); }, /* @@ -456,21 +269,22 @@ angular.module('ds.cart') if (productDetailQty > 0) { - var self = this; - var productId = _.has(product, 'itemYrn') ? product.itemYrn.split(';')[1] : product.id; + var self = this; + var productId = _.has(product, 'itemYrn') ? product.itemYrn.split(';')[1] : product.id; - return ProductSvc.getProduct({productId:productId}).then(function(response) { - if(!_.has(product, 'itemYrn')){ - product.itemYrn = response.yrn; - } + return ProductSvc.getProduct({productId: productId}).then(function (response) { + var product = response.product; + if (!_.has(product, 'itemYrn')) { + product.itemYrn = response.yrn; + } - product.mixins = response.mixins; - var item = getProductInCart(cart, product); - if(item){ - return self.updateCartItemQty(item, item.quantity + productDetailQty, config); - } - return createCartItem(product, prices, productDetailQty, config); - }); + product.mixins = response.mixins; + var item = getProductInCart(cart, product); + if (item) { + return self.updateCartItemQty(item, item.quantity + productDetailQty, config); + } + return createCartItem(product, prices, productDetailQty, config); + }); } else { return $q.when({}); @@ -479,21 +293,15 @@ angular.module('ds.cart') redeemCoupon: function (coupon, cartId) { coupon = parseCoupon(coupon); - return CartREST.Cart.one('carts', cartId).customPOST(coupon, 'discounts').then(function() { - refreshCart(cartId, 'manual'); - }); + refreshCart(cart.id, 'manual'); }, removeAllCoupons: function (cartId) { - return CartREST.Cart.one('carts', cartId).all('discounts').remove().then(function () { - refreshCart(cartId, 'manual'); - }); + refreshCart(cart.id, 'manual'); }, removeCoupon: function (cartId, couponId) { - return CartREST.Cart.one('carts', cartId).one('discounts', couponId).remove().then(function () { - refreshCart(cartId, 'manual'); - }); + refreshCart(cart.id, 'manual'); }, getCalculateTax: function () { @@ -504,19 +312,17 @@ angular.module('ds.cart') taxCalculationApplied: true }; } - return { taxCalculationApplied: false }; + return {taxCalculationApplied: false}; }, setCalculateTax: function (zipCode, countryCode, cartId) { - return CartREST.Cart.one('carts', cartId).customPUT({ zipCode: zipCode, countryCode: countryCode }, '').then(function () { - refreshCart(cartId, 'manual'); - }); + refreshCart(cart.id, 'manual'); }, recalculateCart: function (cart, addressToShip, shippingCostObject) { var items = reformatCartItems(cart); var discounts = []; - angular.forEach(cart.discounts, function(discount){ + angular.forEach(cart.discounts, function (discount) { discounts.push({ discountRate: discount.discountRate, amount: discount.amount, @@ -532,12 +338,12 @@ angular.module('ds.cart') discounts: discounts, addresses: [ { - type: 'SHIP_TO', - addressLine1: addressToShip.address1, - city: addressToShip.city, - state: addressToShip.state, - zipCode: addressToShip.zipCode, - country: addressToShip.country + type: 'SHIP_TO', + addressLine1: addressToShip.address1, + city: addressToShip.city, + state: addressToShip.state, + zipCode: addressToShip.zipCode, + country: addressToShip.country } ] }; @@ -548,7 +354,7 @@ angular.module('ds.cart') zoneId: shippingCostObject.zoneId }; } - return CartREST.CalculateCart.all('calculation').customPOST(data, ''); + return $q.resolve(data) } }; diff --git a/public/js/app/cart/templates/cart.html b/public/js/app/cart/templates/cart.html index aad2b5dd4..80703f173 100644 --- a/public/js/app/cart/templates/cart.html +++ b/public/js/app/cart/templates/cart.html @@ -50,9 +50,6 @@
- -
-
@@ -109,25 +106,6 @@
{{'TOTAL_PRICE' | translate}}: {{ item.itemPrice.amount || 0 | currency: currencySymbol}}
- -
-
@@ -137,29 +115,6 @@
- -
- - -
-
-
- - - - - -

{{'UNABLE_TO_SAVE_NOTE' | translate}}

- - - - -
-
- -
- -
diff --git a/public/js/app/products/controllers/browse-products-ctrl.js b/public/js/app/products/controllers/browse-products-ctrl.js index f8632699c..249e9bf20 100644 --- a/public/js/app/products/controllers/browse-products-ctrl.js +++ b/public/js/app/products/controllers/browse-products-ctrl.js @@ -89,7 +89,7 @@ angular.module('ds.products') ProductSvc.queryProductList(query) .then(function getProducts(products) { if (products) { - GlobalData.products.meta.total = parseInt(products.headers[settings.headers.paging.total.toLowerCase()], 10) || 0; + GlobalData.products.meta.total = products.length; if (concat) { $scope.products = $scope.products.concat(products); } else { diff --git a/public/js/app/products/services/category-service.js b/public/js/app/products/services/category-service.js index 019aa0c1f..1682888d8 100644 --- a/public/js/app/products/services/category-service.js +++ b/public/js/app/products/services/category-service.js @@ -60,24 +60,8 @@ angular.module('ds.products') /** Returns a promise over the category list as loaded from the service. Fires event "categories:updated". * @param source - indicates source/reason for update, eg. 'languageUpdate' - see setting.eventSource. * */ - getCategories: function (source) { - var catDef = $q.defer(); - - PriceProductREST.Categories.all('categories').getList({ expand: 'subcategories', toplevel: true }).then(function (result) { - categoryMap = {}; - catList = []; - angular.forEach(result.plain(), function (category) { - if(category.name){ - catList.push(category); - loadCategory(category); - } - }); - $rootScope.$emit('categories:updated', {categories: catList, source: source}); - catDef.resolve(catList); - }, function (error) { - catDef.reject(error); - }); - return catDef.promise; + getCategories: function () { + return $q.resolve([]) }, /** Returns categories from cache.*/ diff --git a/public/js/app/products/services/price-svc.js b/public/js/app/products/services/price-svc.js index 17d9ee9f5..3a7b256f8 100644 --- a/public/js/app/products/services/price-svc.js +++ b/public/js/app/products/services/price-svc.js @@ -13,10 +13,12 @@ 'use strict'; angular.module('ds.products') - .factory('PriceSvc', ['PricesREST', '$q', function (PricesREST, $q) { + .factory('PriceSvc', ['$q', '$http', function ($q, $http) { var getPrices = function (parms) { - return PricesREST.Prices.all('prices').customGET('', parms); + return $http.get('prices.json').then(function (response) { + return response.data; + }); }; return { diff --git a/public/js/app/products/services/product-service.js b/public/js/app/products/services/product-service.js index 01fa3f605..0e66f380a 100644 --- a/public/js/app/products/services/product-service.js +++ b/public/js/app/products/services/product-service.js @@ -16,26 +16,43 @@ * Encapsulates access to the CAAS product API. */ angular.module('ds.products') - .factory('ProductSvc', ['PriceProductREST', function(PriceProductREST){ + .factory('ProductSvc', ['$http', '$q', function ($http, $q) { - var getProductList = function (parms) { - return PriceProductREST.Products.all('products').getList(parms); - }; + var listPromise = $http.get('products.json').then(function (response) { + return response.data; + }); + + var pricesPromise = $http.get('prices.json').then(function (response) { + return response.data; + }); return { - queryProductList: function(parms) { - return getProductList(parms); + queryProductList: function (parms) { + return listPromise.then(function (data) { + return data; + }); }, - getProductVariant: function(params) { - return PriceProductREST.Products.withConfig(function(RestangularConfigurer){ - RestangularConfigurer.restangularFields.options = 'restangularOptions'; - }).one('products', params.productId).one('variants', params.variantId).get(); + getProductVariant: function (params) { + return listPromise.then(function (data) { + return _.findWhere(data, {id: params.productId}); + }); }, - getProductVariants: function(params) { - return PriceProductREST.Products.one('products', params.productId).all('variants').getList(); + getProductVariants: function (params) { + return listPromise.then(function (data) { + return _.filter(data, function (item) { + item.id === params.productId; + }); + }); }, - getProduct: function(params) { - return PriceProductREST.Products.one('products', params.productId).get(); + getProduct: function (params) { + return $q.all([listPromise, pricesPromise]).then(function (results) { + return { + product: _.findWhere(results[0], {id: params.productId}), + prices: _.filter(results[1], function (item) { + return item.productId === params.productId; + }) + } + }); } }; -}]); + }]); diff --git a/public/js/app/products/templates/product-detail.html b/public/js/app/products/templates/product-detail.html index fadea7024..e422c0841 100644 --- a/public/js/app/products/templates/product-detail.html +++ b/public/js/app/products/templates/product-detail.html @@ -80,12 +80,6 @@

{{product.name}}

- -
- -
-
@@ -99,7 +93,7 @@

{{product.name}}

-
+
diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index 2a0bceb7c..172c85e33 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -35,14 +35,14 @@ angular.module('ds.appconfig', []) tenantId = window.location.pathname.substring( 1, pathLength-1 ); } else { // Dynamic ProjectId is configured and replaced by build script, see gruntfile. - tenantId = /*StartProjectId*/ 'saphybriscaas' /*EndProjectId*/; + tenantId = /*StartProjectId*/ '' /*EndProjectId*/; } return tenantId; }, clientId: function() { // Dynamic ClientId is configured and replaced by build script, see gruntfile. - return /*StartClientId*/ 'hkpWzlQnCIe4MSTi1Ud94Q7O36aRrRrO' /*EndClientId*/; + return /*StartClientId*/ 'NmIaB67D5XXMv9YzPUXT32X4TKQwdCM2' /*EndClientId*/; }, redirectURI: function() { diff --git a/public/js/app/shared/directives/site-selector/site-selector-service.js b/public/js/app/shared/directives/site-selector/site-selector-service.js index 6040a0111..43a3e8dae 100644 --- a/public/js/app/shared/directives/site-selector/site-selector-service.js +++ b/public/js/app/shared/directives/site-selector/site-selector-service.js @@ -13,18 +13,19 @@ 'use strict'; angular.module('ds.shared') - .factory('SiteSelectorSvc', ['GlobalData', 'CartSvc', 'SiteSettingsREST', - function (GlobalData, CartSvc, SiteSettingsREST) { - + .factory('SiteSelectorSvc', ['GlobalData', 'CartSvc', '$q', '$http', + function (GlobalData, CartSvc, $q, $http) { + return { /** * Method that is used to change current site on storefront */ changeSite: function (site, languageCode) { - SiteSettingsREST.SiteSettings.one('sites', site.code).get({ expand: 'payment:active,tax:active,mixin:*' }).then(function (result) { - GlobalData.setSite(result, languageCode); - GlobalData.setSiteCookie(result); + $http.get('sites.json').then(function (response) { + var site = response.data[0]; + GlobalData.setSite(site, languageCode); + GlobalData.setSiteCookie(site); }); } diff --git a/public/js/app/shared/router.js b/public/js/app/shared/router.js index e167e492c..a7bf73248 100644 --- a/public/js/app/shared/router.js +++ b/public/js/app/shared/router.js @@ -127,49 +127,34 @@ angular.module('ds.router', []) } }, resolve: { - product: ['$stateParams', 'PriceProductREST', 'CategorySvc', 'initialized', function ($stateParams, PriceProductREST, CategorySvc, initialized) { + product: ['$stateParams', 'ProductSvc', 'CategorySvc', 'initialized', function ($stateParams, ProductSvc, CategorySvc, initialized) { if(initialized){ - return PriceProductREST.ProductDetails.one('productdetails', $stateParams.productId).customGET('', {expand: 'media'}) - .then(function (prod) { - if(prod.categories && prod.categories.length){ - return CategorySvc.getCategoryById(prod.categories[0].id).then(function(category){ - prod.richCategory = category; - return prod; - }); - - } else { + return ProductSvc.getProduct($stateParams).then(function (prod) { + if(prod.categories && prod.categories.length){ + return CategorySvc.getCategoryById(prod.categories[0].id).then(function(category){ + prod.richCategory = category; return prod; - } - }); + }); + + } else { + return prod; + } + }); } }], - variants: ['$stateParams', 'initialized', '$http', 'SiteConfigSvc', - function ($stateParams, initialized, $http, SiteConfigSvc) { + variants: ['$stateParams', 'initialized', 'ProductSvc', 'SiteConfigSvc', + function ($stateParams, initialized, ProductSvc, SiteConfigSvc) { if (initialized) { - // $http used since 'option' property in response body is not handled correctly by Restangular - return $http.get(SiteConfigSvc.apis.products.baseUrl + '/products/' + $stateParams.productId + '/variants', { - params: { - pageNumber: 1, pageSize: 9999 - } - }).then(function (response) { - return response.data; - }); + return ProductSvc.getProductVariants($stateParams); } }], - variantPrices: ['$stateParams', 'initialized', '$http', 'SiteConfigSvc', 'GlobalData', - function ($stateParams, initialized, $http, SiteConfigSvc, GlobalData) { + variantPrices: ['$stateParams', 'initialized', 'PriceSvc', 'SiteConfigSvc', 'GlobalData', + function ($stateParams, initialized, PriceSvc, SiteConfigSvc, GlobalData) { if (initialized) { - return $http.get(SiteConfigSvc.apis.prices.baseUrl + '/prices', { - params: { - group: $stateParams.productId, - currency: GlobalData.getCurrencyId() - } - }).then(function (response) { - return response.data; - }); + return PriceSvc.getPrices($stateParams); } }], diff --git a/public/js/app/shared/services/configuration-service.js b/public/js/app/shared/services/configuration-service.js index e7eaf76fe..11859732e 100644 --- a/public/js/app/shared/services/configuration-service.js +++ b/public/js/app/shared/services/configuration-service.js @@ -16,8 +16,8 @@ * Encapsulates access to the configuration service. */ angular.module('ds.shared') - .factory('ConfigSvc', ['$rootScope', '$q', 'settings', 'GlobalData', 'AuthSvc', 'AccountSvc', 'CartSvc', 'CategorySvc', 'SiteSettingsREST', - function ($rootScope, $q, settings, GlobalData, AuthSvc, AccountSvc, CartSvc, CategorySvc, SiteSettingsREST) { + .factory('ConfigSvc', ['$rootScope', '$q', 'settings', 'GlobalData', 'AuthSvc', 'AccountSvc', 'CartSvc', 'CategorySvc', 'SiteSettingsREST', '$http', + function ($rootScope, $q, settings, GlobalData, AuthSvc, AccountSvc, CartSvc, CategorySvc, SiteSettingsREST, $http) { var initialized = false; var selectedSiteCode = ''; @@ -53,9 +53,12 @@ angular.module('ds.shared') function loadConfiguration() { /** - * Get default site for the moment - */ - var configPromise = SiteSettingsREST.SiteSettings.all('sites').getList({}); + * Get default site for the moment + */ + var configPromise = $http.get('sites.json').then(function (response) { + return response.data; + }); + configPromise.then(function (sites) { //Check if there is already default site in memory or cookies and if that one is valid one (exists in returned array) @@ -85,23 +88,7 @@ angular.module('ds.shared') }); - /** - * Get login config (Facebook and Google) - */ - var loginConfigPromise = AuthSvc.getFBAndGoogleLoginKeys(); - loginConfigPromise.then(function (result) { - - if (!!result.facebookAppId) { - settings.facebookAppId = result.facebookAppId; - } - if (!!result.googleClientId) { - settings.googleClientId = result.googleClientId; - } - }, function (error) { - console.error('Facebook and Google key retrieval failed: ' + JSON.stringify(error)); - }); - - return $q.all([configPromise]); + return configPromise; } @@ -117,7 +104,9 @@ angular.module('ds.shared') } else { loadConfiguration(GlobalData.store.tenant).then(function () { - var siteSettingPromise = SiteSettingsREST.SiteSettings.one('sites', selectedSiteCode).get({ expand: 'payment:active,tax:active,mixin:*' }); + var siteSettingPromise = $http.get('sites.json').then(function (response) { + return response.data[0] + }); siteSettingPromise.then(function (site) { //Set site and load initial language @@ -133,7 +122,7 @@ angular.module('ds.shared') return account; }).then(function (account) { - if(account) { + if (account) { CartSvc.refreshCartAfterLogin(account.id); } }); diff --git a/public/js/app/shared/templates/top-navigation.html b/public/js/app/shared/templates/top-navigation.html index c1d568436..14aa6bfae 100644 --- a/public/js/app/shared/templates/top-navigation.html +++ b/public/js/app/shared/templates/top-navigation.html @@ -27,17 +27,6 @@
- - -
- -
-
@@ -65,7 +54,7 @@
documentation.", + //XMSG "ERROR_REDIRECT": "Hier ist eine Seite, die Ihnen weiterhilft.", //XBUT "ERROR_BUTTON_TEXT": "STARTSEITE", diff --git a/public/js/app/shared/i18n/dev/dev_en.json b/public/js/app/shared/i18n/dev/dev_en.json index 720e9cdb6..ec3e19bd5 100644 --- a/public/js/app/shared/i18n/dev/dev_en.json +++ b/public/js/app/shared/i18n/dev/dev_en.json @@ -680,8 +680,12 @@ //XMSG "ERROR_TITLE_404": "Page not found", //XMSG + "ERROR_TITLE_409": "Please configure your tenant", + //XMSG "ERROR_MESSAGE_404": "Oops! There's a problem. This page doesn't exist.", //XMSG + "ERROR_MESSAGE_409": "Please check if you use the right tenant and your tenant is subscribed to the required packages. Please refer the documentation.", + //XMSG "ERROR_REDIRECT": "Here is a page to help you get back on track.", //XBUT "ERROR_BUTTON_TEXT": "HOMEPAGE", From fbd65322526e973c9c8b76105e0879baa81a4013 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Fri, 2 Jun 2017 10:23:44 +0200 Subject: [PATCH 04/23] Improved 409 error handling --- .../js/app/errors/controllers/errors-ctrl.js | 63 ++++++++++--------- .../app/errors/templates/error-display.html | 4 +- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/public/js/app/errors/controllers/errors-ctrl.js b/public/js/app/errors/controllers/errors-ctrl.js index f9f21b669..a80ef93dc 100644 --- a/public/js/app/errors/controllers/errors-ctrl.js +++ b/public/js/app/errors/controllers/errors-ctrl.js @@ -12,35 +12,40 @@ 'use strict'; angular.module('ds.errors', []) - /** - * Dynamic error display. - */ +/** + * Dynamic error display. + */ .controller('ErrorsCtrl', ['$scope', '$state', '$stateParams', '$translate', - function( $scope, $state, $stateParams, $translate ) { - - var errorType = ''; - - // if errorId is valid, then postfix dynamic message, else always generic message. - if($stateParams.errorId === '401' || $stateParams.errorId === '404' || $stateParams.errorId === '409'){ - errorType = '_' + $stateParams.errorId; - } - - $translate('ERROR_TITLE' + errorType).then(function(value){ - $scope.errorTitle = value; - }); - $translate('ERROR_MESSAGE' + errorType).then(function(value){ - $scope.errorMessage = value; - }); - $translate('ERROR_REDIRECT').then(function(value){ - $scope.errorRedirect = value; - }); - $translate('ERROR_BUTTON_TEXT').then(function(value){ - $scope.errorButtonText = value; - }); - - $scope.redirect = function() { - $state.go('base.home'); - }; - }]); + function ($scope, $state, $stateParams, $translate) { + + var errorType = ''; + + // if errorId is valid, then postfix dynamic message, else always generic message. + if ($stateParams.errorId === '401' || $stateParams.errorId === '404' || $stateParams.errorId === '409') { + errorType = '_' + $stateParams.errorId; + } + + $translate('ERROR_TITLE' + errorType).then(function (value) { + $scope.errorTitle = value; + }); + $translate('ERROR_MESSAGE' + errorType).then(function (value) { + $scope.errorMessage = value; + }); + + if ($stateParams.errorId !== '409') { + $translate('ERROR_REDIRECT').then(function (value) { + $scope.errorRedirect = value; + }); + + $translate('ERROR_BUTTON_TEXT').then(function (value) { + $scope.errorButtonText = value; + }); + + } + + $scope.redirect = function () { + $state.go('base.home'); + }; + }]); diff --git a/public/js/app/errors/templates/error-display.html b/public/js/app/errors/templates/error-display.html index ceaf0a6aa..6dec04afe 100644 --- a/public/js/app/errors/templates/error-display.html +++ b/public/js/app/errors/templates/error-display.html @@ -3,10 +3,10 @@

{{errorTitle}}


- {{errorRedirect}} + {{errorRedirect}}

-
+
From c27134c6dbaa80cdc687b01d1a6294266f964462 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Tue, 3 Oct 2017 14:52:15 +0200 Subject: [PATCH 05/23] Added cookie consent, profile toolbox and updated layout. --- bower.json | 3 +- gruntfile.js | 15 +- public/favicon.ico | Bin 1150 -> 432490 bytes public/index.html | 32 +-- public/js/app/app.js | 3 +- public/js/app/shared/app-config.js | 8 +- public/js/app/shared/directives/y-profile.js | 79 ++++++ public/js/app/shared/directives/y-tracking.js | 232 ++++++++++++------ public/js/app/shared/services/cookie-svc.js | 21 ++ public/js/app/shared/settings.js | 1 + .../app/shared/templates/profile-header.html | 5 + .../app/shared/templates/profile-toolbox.html | 8 + .../app/shared/templates/top-navigation.html | 91 +------ public/less/_global.less | 8 +- public/less/_pdp.less | 4 - public/less/_products-list.less | 3 +- public/less/_variables.less | 3 + public/less/_y-profile.less | 53 ++++ public/less/main.less | 2 + 19 files changed, 360 insertions(+), 211 deletions(-) create mode 100644 public/js/app/shared/directives/y-profile.js create mode 100644 public/js/app/shared/templates/profile-header.html create mode 100644 public/js/app/shared/templates/profile-toolbox.html create mode 100644 public/less/_y-profile.less diff --git a/bower.json b/bower.json index 13f88c6c7..9c14ced5e 100644 --- a/bower.json +++ b/bower.json @@ -37,7 +37,8 @@ "algoliasearch": "2.9.2", "yamm3": "1.1.0", "angular-ui-notification": "^0.2.0", - "angular-moment": "^1.0.0" + "angular-moment": "^1.0.0", + "cookieconsent": "^3.0.4" }, "install": { "path": { diff --git a/gruntfile.js b/gruntfile.js index e5a9d5f4f..2628bad8c 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -183,6 +183,10 @@ module.exports = function (grunt) { { from: /StartBuilderUrl(.*)EndBuilderUrl/g, to: 'StartBuilderUrl*/ \'' + 'https://' + PROD_DOMAIN.replace('api', 'builder').replace('{0}', '') + '/\' /*EndBuilderUrl' + }, + { + from: /StartConsentManagerUrl(.*)EndConsentManagerUrl/g, + to: 'StartConsentManagerUrl*/ \'' + 'https://' + getProdBaseUrl(REGION_CODE).concat('/hybris/customer-consent/v1') + '/\' /*EndConsentManagerUrl' }, { from: /StartPiwikUrl(.*)EndPiwikUrl/g, @@ -220,16 +224,6 @@ module.exports = function (grunt) { from: /StartUseHTTPS(.*)EndUseHTTPS/g, to: 'StartUseHTTPS*/ ' + !!USE_HTTPS + ' /*EndUseHTTPS' }] - }, - tracing: { - src: INDEX_PATH, - overwrite: true, - replacements: [ - { - from: /StartProjectId(.*)EndProjectId/g, - to: 'StartProjectId*/ \'' + PROJECT_ID + '\' /*EndProjectId' - } - ] } }, @@ -253,7 +247,6 @@ module.exports = function (grunt) { grunt.task.run('replace:clientId'); grunt.task.run('replace:redirectURI'); grunt.task.run('replace:useHttps'); - grunt.task.run('replace:tracing'); runDomainReplace(domainParam); diff --git a/public/favicon.ico b/public/favicon.ico index ac20476a5ac15967fb5349e36533a18ee16b7566..7c9c805f845c33615027ee690eb0a8749751391b 100644 GIT binary patch literal 432490 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 + @@ -22,30 +23,6 @@ - - -
@@ -58,14 +35,13 @@
- -
+ @@ -101,6 +77,7 @@ + @@ -243,6 +220,7 @@
- + + diff --git a/public/js/app/app.js b/public/js/app/app.js index 18582b6ff..c83656f68 100644 --- a/public/js/app/app.js +++ b/public/js/app/app.js @@ -43,7 +43,8 @@ window.app = angular.module('ds.app', [ 'ds.localstorage', 'ds.appconfig', 'ds.searchlist', - 'ds.ysearch' + 'ds.ysearch', + 'ds.yprofile' ]) .constant('_', window._) diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index 172c85e33..79bc089c6 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -48,7 +48,13 @@ angular.module('ds.appconfig', []) redirectURI: function() { // Dynamic RedirectURI is configured and replaced by build script, see gruntfile. return /*StartRedirectURI*/ 'http://example.com' /*EndRedirectURI*/; - } + }, + builderURL: function () { + return /*StartBuilderUrl*/ '' /*EndBuilderUrl*/; + }, + consentManagerURL: function () { + return /*StartConsentManagerUrl*/ '' /*EndConsentManagerUrl*/ + } }); diff --git a/public/js/app/shared/directives/y-profile.js b/public/js/app/shared/directives/y-profile.js new file mode 100644 index 000000000..c51c7358b --- /dev/null +++ b/public/js/app/shared/directives/y-profile.js @@ -0,0 +1,79 @@ +/** + * [y] hybris Platform + * + * Copyright (c) 2000-2015 hybris AG + * All rights reserved. + * + * This software is the confidential and proprietary information of hybris + * ("Confidential Information"). You shall not disclose such Confidential + * Information and shall use it only in accordance with the terms of the + * license agreement you entered into with hybris. + */ + +'use strict'; + +angular.module('ds.yprofile', []) + .directive('yProfileToolbox', ['appConfig', 'CookieSvc', 'ytrackingSvc', function (appConfig, CookieSvc, ytrackingSvc) { + return { + restrict: 'E', + scope: {}, + templateUrl: 'js/app/shared/templates/profile-toolbox.html', + link: function (scope) { + var apiPath = appConfig.dynamicDomain(); + var tenantId = appConfig.storeTenant(); + var builderPath = appConfig.builderURL(); + var consentUrl = appConfig.consentManagerURL(); + + scope.$on('tracing:response', function (event, contextTraceId) { + var tracingIdUrlPart = encodeURIComponent(JSON.stringify({"contextTraceId": contextTraceId})); + + var params = $.param({ + project: tenantId, + selectedPath: '/Home/' + tenantId + '/Hybris Profile Developer Tools/trace-explorer/' + tracingIdUrlPart + }); + var href = builderPath + '#?' + params; + scope.lastEventUrl = href; + scope.lastEventId = contextTraceId; + console.log('Last event contextTraceId:', contextTraceId, 'url:', href); + }); + + scope.isGranted = function () { + return ytrackingSvc.isGranted(); + }; + + scope.goToConsentUI = function () { + var consentReference = CookieSvc.getConsentReferenceCookie(); + var consentReferenceToken = CookieSvc.getConsentReferenceTokenCookie(); + + var params = $.param({ + token: consentReferenceToken, + cr: consentReference, + t: tenantId + }); + + var url = consentUrl + '?' + params; + var win = window.open(url, '_blank'); + win.focus(); + }; + + scope.getProfileUrl = function () { + var params = $.param({ + project: tenantId, + selectedPath: '/Home/' + tenantId + '/Hybris Profile Developer Tools/profile-browser/' + CookieSvc.getConsentReferenceCookie() + }); + var href = builderPath + '#?' + params; + return href; + } + + } + }; + }]) + .directive('yProfileHeader', [function () { + return { + restrict: 'E', + templateUrl: 'js/app/shared/templates/profile-header.html', + scope: {}, + link: function (scope, elem, attrs) { + } + } + }]); \ No newline at end of file diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js index 1200e03f0..9457773ee 100644 --- a/public/js/app/shared/directives/y-tracking.js +++ b/public/js/app/shared/directives/y-tracking.js @@ -14,6 +14,82 @@ angular.module('ds.ytracking', []) .constant('yTrackingLocalStorageKey', 'ytracking') + .config(function ($httpProvider) { + $httpProvider.interceptors.push(function ($injector, $rootScope) { + return { + 'request': function (config) { + var svc = $injector.get('ytrackingSvc'); + if (config.method === 'POST' && svc.isGranted()) { + config.headers['X-B3-Sampled'] = 1; + } + return config; + }, + 'response': function (response) { + if (response.headers('Hybris-Context-Trace-Id')) { + $rootScope.$broadcast('tracing:response', response.headers('Hybris-Context-Trace-Id')); + } + return response; + } + } + }); + }) + .directive('ytrackingCookieNotice', ['ytrackingSvc', '$window', '$timeout', function (ytrackingSvc, $window, $timeout) { + return { + restrict: 'E', + scope: {}, + link: function (scope, elem, attrs) { + + function grant() { + ytrackingSvc.grantConsent(); + $timeout(function () { + scope.$apply(); + }) + } + + function revoke() { + ytrackingSvc.revoke(); + $timeout(function () { + scope.$apply(); + }) + } + + window.cookieconsent.initialise({ + palette: { + "popup": { + "background": "#000" + }, + "button": { + "background": "#f1d600" + } + }, + elements: { + messagelink: '{{message}}', + }, + type: "opt-in", + revokable: false, + revokeBtn: '
', + animateRevokable: false, + onInitialise: function (status) { + var didConsent = this.hasConsented(); + if (didConsent) { + grant(); + } + }, + onStatusChange: function (status, chosenBefore) { + var type = this.options.type; + var didConsent = this.hasConsented(); + if (didConsent) { + grant(); + } + if (!didConsent) { + revoke(); + } + } + }); + + } + } + }]) .directive('ytracking', ['ytrackingSvc', '$rootScope', '$document', function (ytrackingSvc, $rootScope, $document) { return { @@ -89,22 +165,44 @@ angular.module('ds.ytracking', []) }; }]) .factory('ytrackingSvc', ['yTrackingLocalStorageKey', '$http', 'localStorage', '$window', '$timeout', 'GlobalData', 'settings', 'appConfig', 'CookieSvc', - function (yTrackingLocalStorageKey, $http, localStorage, $window, $timeout, GlobalData, settings, appConfig, CookieSvc) { + function (yTrackingLocalStorageKey, $http, localStorage, $window, $timeout, GlobalData, settings, appConfig, CookieSvc, $q) { + + + var consentGranted = !!CookieSvc.getConsentReferenceCookie() && !!CookieSvc.getConsentReferenceTokenCookie(); var internalCart = {}; /** - * Url for piwik service. - * appConfig dependency should be refactored out maybe and tenant and domain - * should be provided for example as parameters to ytracking directive so this tracking - * can also work for any other storefront (not just this template) - */ + * Url for piwik service. + * appConfig dependency should be refactored out maybe and tenant and domain + * should be provided for example as parameters to ytracking directive so this tracking + * can also work for any other storefront (not just this template) + */ var apiPath = appConfig.dynamicDomain(); var tenantId = appConfig.storeTenant(); - + var piwikUrl = 'https://' + apiPath + '/hybris/profile-edge/v1' + '/events'; var consentUrl = 'https://' + apiPath + '/hybris/profile-consent/v1/' + tenantId + '/consentReferences'; + + var grantConsent = function () { + makeOptInRequest().success(function (response) { + if (!!response.id) { + CookieSvc.setConsentReferenceCookie(response.id); + CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken); + } + consentGranted = true; + }); + }; + + var isGranted = function () { + return consentGranted; + }; + + var revoke = function () { + consentGranted = false; + }; + var getConsentReference = function () { var consentReferenceCookie = CookieSvc.getConsentReferenceCookie(); if (!!consentReferenceCookie) { @@ -117,7 +215,7 @@ angular.module('ds.ytracking', []) // We could do this in ConfigSvc. This way, consent-reference will be fetched before piwik starts tracking and sending // events. When done in ConfigSvc then the code should probably also detect if ytracking is enabled before attmepting // to fetch the consent-reference. - var makeOptInRequest = function() { + var makeOptInRequest = function () { var req = { method: 'POST', url: consentUrl, @@ -131,8 +229,8 @@ angular.module('ds.ytracking', []) }; /** - * Create object from piwik GET request - */ + * Create object from piwik GET request + */ var getPiwikQueryParameters = function (hash) { var split = hash.split('&'); @@ -149,37 +247,25 @@ angular.module('ds.ytracking', []) }; /** - * Function that process piwik requests - */ + * Function that process piwik requests + */ var processPiwikRequest = function (e) { + if (!consentGranted) { + return; + } //Get object from query parameters var obj = getPiwikQueryParameters(e); - - /* - if no consent reference cookie present, we must get the consent reference before making the - first call to the tracking endpoint - */ - if (!getConsentReference()) { - //noinspection JSUnusedAssignment - makeOptInRequest().success(function (response) { - if (!!response.id) { - CookieSvc.setConsentReferenceCookie(response.id); - } - //Make post request to service - makePiwikRequest(obj); - }); - } - else { + if (getConsentReference()) { //Make post request to service makePiwikRequest(obj); } }; /** - * Function that creates POST request to CDM endpoint - */ + * Function that creates POST request to CDM endpoint + */ var makePiwikRequest = function (obj) { var req = { @@ -213,8 +299,8 @@ angular.module('ds.ytracking', []) }; /** - * Initialization of piwik - */ + * Initialization of piwik + */ var init = function () { $window._paq = $window._paq || []; @@ -238,8 +324,8 @@ angular.module('ds.ytracking', []) }; /** - * Method that is setting current url - */ + * Method that is setting current url + */ var setCustomUrl = function () { if (!!$window._paq) { $window._paq.push(['setCustomUrl', $window.document.URL]); @@ -247,8 +333,8 @@ angular.module('ds.ytracking', []) }; /** - * User viewed product - */ + * User viewed product + */ var setProductViewed = function (sku, name, category, price) { if (!!$window._paq) { //Wait current digest loop to finish, and then when the page is changed update values to PIWIK @@ -268,8 +354,8 @@ angular.module('ds.ytracking', []) }; /** - * User viewed category - */ + * User viewed category + */ var setCategoryViewed = function (categoryPage) { if (!!$window._paq) { //Wait current digest loop to finish, and then when the page is changed update values to PIWIK @@ -288,31 +374,31 @@ angular.module('ds.ytracking', []) }; /** - * User searched event - */ - //Category missing? There is no way to set SearchEvent and SearchNoResultsEvent as action_view + * User searched event + */ + //Category missing? There is no way to set SearchEvent and SearchNoResultsEvent as action_view var searchEvent = function (searchTerm, numberOfResults) { - if (!!$window._paq) { - if (numberOfResults > 0) { - $window._paq.push(['trackSiteSearch', - searchTerm, - false, //This is search category selected in our search. At the moment we dont have this - numberOfResults - ]); - } - else { - $window._paq.push(['trackSiteSearch', - searchTerm, - false, //This is search category selected in our search. At the moment we dont have this - 0 - ]); + if (!!$window._paq) { + if (numberOfResults > 0) { + $window._paq.push(['trackSiteSearch', + searchTerm, + false, //This is search category selected in our search. At the moment we dont have this + numberOfResults + ]); + } + else { + $window._paq.push(['trackSiteSearch', + searchTerm, + false, //This is search category selected in our search. At the moment we dont have this + 0 + ]); + } } - } - }; + }; /** - * User clicked on element with class banner - */ + * User clicked on element with class banner + */ var bannerClick = function (bannerId, url) { if (!!$window._paq) { $timeout(function () { @@ -324,8 +410,8 @@ angular.module('ds.ytracking', []) }; /** - * User updated cart - */ + * User updated cart + */ var cartUpdated = function (cart) { var i = 0; //Check if there is some item that is removed in new cart?? @@ -374,8 +460,8 @@ angular.module('ds.ytracking', []) }; /** - * Function for adding item to cart - */ + * Function for adding item to cart + */ var addEcommerceItem = function (id, name, categoryName, unitPrice, amount) { if (!!$window._paq) { $window._paq.push(['addEcommerceItem', @@ -389,8 +475,8 @@ angular.module('ds.ytracking', []) }; /** - * User opened checkout page - */ + * User opened checkout page + */ var proceedToCheckout = function (cart) { if (!!$window._paq) { $timeout(function () { @@ -404,8 +490,8 @@ angular.module('ds.ytracking', []) }; /** - * User created order - */ + * User created order + */ var orderPlaced = function (orderId, orderGrandTotal, orderSubTotal, taxAmount, shippingAmount, isDiscountOffered) { if (!!$window._paq) { $window._paq.push(['trackEcommerceOrder', @@ -420,15 +506,15 @@ angular.module('ds.ytracking', []) }; /** - * User created order - */ + * User created order + */ var customerLogIn = function () { if (!!$window._paq) { $window._paq.push(['trackPageView', 'CustomerLogin']); } }; - - var getCartId = function(cart) { + + var getCartId = function (cart) { return !!cart.id ? cart.id : ''; }; @@ -443,6 +529,8 @@ angular.module('ds.ytracking', []) searchEvent: searchEvent, bannerClick: bannerClick, proceedToCheckout: proceedToCheckout, - customerLogIn: customerLogIn + customerLogIn: customerLogIn, + grantConsent: grantConsent, + isGranted: isGranted }; }]); diff --git a/public/js/app/shared/services/cookie-svc.js b/public/js/app/shared/services/cookie-svc.js index cdc8f221f..eb2ded275 100644 --- a/public/js/app/shared/services/cookie-svc.js +++ b/public/js/app/shared/services/cookie-svc.js @@ -41,6 +41,13 @@ angular.module('ds.shared') }; }; + var ConsentReferenceTokenCookie = function(consentReferenceToken) { + this.consentReferenceToken = consentReferenceToken; + this.getConsentReferenceToken = function () { + return this.consentReferenceToken; + }; + }; + var CookieSvc = { setLanguageCookie: function (languageCode, expiresIn) { @@ -80,6 +87,20 @@ angular.module('ds.shared') return consentReferenceCookie.consentReference; } return consentReferenceCookie; + }, + + setConsentReferenceTokenCookie: function (consentReferenceToken, expiresIn) { + ipCookie.remove(settings.consentReferenceTokenCookie); + var consentReferenceTokenCookie = new ConsentReferenceTokenCookie(consentReferenceToken); + ipCookie(settings.consentReferenceTokenCookie, JSON.stringify(consentReferenceTokenCookie), { expirationUnit: 'seconds', expires: expiresIn ? expiresIn : defaultExpirySeconds }); + }, + + getConsentReferenceTokenCookie: function() { + var consentReferenceTokenCookie = ipCookie(settings.consentReferenceTokenCookie); + if (consentReferenceTokenCookie) { + return consentReferenceTokenCookie.consentReferenceToken; + } + return consentReferenceTokenCookie; } }; diff --git a/public/js/app/shared/settings.js b/public/js/app/shared/settings.js index fba9e11fa..c3c2450a3 100644 --- a/public/js/app/shared/settings.js +++ b/public/js/app/shared/settings.js @@ -30,6 +30,7 @@ angular.module('ds.shared') languageCookie: 'languageCookie', siteCookie: 'siteCookie', consentReferenceCookie: 'consentReferenceCookie', + consentReferenceTokenCookie: 'consentReferenceTokenCookie', // header keys headers: { diff --git a/public/js/app/shared/templates/profile-header.html b/public/js/app/shared/templates/profile-header.html new file mode 100644 index 000000000..95d9bcfff --- /dev/null +++ b/public/js/app/shared/templates/profile-header.html @@ -0,0 +1,5 @@ +
+

SAP Hybris Profile Quick Start Storefront

+

The purpose of this website is to demonstrate the basic flow in SAP Hybris Profile. It is not a real storefront and it does not communicate with any external e-commerce services. This demo shows how the customer is tracked while browsing the products, and provides quick links to the SAP Hybris Profile related tools.

+

Check our Quick Start Guide

+
\ No newline at end of file diff --git a/public/js/app/shared/templates/profile-toolbox.html b/public/js/app/shared/templates/profile-toolbox.html new file mode 100644 index 000000000..e94c21bbd --- /dev/null +++ b/public/js/app/shared/templates/profile-toolbox.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/public/js/app/shared/templates/top-navigation.html b/public/js/app/shared/templates/top-navigation.html index 14aa6bfae..d626e44ef 100644 --- a/public/js/app/shared/templates/top-navigation.html +++ b/public/js/app/shared/templates/top-navigation.html @@ -1,4 +1,5 @@ + - -
- -
-
@@ -35,88 +29,5 @@ diff --git a/public/less/_global.less b/public/less/_global.less index 15ccffc1d..d37c2c800 100644 --- a/public/less/_global.less +++ b/public/less/_global.less @@ -292,8 +292,8 @@ section{ } .product-list-sort .ui-select-container .btn-default { - background-color: #333331; - border-color: #333331; + background-color: @sapblue; + border-color: @white; color: #fff; } @@ -397,3 +397,7 @@ variant-options p { color: #fff; background: #000; } + +.cc-btn.cc-allow{ + background: @sapyellow!important; +} \ No newline at end of file diff --git a/public/less/_pdp.less b/public/less/_pdp.less index 9268c7aef..3ffd21c03 100644 --- a/public/less/_pdp.less +++ b/public/less/_pdp.less @@ -176,10 +176,6 @@ } - @media (min-width: @screen-md-max) { - padding-top: 65px; - } - section.gray{ .headline{ diff --git a/public/less/_products-list.less b/public/less/_products-list.less index d94593da7..d50802260 100644 --- a/public/less/_products-list.less +++ b/public/less/_products-list.less @@ -96,7 +96,7 @@ section .hero-unit{ .refine-section{ - background-color: @offblack; + background-color: @sapblue; margin: 0; .title{ font:34px @font-family-sans-serif, sans-serif; @@ -312,7 +312,6 @@ section .hero-unit{ } top:-100px; - background: @offblack; margin: 0; -webkit-transition: all .25s ease-out; diff --git a/public/less/_variables.less b/public/less/_variables.less index aba270013..05a79637d 100644 --- a/public/less/_variables.less +++ b/public/less/_variables.less @@ -29,6 +29,9 @@ @lightyellow: #ffffcc; +@sapyellow :#F4B800; +@sapblue: #0076cb; + // COMPONENTS // ========================================================================== diff --git a/public/less/_y-profile.less b/public/less/_y-profile.less new file mode 100644 index 000000000..ae0fa67f9 --- /dev/null +++ b/public/less/_y-profile.less @@ -0,0 +1,53 @@ +.y-profile-toolbox { + position: fixed; + right: 0; + top: 40%; + width: 220px; + color: #fff; + background: @sapyellow; + z-index: 100; + padding: 10px; + + & h4 { + color: #624907; + } + + & ul { + list-style-type: square; + padding-left: 10px; + + & li { + margin-bottom: 5px; + } + } + + & a { + color: #fff; + } + + & .truncate { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + font-weight: 300; + } +} + +.y-profile-header { + width: 100%; + background: @sapyellow; + color: #fff; + padding: 10px; + z-index: 100001; + font-weight: 500; + margin-bottom: 10px; + & h1 { + & a { + color: #624907; + text-decoration: none; + } + + } +} \ No newline at end of file diff --git a/public/less/main.less b/public/less/main.less index 7f3742df0..b3cba8354 100644 --- a/public/less/main.less +++ b/public/less/main.less @@ -26,3 +26,5 @@ @import '_global.less'; + +@import '_y-profile'; From 98b571a586af4b7727d80effe395198bee1d2fc4 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Fri, 6 Oct 2017 11:04:09 +0200 Subject: [PATCH 06/23] Updated service urls, changed consent cookie bar layout --- gruntfile.js | 32 +++++++++++-------- public/js/app/shared/app-config.js | 6 ++-- public/js/app/shared/directives/y-tracking.js | 4 +++ public/less/_global.less | 23 ++++++------- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 2628bad8c..e7dffbcf4 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -70,6 +70,13 @@ module.exports = function (grunt) { } }; + var getCustomerConsentManagerUrl = function (regionCode) { + if(regionCode.toUpperCase === 'EU'){ + return 'https://customer-manager.eu-central.stage.modules.yaas.io' + } + return 'https://customer-manager.us-east.modules.yaas.io' + }; + require('load-grunt-tasks')(grunt); grunt.loadNpmTasks('grunt-text-replace'); grunt.loadNpmTasks('grunt-angular-templates'); //combines templates into cache @@ -108,7 +115,7 @@ module.exports = function (grunt) { }, jshint: { options: { - jshintrc: '.jshintrc' + jshintrc: '.jshintrc', }, all: [ 'gruntfile.js', @@ -179,19 +186,19 @@ module.exports = function (grunt) { replacements: [{ from: /StartDynamicDomain(.*)EndDynamicDomain/g, to: 'StartDynamicDomain*/ \'' + getProdBaseUrl(REGION_CODE) + '\' /*EndDynamicDomain' - }, - { - from: /StartBuilderUrl(.*)EndBuilderUrl/g, - to: 'StartBuilderUrl*/ \'' + 'https://' + PROD_DOMAIN.replace('api', 'builder').replace('{0}', '') + '/\' /*EndBuilderUrl' }, { - from: /StartConsentManagerUrl(.*)EndConsentManagerUrl/g, - to: 'StartConsentManagerUrl*/ \'' + 'https://' + getProdBaseUrl(REGION_CODE).concat('/hybris/customer-consent/v1') + '/\' /*EndConsentManagerUrl' - }, - { - from: /StartPiwikUrl(.*)EndPiwikUrl/g, - to: 'StartPiwikUrl*/ \'' + getPiwikUrl('PROD', REGION_CODE) + '\' /*EndPiwikUrl' - }] + from: /StartBuilderUrl(.*)EndBuilderUrl/g, + to: 'StartBuilderUrl*/ \'' + 'https://' + PROD_DOMAIN.replace('api', 'builder').replace('{0}', '') + '/\' /*EndBuilderUrl' + }, + { + from: /StartConsentManagerUrl(.*)EndConsentManagerUrl/g, + to: 'StartConsentManagerUrl*/ \'' + getCustomerConsentManagerUrl(REGION_CODE) + '/\' /*EndConsentManagerUrl' + }, + { + from: /StartPiwikUrl(.*)EndPiwikUrl/g, + to: 'StartPiwikUrl*/ \'' + getPiwikUrl('PROD', REGION_CODE) + '\' /*EndPiwikUrl' + }] }, projectId: { src: [PROJECT_ID_PATH], @@ -255,7 +262,6 @@ module.exports = function (grunt) { //---Specialized-Build-Behaviors-------------------------------------------------------- grunt.registerTask('singleProjectTask', [ - 'jshint', 'compileTranslations', 'less:dev', 'concurrent:singleProject' //server.js diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index 79bc089c6..f8a031984 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -35,7 +35,7 @@ angular.module('ds.appconfig', []) tenantId = window.location.pathname.substring( 1, pathLength-1 ); } else { // Dynamic ProjectId is configured and replaced by build script, see gruntfile. - tenantId = /*StartProjectId*/ '' /*EndProjectId*/; + tenantId = /*StartProjectId*/ 'storksfront' /*EndProjectId*/; } return tenantId; }, @@ -51,10 +51,10 @@ angular.module('ds.appconfig', []) }, builderURL: function () { - return /*StartBuilderUrl*/ '' /*EndBuilderUrl*/; + return /*StartBuilderUrl*/ 'https://builder.yaas.io/' /*EndBuilderUrl*/; }, consentManagerURL: function () { - return /*StartConsentManagerUrl*/ '' /*EndConsentManagerUrl*/ + return /*StartConsentManagerUrl*/ 'https://customer-manager.us-east.modules.yaas.io/' /*EndConsentManagerUrl*/ } }); diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js index 9457773ee..6bd85814d 100644 --- a/public/js/app/shared/directives/y-tracking.js +++ b/public/js/app/shared/directives/y-tracking.js @@ -62,6 +62,10 @@ angular.module('ds.ytracking', []) "background": "#f1d600" } }, + compliance: { + 'opt-in': '
{{allow}}
', + + }, elements: { messagelink: '{{message}}', }, diff --git a/public/less/_global.less b/public/less/_global.less index d37c2c800..2fb1171df 100644 --- a/public/less/_global.less +++ b/public/less/_global.less @@ -38,7 +38,7 @@ table .error{ background-color: transparent; padding: 0; font-weight: normal; - + } .content-container{ @@ -85,7 +85,7 @@ input::-webkit-inner-spin-button { select{ - // GET RIDD OF DEFAULT SKINNING + // GET RIDD OF DEFAULT SKINNING -webkit-appearance: none; -moz-appearance: none; -webkit-border-radius: 0; @@ -154,16 +154,16 @@ input[type='number']{ .errorPageContainer{ text-align: center; padding-top: 75px; - + .errorTitle{ margin-bottom: 40px; text-transform: uppercase; - + } - + .actionButton{ margin: 30px 0 0 0; - + .btn-secondary{ color: #fff; } @@ -209,7 +209,7 @@ section{ text-transform: uppercase; font-weight: bold; color: #202122; - + } .popoverCloseBtn{ @@ -246,7 +246,7 @@ section{ padding-right: 50px; } - + .shippingZoneModal{ padding-top: 0; padding-bottom: 50px; @@ -258,11 +258,11 @@ section{ border-bottom: none; } } - + .zoneparent{ padding-top: 10px; border-bottom: none !important; - background-color: #ebeced; + background-color: #ebeced; } .zonetitle{ @@ -333,7 +333,7 @@ section{ .search-list-sort .ui-select-container .btn-default-focus { line-height: 20px; margin-top: 9px; - + } .checkout-selector .btn-default-focus { @@ -400,4 +400,5 @@ variant-options p { .cc-btn.cc-allow{ background: @sapyellow!important; + color: #000!important; } \ No newline at end of file From 2e7d07ebf886648f01d6d21722ff316802eefd83 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Fri, 6 Oct 2017 13:40:46 +0200 Subject: [PATCH 07/23] Removed unused dependency --- public/js/app/shared/directives/y-tracking.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js index 6bd85814d..d290b7f1a 100644 --- a/public/js/app/shared/directives/y-tracking.js +++ b/public/js/app/shared/directives/y-tracking.js @@ -169,7 +169,7 @@ angular.module('ds.ytracking', []) }; }]) .factory('ytrackingSvc', ['yTrackingLocalStorageKey', '$http', 'localStorage', '$window', '$timeout', 'GlobalData', 'settings', 'appConfig', 'CookieSvc', - function (yTrackingLocalStorageKey, $http, localStorage, $window, $timeout, GlobalData, settings, appConfig, CookieSvc, $q) { + function (yTrackingLocalStorageKey, $http, localStorage, $window, $timeout, GlobalData, settings, appConfig, CookieSvc) { var consentGranted = !!CookieSvc.getConsentReferenceCookie() && !!CookieSvc.getConsentReferenceTokenCookie(); From 1be7369b4225511afe5e64d46693f1dddda2d4a1 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 13:02:10 +0200 Subject: [PATCH 08/23] Review changes --- gruntfile.js | 56 ++++++++++--------- public/js/app/shared/app-config.js | 12 ++-- public/js/app/shared/directives/y-tracking.js | 16 ++---- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index e7dffbcf4..b5593a6a4 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -30,11 +30,12 @@ module.exports = function (grunt) { //--Set Parameters for Server Configuration---------------------------------------------------- // Read npm argument and set the dynamic server environment or use default configuration. // Syntax example for npm 2.0 parameters: $ npm run-script singleProd -- --pid=xxx --cid=123 --ruri=http://example.com + ENV_ID = grunt.option('env') ? grunt.option('env').toUpperCase() : 'PROD', PROJECT_ID = grunt.option('pid'), - CLIENT_ID = 'NmIaB67D5XXMv9YzPUXT32X4TKQwdCM2', + CLIENT_ID = ENV_ID === 'STAGE' ? 'FACLIjPaW5IrMLyqVJ5XybBweHV9B6jx' : 'NmIaB67D5XXMv9YzPUXT32X4TKQwdCM2', REDIRECT_URI = grunt.option('ruri') || 'http://example.com', USE_HTTPS = grunt.option('https') || false, - REGION_CODE = grunt.option('region') || '', + REGION_CODE = grunt.option('region') ? grunt.option('region').toUpperCase() : '', SERVER_FILES = ['./server.js', './server/singleProdServer.js', './multi-tenant/multi-tenant-server.js'], @@ -46,35 +47,38 @@ module.exports = function (grunt) { TRANSLATE_FILES_PATH = './public/js/app/shared/i18n/lang/lang_*.json', DOMAIN_MSG = 'Could not find environment domain in build parameter. Site is built with default API domain. Use grunt build:prod [:stage] to specify.'; - var getProdBaseUrl = function getProdBaseUrl(regionCode) { - if (!!regionCode && ALLOWED_REGIONS.indexOf(regionCode) < 0) { - grunt.option('force', true); - grunt.warn(REGION_INVALID_MSG); - regionCode = ''; + var getBuilderUrl = function () { + if(ENV_ID === 'STAGE'){ + return 'https://builder.stage.yaas.io' } + return 'https://builder.yaas.io'; + }; - if (!!regionCode) { - return PROD_DOMAIN.replace('{0}', '.' + regionCode); + var getServicesBaseUrl = function () { + if(ENV_ID === 'STAGE'){ + return 'api.stage.yaas.io' } - else { - return PROD_DOMAIN.replace('{0}', ''); + if(REGION_CODE === 'EU') { + return 'api.eu.yaas.io' } + return 'api.yaas.io'; }; - var getPiwikUrl = function (env, regionCode) { - if (env === 'PROD') { - return 'https://' + getProdBaseUrl(regionCode) + '/hybris/profile-edge/v1' + '/events'; - } - else if (env === 'STAGE') { - return 'https://' + STAGE_DOMAIN + '/hybris/profile-edge/v1' + '/events'; - } + + + var getPiwikUrl = function () { + var serviceUrl = getServicesBaseUrl(); + return serviceUrl + '/hybris/profile-edge/v1' + '/events'; }; - var getCustomerConsentManagerUrl = function (regionCode) { - if(regionCode.toUpperCase === 'EU'){ - return 'https://customer-manager.eu-central.stage.modules.yaas.io' + var getCustomerConsentManagerUrl = function () { + if(ENV_ID === 'STAGE'){ + return 'https://profile-manager.us-east.stage.modules.yaas.io' + } + if(REGION_CODE === 'EU'){ + return 'https://profile-manager.eu-central.modules.yaas.io' } - return 'https://customer-manager.us-east.modules.yaas.io' + return 'https://profile-manager.us-east.modules.yaas.io' }; require('load-grunt-tasks')(grunt); @@ -185,19 +189,19 @@ module.exports = function (grunt) { overwrite: true, replacements: [{ from: /StartDynamicDomain(.*)EndDynamicDomain/g, - to: 'StartDynamicDomain*/ \'' + getProdBaseUrl(REGION_CODE) + '\' /*EndDynamicDomain' + to: 'StartDynamicDomain*/ \'' + getServicesBaseUrl() + '\' /*EndDynamicDomain' }, { from: /StartBuilderUrl(.*)EndBuilderUrl/g, - to: 'StartBuilderUrl*/ \'' + 'https://' + PROD_DOMAIN.replace('api', 'builder').replace('{0}', '') + '/\' /*EndBuilderUrl' + to: 'StartBuilderUrl*/ \'' + getBuilderUrl() + '/\' /*EndBuilderUrl' }, { from: /StartConsentManagerUrl(.*)EndConsentManagerUrl/g, - to: 'StartConsentManagerUrl*/ \'' + getCustomerConsentManagerUrl(REGION_CODE) + '/\' /*EndConsentManagerUrl' + to: 'StartConsentManagerUrl*/ \'' + getCustomerConsentManagerUrl() + '/\' /*EndConsentManagerUrl' }, { from: /StartPiwikUrl(.*)EndPiwikUrl/g, - to: 'StartPiwikUrl*/ \'' + getPiwikUrl('PROD', REGION_CODE) + '\' /*EndPiwikUrl' + to: 'StartPiwikUrl*/ \'' + getPiwikUrl() + '\' /*EndPiwikUrl' }] }, projectId: { diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index f8a031984..1b140f996 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -23,7 +23,7 @@ angular.module('ds.appconfig', []) dynamicDomain: function(){ // Dynamic Domain is generated and replaced by build script, see gruntfile. - return /*StartDynamicDomain*/ 'api.yaas.io' /*EndDynamicDomain*/; + return /*StartDynamicDomain*/ '' /*EndDynamicDomain*/; }, storeTenant: function(){ @@ -35,26 +35,26 @@ angular.module('ds.appconfig', []) tenantId = window.location.pathname.substring( 1, pathLength-1 ); } else { // Dynamic ProjectId is configured and replaced by build script, see gruntfile. - tenantId = /*StartProjectId*/ 'storksfront' /*EndProjectId*/; + tenantId = /*StartProjectId*/ '' /*EndProjectId*/; } return tenantId; }, clientId: function() { // Dynamic ClientId is configured and replaced by build script, see gruntfile. - return /*StartClientId*/ 'NmIaB67D5XXMv9YzPUXT32X4TKQwdCM2' /*EndClientId*/; + return /*StartClientId*/ '' /*EndClientId*/; }, redirectURI: function() { // Dynamic RedirectURI is configured and replaced by build script, see gruntfile. - return /*StartRedirectURI*/ 'http://example.com' /*EndRedirectURI*/; + return /*StartRedirectURI*/ '' /*EndRedirectURI*/; }, builderURL: function () { - return /*StartBuilderUrl*/ 'https://builder.yaas.io/' /*EndBuilderUrl*/; + return /*StartBuilderUrl*/ '' /*EndBuilderUrl*/; }, consentManagerURL: function () { - return /*StartConsentManagerUrl*/ 'https://customer-manager.us-east.modules.yaas.io/' /*EndConsentManagerUrl*/ + return /*StartConsentManagerUrl*/ '' /*EndConsentManagerUrl*/ } }); diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js index d290b7f1a..2df4bb468 100644 --- a/public/js/app/shared/directives/y-tracking.js +++ b/public/js/app/shared/directives/y-tracking.js @@ -190,10 +190,11 @@ angular.module('ds.ytracking', []) var grantConsent = function () { + var tenYears = 3600 * 24 * 365 * 10; makeOptInRequest().success(function (response) { if (!!response.id) { - CookieSvc.setConsentReferenceCookie(response.id); - CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken); + CookieSvc.setConsentReferenceCookie(response.id, tenYears); + CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken, tenYears); } consentGranted = true; }); @@ -254,15 +255,8 @@ angular.module('ds.ytracking', []) * Function that process piwik requests */ var processPiwikRequest = function (e) { - if (!consentGranted) { - return; - } - - //Get object from query parameters - var obj = getPiwikQueryParameters(e); - - if (getConsentReference()) { - //Make post request to service + if (consentGranted && getConsentReference()) { + var obj = getPiwikQueryParameters(e); makePiwikRequest(obj); } }; From cc27ff5b0eeb9f8b2412c8dd86ed13aa1f3f252a Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 13:03:30 +0200 Subject: [PATCH 09/23] Review changes --- Readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Readme.md b/Readme.md index 83a8e92e2..fddc24358 100644 --- a/Readme.md +++ b/Readme.md @@ -19,6 +19,11 @@ If your tenant is created in the EU region, you should start the app with: ``` npm start -- --pid= --region=eu ``` + +If your tenant is placed on the STAGE environment, you should start the app with: +``` +npm start -- --pid= --region=stage + ``` ## Using Tracing debug bar should show a link to the Tracing UI. Click a product to create a "Product View" event, and get the new link. The generated links are also visible in the browser developer console. From aa2efe0a7e0c2884314bf25be20c03e79a51d7ab Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 13:54:03 +0200 Subject: [PATCH 10/23] Jshint --- gruntfile.js | 50 ++--- public/js/app/cart/services/cart-service.js | 54 +----- .../controllers/product-detail-ctrl.js | 2 +- .../app/products/services/category-service.js | 25 +-- public/js/app/products/services/price-svc.js | 2 +- .../app/products/services/product-service.js | 6 +- public/js/app/shared/app-config.js | 12 +- public/js/app/shared/directives/y-profile.js | 11 +- public/js/app/shared/directives/y-tracking.js | 171 +++++++++--------- public/js/app/shared/http-proxy.js | 4 +- public/js/app/shared/router.js | 4 +- .../shared/services/configuration-service.js | 2 +- .../app/shipping/services/shipping-service.js | 2 +- 13 files changed, 128 insertions(+), 217 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index b5593a6a4..07b320eed 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -24,9 +24,6 @@ module.exports = function (grunt) { LESS_DIR = 'public/less', TRANSLATIONS_DIR = 'public/js/app/shared/i18n/dev', - ALLOWED_REGIONS = ['eu'], - REGION_INVALID_MSG = 'Selected region is not valid, changing back to default region. Please provide one of valid regions: [' + ALLOWED_REGIONS.join(',') + ']', - //--Set Parameters for Server Configuration---------------------------------------------------- // Read npm argument and set the dynamic server environment or use default configuration. // Syntax example for npm 2.0 parameters: $ npm run-script singleProd -- --pid=xxx --cid=123 --ruri=http://example.com @@ -41,25 +38,22 @@ module.exports = function (grunt) { PROJECT_ID_PATH = './public/js/app/shared/app-config.js', INDEX_PATH = './public/index.html', - PROD_DOMAIN = 'api{0}.yaas.io', - STAGE_DOMAIN = 'api.stage.yaas.io', API_DOMAIN_PATH = './public/js/app/shared/app-config.js', - TRANSLATE_FILES_PATH = './public/js/app/shared/i18n/lang/lang_*.json', - DOMAIN_MSG = 'Could not find environment domain in build parameter. Site is built with default API domain. Use grunt build:prod [:stage] to specify.'; + TRANSLATE_FILES_PATH = './public/js/app/shared/i18n/lang/lang_*.json'; var getBuilderUrl = function () { if(ENV_ID === 'STAGE'){ - return 'https://builder.stage.yaas.io' + return 'https://builder.stage.yaas.io'; } return 'https://builder.yaas.io'; }; var getServicesBaseUrl = function () { if(ENV_ID === 'STAGE'){ - return 'api.stage.yaas.io' + return 'api.stage.yaas.io'; } if(REGION_CODE === 'EU') { - return 'api.eu.yaas.io' + return 'api.eu.yaas.io'; } return 'api.yaas.io'; }; @@ -73,12 +67,12 @@ module.exports = function (grunt) { var getCustomerConsentManagerUrl = function () { if(ENV_ID === 'STAGE'){ - return 'https://profile-manager.us-east.stage.modules.yaas.io' + return 'https://profile-manager.us-east.stage.modules.yaas.io'; } if(REGION_CODE === 'EU'){ - return 'https://profile-manager.eu-central.modules.yaas.io' + return 'https://profile-manager.eu-central.modules.yaas.io'; } - return 'https://profile-manager.us-east.modules.yaas.io' + return 'https://profile-manager.us-east.modules.yaas.io'; }; require('load-grunt-tasks')(grunt); @@ -119,7 +113,7 @@ module.exports = function (grunt) { }, jshint: { options: { - jshintrc: '.jshintrc', + jshintrc: '.jshintrc' }, all: [ 'gruntfile.js', @@ -184,7 +178,7 @@ module.exports = function (grunt) { }, replace: { - prod: { + urls: { src: [API_DOMAIN_PATH, INDEX_PATH], overwrite: true, replacements: [{ @@ -252,20 +246,20 @@ module.exports = function (grunt) { //--Tasks-With-Environment-Parameters---------------------------------------------- // Wrap build task with parameters and dynamic domain warnings. grunt.registerTask('singleProject', 'Build parameters for singleProject build', - function (domainParam) { + function () { grunt.task.run('replace:projectId'); grunt.task.run('replace:clientId'); grunt.task.run('replace:redirectURI'); grunt.task.run('replace:useHttps'); - - runDomainReplace(domainParam); + grunt.task.run('replace:urls'); grunt.task.run('singleProjectTask'); }); //---Specialized-Build-Behaviors-------------------------------------------------------- grunt.registerTask('singleProjectTask', [ + 'jshint', 'compileTranslations', 'less:dev', 'concurrent:singleProject' //server.js @@ -275,24 +269,4 @@ module.exports = function (grunt) { 'copy:translations', //copies translation files from dev folder to lang folder 'json-minify:translations' //minifies JSON translation files ]); - - //--Dynamic-Replacement-Build-Behaviors---------------------------------------------------- - // Read build parameter and set the dynamic domain for environment or give warning message. - - function runDomainReplace(domainParam) { - switch ((domainParam !== undefined) ? domainParam.toLowerCase() : domainParam) { - case 'stage': - grunt.task.run('replace:stage'); - break; - case 'prod': - grunt.task.run('replace:prod'); - break; - default: - grunt.warn(DOMAIN_MSG); - // Default build domain if none is specified. - grunt.task.run('replace:prod'); - } - } - - }; diff --git a/public/js/app/cart/services/cart-service.js b/public/js/app/cart/services/cart-service.js index 614c0d9b0..230ae65bd 100644 --- a/public/js/app/cart/services/cart-service.js +++ b/public/js/app/cart/services/cart-service.js @@ -52,7 +52,7 @@ angular.module('ds.cart') * (Will create a new cart if the current cart hasn't been persisted yet). */ function getOrCreateCart() { - return $q.resolve(cart) + return $q.resolve(cart); } @@ -68,24 +68,14 @@ angular.module('ds.cart') return $q.resolve(cart); } - function mergeAnonymousCartIntoCurrent(anonCart) { - var deferred = $q.defer(); - cart.items = cart.items.concat(anonCart.items); - $rootScope.$emit('cart:updated', {cart: cart}); - - return deferred.promise; - } - /** Creates a new Cart Item. If the cart hasn't been persisted yet, the * cart is created first. */ - function createCartItem(product, prices, qty, config) { + function createCartItem(product, prices, qty) { //product.yrn - var closeCartAfterTimeout = (!_.isUndefined(config.closeCartAfterTimeout)) ? config.closeCartAfterTimeout : undefined; - var cartUpdateMode = (!config.opencartAfterEdit) ? 'auto' : 'manual'; var createItemDef = $q.defer(); - getOrCreateCart().then(function (cartResult) { + getOrCreateCart().then(function () { var price = { 'priceId': prices[0].priceId, @@ -103,9 +93,7 @@ angular.module('ds.cart') var item = new Item(product, price, qty); cart.items.push(item); - - //refreshCart(cart.id, cartUpdateMode, closeCartAfterTimeout); - createItemDef.resolve() + createItemDef.resolve(); }, function () { createItemDef.reject(); @@ -122,20 +110,6 @@ angular.module('ds.cart') }); } - function getIdFromItemYrn(itemYrn) { - if (_.contains(itemYrn, 'product:product')) { - return itemYrn.split(';')[1]; - } else if (_.contains(itemYrn, 'product:product-variant')) { - return itemYrn.split(';')[2]; - } - } - - function getProductIdsFromCart(items) { - return _.map(items, function (item) { - return item.itemYrn ? getIdFromItemYrn(item.itemYrn) : ''; - }).join(','); - } - function reformatCartItems(cart) { var items = []; for (var i = 0; i < cart.items.length; i++) { @@ -212,12 +186,10 @@ angular.module('ds.cart') // create new cart for customer so anon cart can be merged into it _.extend(cart, { - customerId: customerId, currency: GlobalData.getCurrencyId(), siteCode: GlobalData.getSiteCode(), channel: GlobalData.getChannel() }); - ; } else { // anonymous cart was never created cart.currency = GlobalData.getCurrencyId(); cart.siteCode = GlobalData.getSiteCode(); @@ -231,15 +203,9 @@ angular.module('ds.cart') /** Persists the cart instance via PUT request (if qty > 0). Then, reloads that cart * from the API for consistency and in order to display the updated calculations (line item totals, etc). * @return promise to signal success/failure*/ - updateCartItemQty: function (item, qty, config) { - var closeCartAfterTimeout = (!_.isUndefined(config.closeCartAfterTimeout)) ? config.closeCartAfterTimeout : undefined; - var cartUpdateMode = (!config.opencartAfterEdit) ? 'auto' : 'manual'; + updateCartItemQty: function (item, qty) { var updateDef = $q.defer(); if (qty > 0) { - //this is a partial update, so only quantity data is needed - var cartItem = { - quantity: qty - }; refreshCart(cart.id, 'manual'); updateDef.resolve(); } @@ -291,16 +257,16 @@ angular.module('ds.cart') } }, - redeemCoupon: function (coupon, cartId) { + redeemCoupon: function (coupon) { coupon = parseCoupon(coupon); refreshCart(cart.id, 'manual'); }, - removeAllCoupons: function (cartId) { + removeAllCoupons: function () { refreshCart(cart.id, 'manual'); }, - removeCoupon: function (cartId, couponId) { + removeCoupon: function () { refreshCart(cart.id, 'manual'); }, @@ -315,7 +281,7 @@ angular.module('ds.cart') return {taxCalculationApplied: false}; }, - setCalculateTax: function (zipCode, countryCode, cartId) { + setCalculateTax: function () { refreshCart(cart.id, 'manual'); }, @@ -354,7 +320,7 @@ angular.module('ds.cart') zoneId: shippingCostObject.zoneId }; } - return $q.resolve(data) + return $q.resolve(data); } }; diff --git a/public/js/app/products/controllers/product-detail-ctrl.js b/public/js/app/products/controllers/product-detail-ctrl.js index 5adf50fab..5d6f510bc 100644 --- a/public/js/app/products/controllers/product-detail-ctrl.js +++ b/public/js/app/products/controllers/product-detail-ctrl.js @@ -161,7 +161,7 @@ angular.module('ds.products') function filterPricesForVariant(variantId) { return variantPrices.filter(function (price) { - var foundVariantId = price.itemYrn.split(';').pop(); + var foundVariantId = price.yrn.split(';').pop(); return variantId === foundVariantId; }); } diff --git a/public/js/app/products/services/category-service.js b/public/js/app/products/services/category-service.js index 1682888d8..545cae29d 100644 --- a/public/js/app/products/services/category-service.js +++ b/public/js/app/products/services/category-service.js @@ -23,29 +23,6 @@ angular.module('ds.products') var categoryMap; var catList; - function sluggify(name){ - // very simplistic algorithm to handle German Umlaute - should ultimately be provided by server - if(name){ //ensure categories without name are not created - return window.encodeURIComponent(name.toLowerCase().replace(' ', '-').replace('ä', 'ae').replace('ö', 'oe').replace('ü', 'ue').replace('ß', 'ss')); - } - } - - function loadCategory(cat, parent){ - cat.path = []; - if(parent){ - angular.copy(parent.path, cat.path); - } - cat.path.push(cat); - cat.slug = sluggify(cat.name)+'~'+cat.id; - categoryMap[cat.id] = cat; - - if(cat.subcategories){ - angular.forEach(cat.subcategories, function(sub){ - loadCategory(sub, cat); - }); - } - } - function getCategory(slug){ var tildeIndex = slug.indexOf('~'); if(tildeIndex < 0) { @@ -61,7 +38,7 @@ angular.module('ds.products') * @param source - indicates source/reason for update, eg. 'languageUpdate' - see setting.eventSource. * */ getCategories: function () { - return $q.resolve([]) + return $q.resolve([]); }, /** Returns categories from cache.*/ diff --git a/public/js/app/products/services/price-svc.js b/public/js/app/products/services/price-svc.js index 3a7b256f8..f2e5554df 100644 --- a/public/js/app/products/services/price-svc.js +++ b/public/js/app/products/services/price-svc.js @@ -15,7 +15,7 @@ angular.module('ds.products') .factory('PriceSvc', ['$q', '$http', function ($q, $http) { - var getPrices = function (parms) { + var getPrices = function () { return $http.get('prices.json').then(function (response) { return response.data; }); diff --git a/public/js/app/products/services/product-service.js b/public/js/app/products/services/product-service.js index 0e66f380a..2130e8481 100644 --- a/public/js/app/products/services/product-service.js +++ b/public/js/app/products/services/product-service.js @@ -27,7 +27,7 @@ angular.module('ds.products') }); return { - queryProductList: function (parms) { + queryProductList: function () { return listPromise.then(function (data) { return data; }); @@ -40,7 +40,7 @@ angular.module('ds.products') getProductVariants: function (params) { return listPromise.then(function (data) { return _.filter(data, function (item) { - item.id === params.productId; + return item.id === params.productId; }); }); }, @@ -51,7 +51,7 @@ angular.module('ds.products') prices: _.filter(results[1], function (item) { return item.productId === params.productId; }) - } + }; }); } }; diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index 1b140f996..f541bd8d4 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -23,7 +23,7 @@ angular.module('ds.appconfig', []) dynamicDomain: function(){ // Dynamic Domain is generated and replaced by build script, see gruntfile. - return /*StartDynamicDomain*/ '' /*EndDynamicDomain*/; + return /*StartDynamicDomain*/ 'api.stage.yaas.io' /*EndDynamicDomain*/; }, storeTenant: function(){ @@ -35,26 +35,26 @@ angular.module('ds.appconfig', []) tenantId = window.location.pathname.substring( 1, pathLength-1 ); } else { // Dynamic ProjectId is configured and replaced by build script, see gruntfile. - tenantId = /*StartProjectId*/ '' /*EndProjectId*/; + tenantId = /*StartProjectId*/ 'storksfront' /*EndProjectId*/; } return tenantId; }, clientId: function() { // Dynamic ClientId is configured and replaced by build script, see gruntfile. - return /*StartClientId*/ '' /*EndClientId*/; + return /*StartClientId*/ 'FACLIjPaW5IrMLyqVJ5XybBweHV9B6jx' /*EndClientId*/; }, redirectURI: function() { // Dynamic RedirectURI is configured and replaced by build script, see gruntfile. - return /*StartRedirectURI*/ '' /*EndRedirectURI*/; + return /*StartRedirectURI*/ 'http://example.com' /*EndRedirectURI*/; }, builderURL: function () { - return /*StartBuilderUrl*/ '' /*EndBuilderUrl*/; + return /*StartBuilderUrl*/ 'https://builder.stage.yaas.io/' /*EndBuilderUrl*/; }, consentManagerURL: function () { - return /*StartConsentManagerUrl*/ '' /*EndConsentManagerUrl*/ + return /*StartConsentManagerUrl*/ 'https://profile-manager.us-east.stage.modules.yaas.io/' /*EndConsentManagerUrl*/; } }); diff --git a/public/js/app/shared/directives/y-profile.js b/public/js/app/shared/directives/y-profile.js index c51c7358b..7cc969143 100644 --- a/public/js/app/shared/directives/y-profile.js +++ b/public/js/app/shared/directives/y-profile.js @@ -19,13 +19,12 @@ angular.module('ds.yprofile', []) scope: {}, templateUrl: 'js/app/shared/templates/profile-toolbox.html', link: function (scope) { - var apiPath = appConfig.dynamicDomain(); var tenantId = appConfig.storeTenant(); var builderPath = appConfig.builderURL(); var consentUrl = appConfig.consentManagerURL(); scope.$on('tracing:response', function (event, contextTraceId) { - var tracingIdUrlPart = encodeURIComponent(JSON.stringify({"contextTraceId": contextTraceId})); + var tracingIdUrlPart = encodeURIComponent(JSON.stringify({'contextTraceId': contextTraceId})); var params = $.param({ project: tenantId, @@ -63,7 +62,7 @@ angular.module('ds.yprofile', []) }); var href = builderPath + '#?' + params; return href; - } + }; } }; @@ -72,8 +71,6 @@ angular.module('ds.yprofile', []) return { restrict: 'E', templateUrl: 'js/app/shared/templates/profile-header.html', - scope: {}, - link: function (scope, elem, attrs) { - } - } + scope: {} + }; }]); \ No newline at end of file diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js index 2df4bb468..96749eadf 100644 --- a/public/js/app/shared/directives/y-tracking.js +++ b/public/js/app/shared/directives/y-tracking.js @@ -30,57 +30,55 @@ angular.module('ds.ytracking', []) } return response; } - } + }; }); }) .directive('ytrackingCookieNotice', ['ytrackingSvc', '$window', '$timeout', function (ytrackingSvc, $window, $timeout) { return { restrict: 'E', scope: {}, - link: function (scope, elem, attrs) { + link: function (scope) { function grant() { ytrackingSvc.grantConsent(); $timeout(function () { scope.$apply(); - }) + }); } function revoke() { ytrackingSvc.revoke(); $timeout(function () { scope.$apply(); - }) + }); } window.cookieconsent.initialise({ palette: { - "popup": { - "background": "#000" + popup: { + background: '#000' }, - "button": { - "background": "#f1d600" + button: { + background: '#f1d600' } }, compliance: { - 'opt-in': '
{{allow}}
', - + 'opt-in': '
{{allow}}
' }, elements: { - messagelink: '{{message}}', + messagelink: '{{message}}' }, - type: "opt-in", + type: 'opt-in', revokable: false, revokeBtn: '
', animateRevokable: false, - onInitialise: function (status) { + onInitialise: function () { var didConsent = this.hasConsented(); if (didConsent) { grant(); } }, - onStatusChange: function (status, chosenBefore) { - var type = this.options.type; + onStatusChange: function () { var didConsent = this.hasConsented(); if (didConsent) { grant(); @@ -92,7 +90,7 @@ angular.module('ds.ytracking', []) }); } - } + }; }]) .directive('ytracking', ['ytrackingSvc', '$rootScope', '$document', function (ytrackingSvc, $rootScope, $document) { @@ -189,34 +187,6 @@ angular.module('ds.ytracking', []) var consentUrl = 'https://' + apiPath + '/hybris/profile-consent/v1/' + tenantId + '/consentReferences'; - var grantConsent = function () { - var tenYears = 3600 * 24 * 365 * 10; - makeOptInRequest().success(function (response) { - if (!!response.id) { - CookieSvc.setConsentReferenceCookie(response.id, tenYears); - CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken, tenYears); - } - consentGranted = true; - }); - }; - - var isGranted = function () { - return consentGranted; - }; - - var revoke = function () { - consentGranted = false; - }; - - var getConsentReference = function () { - var consentReferenceCookie = CookieSvc.getConsentReferenceCookie(); - if (!!consentReferenceCookie) { - return consentReferenceCookie; - } else { - return ''; - } - }; - // We could do this in ConfigSvc. This way, consent-reference will be fetched before piwik starts tracking and sending // events. When done in ConfigSvc then the code should probably also detect if ytracking is enabled before attmepting // to fetch the consent-reference. @@ -233,31 +203,12 @@ angular.module('ds.ytracking', []) return $http(req); }; - /** - * Create object from piwik GET request - */ - var getPiwikQueryParameters = function (hash) { - var split = hash.split('&'); - - var obj = {}; - for (var i = 0; i < split.length; i++) { - var kv = split[i].split('='); - obj[kv[0]] = decodeURIComponent(kv[1] ? kv[1].replace(/\+/g, ' ') : kv[1]); - } - - //Set date for this request to current datetime when request processed. Needed from CDM for order of events. - obj.date = new Date().getTime(); - - return obj; - }; - - /** - * Function that process piwik requests - */ - var processPiwikRequest = function (e) { - if (consentGranted && getConsentReference()) { - var obj = getPiwikQueryParameters(e); - makePiwikRequest(obj); + var getConsentReference = function () { + var consentReferenceCookie = CookieSvc.getConsentReferenceCookie(); + if (!!consentReferenceCookie) { + return consentReferenceCookie; + } else { + return ''; } }; @@ -296,6 +247,50 @@ angular.module('ds.ytracking', []) }; + var grantConsent = function () { + var tenYears = 3600 * 24 * 365 * 10; + makeOptInRequest().success(function (response) { + if (!!response.id) { + CookieSvc.setConsentReferenceCookie(response.id, tenYears); + CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken, tenYears); + } + consentGranted = true; + }); + }; + + var isGranted = function () { + return consentGranted; + }; + + + /** + * Create object from piwik GET request + */ + var getPiwikQueryParameters = function (hash) { + var split = hash.split('&'); + + var obj = {}; + for (var i = 0; i < split.length; i++) { + var kv = split[i].split('='); + obj[kv[0]] = decodeURIComponent(kv[1] ? kv[1].replace(/\+/g, ' ') : kv[1]); + } + + //Set date for this request to current datetime when request processed. Needed from CDM for order of events. + obj.date = new Date().getTime(); + + return obj; + }; + + /** + * Function that process piwik requests + */ + var processPiwikRequest = function (e) { + if (consentGranted && getConsentReference()) { + var obj = getPiwikQueryParameters(e); + makePiwikRequest(obj); + } + }; + /** * Initialization of piwik */ @@ -407,6 +402,26 @@ angular.module('ds.ytracking', []) } }; + var getCartId = function (cart) { + return !!cart.id ? cart.id : ''; + }; + + + /** + * Function for adding item to cart + */ + var addEcommerceItem = function (id, name, categoryName, unitPrice, amount) { + if (!!$window._paq) { + $window._paq.push(['addEcommerceItem', + id, // (required) SKU: Product unique identifier + name, // (optional) Product name + categoryName, // (optional) Product category. You can also specify an array of up to 5 categories eg. ["Books", "New releases", "Biography"] + unitPrice, // (recommended) Product price + amount // (optional, default to 1) Product quantity + ]); + } + }; + /** * User updated cart */ @@ -457,20 +472,6 @@ angular.module('ds.ytracking', []) internalCart = cart; }; - /** - * Function for adding item to cart - */ - var addEcommerceItem = function (id, name, categoryName, unitPrice, amount) { - if (!!$window._paq) { - $window._paq.push(['addEcommerceItem', - id, // (required) SKU: Product unique identifier - name, // (optional) Product name - categoryName, // (optional) Product category. You can also specify an array of up to 5 categories eg. ["Books", "New releases", "Biography"] - unitPrice, // (recommended) Product price - amount // (optional, default to 1) Product quantity - ]); - } - }; /** * User opened checkout page @@ -512,10 +513,6 @@ angular.module('ds.ytracking', []) } }; - var getCartId = function (cart) { - return !!cart.id ? cart.id : ''; - }; - return { cartUpdated: cartUpdated, init: init, diff --git a/public/js/app/shared/http-proxy.js b/public/js/app/shared/http-proxy.js index 3ec0e275f..3ae038f52 100644 --- a/public/js/app/shared/http-proxy.js +++ b/public/js/app/shared/http-proxy.js @@ -22,8 +22,8 @@ angular.module('ds.httpproxy', []) request: function (config) { document.body.style.cursor = 'wait'; // skip html requests as well as anonymous login URL - if (config.url.indexOf('templates') < 0 && config.url.indexOf(siteConfig.apis.customerlogin.baseUrl) < 0 - && config.url.indexOf('lang_') < 0) { + if (config.url.indexOf('templates') < 0 && config.url.indexOf(siteConfig.apis.customerlogin.baseUrl) < 0 && + config.url.indexOf('lang_') < 0) { var token = TokenSvc.getToken().getAccessToken(); if (token) { diff --git a/public/js/app/shared/router.js b/public/js/app/shared/router.js index a7bf73248..23baceef3 100644 --- a/public/js/app/shared/router.js +++ b/public/js/app/shared/router.js @@ -145,14 +145,14 @@ angular.module('ds.router', []) }], variants: ['$stateParams', 'initialized', 'ProductSvc', 'SiteConfigSvc', - function ($stateParams, initialized, ProductSvc, SiteConfigSvc) { + function ($stateParams, initialized, ProductSvc) { if (initialized) { return ProductSvc.getProductVariants($stateParams); } }], variantPrices: ['$stateParams', 'initialized', 'PriceSvc', 'SiteConfigSvc', 'GlobalData', - function ($stateParams, initialized, PriceSvc, SiteConfigSvc, GlobalData) { + function ($stateParams, initialized, PriceSvc) { if (initialized) { return PriceSvc.getPrices($stateParams); } diff --git a/public/js/app/shared/services/configuration-service.js b/public/js/app/shared/services/configuration-service.js index 11859732e..2824f7c39 100644 --- a/public/js/app/shared/services/configuration-service.js +++ b/public/js/app/shared/services/configuration-service.js @@ -105,7 +105,7 @@ angular.module('ds.shared') loadConfiguration(GlobalData.store.tenant).then(function () { var siteSettingPromise = $http.get('sites.json').then(function (response) { - return response.data[0] + return response.data[0]; }); siteSettingPromise.then(function (site) { diff --git a/public/js/app/shipping/services/shipping-service.js b/public/js/app/shipping/services/shipping-service.js index 5faf62140..579ab9955 100644 --- a/public/js/app/shipping/services/shipping-service.js +++ b/public/js/app/shipping/services/shipping-service.js @@ -32,7 +32,7 @@ angular.module('ds.checkout') }; var getSiteShippingZones = function () { - return $q.resolve([]) + return $q.resolve([]); }; var getMinimumShippingCost = function (costs) { From 3c85a09b81ceaa09e125f90d3330f081c724a28e Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 14:00:51 +0200 Subject: [PATCH 11/23] Review changes --- public/js/app/shared/app-config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index f541bd8d4..b2f2c29b3 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -35,14 +35,14 @@ angular.module('ds.appconfig', []) tenantId = window.location.pathname.substring( 1, pathLength-1 ); } else { // Dynamic ProjectId is configured and replaced by build script, see gruntfile. - tenantId = /*StartProjectId*/ 'storksfront' /*EndProjectId*/; + tenantId = /*StartProjectId*/ '' /*EndProjectId*/; } return tenantId; }, clientId: function() { // Dynamic ClientId is configured and replaced by build script, see gruntfile. - return /*StartClientId*/ 'FACLIjPaW5IrMLyqVJ5XybBweHV9B6jx' /*EndClientId*/; + return /*StartClientId*/ '' /*EndClientId*/; }, redirectURI: function() { @@ -51,10 +51,10 @@ angular.module('ds.appconfig', []) }, builderURL: function () { - return /*StartBuilderUrl*/ 'https://builder.stage.yaas.io/' /*EndBuilderUrl*/; + return /*StartBuilderUrl*/ '' /*EndBuilderUrl*/; }, consentManagerURL: function () { - return /*StartConsentManagerUrl*/ 'https://profile-manager.us-east.stage.modules.yaas.io/' /*EndConsentManagerUrl*/; + return /*StartConsentManagerUrl*/ '' /*EndConsentManagerUrl*/; } }); From 241e48a0eb6615fe9124272c5b5f811f6575d9b5 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 14:01:11 +0200 Subject: [PATCH 12/23] Review changes --- public/js/app/shared/app-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/app/shared/app-config.js b/public/js/app/shared/app-config.js index b2f2c29b3..60591fdaa 100644 --- a/public/js/app/shared/app-config.js +++ b/public/js/app/shared/app-config.js @@ -23,7 +23,7 @@ angular.module('ds.appconfig', []) dynamicDomain: function(){ // Dynamic Domain is generated and replaced by build script, see gruntfile. - return /*StartDynamicDomain*/ 'api.stage.yaas.io' /*EndDynamicDomain*/; + return /*StartDynamicDomain*/ '' /*EndDynamicDomain*/; }, storeTenant: function(){ From 8384e4694dcb256fbe04b69b92dd1608f2058e5d Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 14:08:32 +0200 Subject: [PATCH 13/23] Fixed command --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index fddc24358..1e1329bfb 100644 --- a/Readme.md +++ b/Readme.md @@ -22,7 +22,7 @@ npm start -- --pid= --region=eu If your tenant is placed on the STAGE environment, you should start the app with: ``` -npm start -- --pid= --region=stage +npm start -- --pid= --env=stage ``` ## Using From 11227cd436f1a834da6cffe223f03ccc86cbc296 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 9 Oct 2017 14:46:55 +0200 Subject: [PATCH 14/23] Added warning message --- gruntfile.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/gruntfile.js b/gruntfile.js index 07b320eed..18c06510a 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -18,12 +18,25 @@ module.exports = function (grunt) { var host = process.env.VCAP_APP_HOST || process.env.HOST || '0.0.0.0'; var port = process.env.VCAP_APP_PORT || process.env.PORT || 9000; //process.env.VCAP_APP_PORT is deprecated + + var ALLOWED_REGIONS = ['eu', 'us'], + REGION_INVALID_MSG = 'Selected region is not valid. Please provide one of valid regions: [' + ALLOWED_REGIONS.join(',') + ']'; + + var getRegion = function () { + var region = grunt.option('region'); + if (region && ALLOWED_REGIONS.indexOf(region) < 0) { + grunt.warn(REGION_INVALID_MSG); + return 'US'; + } + return grunt.option('region') ? grunt.option('region').toUpperCase() : 'US'; + }; + // Configuration Variables. var JS_DIR = 'public/js/app', LESS_DIR = 'public/less', TRANSLATIONS_DIR = 'public/js/app/shared/i18n/dev', - + //--Set Parameters for Server Configuration---------------------------------------------------- // Read npm argument and set the dynamic server environment or use default configuration. // Syntax example for npm 2.0 parameters: $ npm run-script singleProd -- --pid=xxx --cid=123 --ruri=http://example.com @@ -32,7 +45,7 @@ module.exports = function (grunt) { CLIENT_ID = ENV_ID === 'STAGE' ? 'FACLIjPaW5IrMLyqVJ5XybBweHV9B6jx' : 'NmIaB67D5XXMv9YzPUXT32X4TKQwdCM2', REDIRECT_URI = grunt.option('ruri') || 'http://example.com', USE_HTTPS = grunt.option('https') || false, - REGION_CODE = grunt.option('region') ? grunt.option('region').toUpperCase() : '', + REGION_CODE = getRegion(), SERVER_FILES = ['./server.js', './server/singleProdServer.js', './multi-tenant/multi-tenant-server.js'], @@ -42,34 +55,33 @@ module.exports = function (grunt) { TRANSLATE_FILES_PATH = './public/js/app/shared/i18n/lang/lang_*.json'; var getBuilderUrl = function () { - if(ENV_ID === 'STAGE'){ + if (ENV_ID === 'STAGE') { return 'https://builder.stage.yaas.io'; } return 'https://builder.yaas.io'; }; var getServicesBaseUrl = function () { - if(ENV_ID === 'STAGE'){ + if (ENV_ID === 'STAGE') { return 'api.stage.yaas.io'; } - if(REGION_CODE === 'EU') { + if (REGION_CODE === 'EU') { return 'api.eu.yaas.io'; } return 'api.yaas.io'; }; - var getPiwikUrl = function () { var serviceUrl = getServicesBaseUrl(); return serviceUrl + '/hybris/profile-edge/v1' + '/events'; }; var getCustomerConsentManagerUrl = function () { - if(ENV_ID === 'STAGE'){ + if (ENV_ID === 'STAGE') { return 'https://profile-manager.us-east.stage.modules.yaas.io'; } - if(REGION_CODE === 'EU'){ + if (REGION_CODE === 'EU') { return 'https://profile-manager.eu-central.modules.yaas.io'; } return 'https://profile-manager.us-east.modules.yaas.io'; @@ -191,7 +203,7 @@ module.exports = function (grunt) { }, { from: /StartConsentManagerUrl(.*)EndConsentManagerUrl/g, - to: 'StartConsentManagerUrl*/ \'' + getCustomerConsentManagerUrl() + '/\' /*EndConsentManagerUrl' + to: 'StartConsentManagerUrl*/ \'' + getCustomerConsentManagerUrl() + '/\' /*EndConsentManagerUrl' }, { from: /StartPiwikUrl(.*)EndPiwikUrl/g, From f1ab7e822ff61a501bf6448b60e7f2f6eb01f229 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Wed, 11 Oct 2017 09:46:56 +0200 Subject: [PATCH 15/23] Fixed Quick Start Guide url --- public/js/app/shared/templates/profile-header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/app/shared/templates/profile-header.html b/public/js/app/shared/templates/profile-header.html index 95d9bcfff..d8f2d0c82 100644 --- a/public/js/app/shared/templates/profile-header.html +++ b/public/js/app/shared/templates/profile-header.html @@ -1,5 +1,5 @@

SAP Hybris Profile Quick Start Storefront

The purpose of this website is to demonstrate the basic flow in SAP Hybris Profile. It is not a real storefront and it does not communicate with any external e-commerce services. This demo shows how the customer is tracked while browsing the products, and provides quick links to the SAP Hybris Profile related tools.

-

Check our Quick Start Guide

+

Check our Quick Start Guide

\ No newline at end of file From 25ee4d967256ae1a757785ce09448058ba2c3498 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Wed, 11 Oct 2017 11:39:27 +0200 Subject: [PATCH 16/23] Added quickstart guide --- Quickstart.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Quickstart.md diff --git a/Quickstart.md b/Quickstart.md new file mode 100644 index 000000000..c0cd1dec6 --- /dev/null +++ b/Quickstart.md @@ -0,0 +1,102 @@ +# Profile Quick Start Guide +## Step 1: Create and configure a YaaS account' + +This section explains how to create a YaaS account, an organization, and a project. + +### Create a YaaS account + +To create a YaaS account, follow these steps: +1. Go to the YaaS main page. +2. Select **Register for free** and follow the steps provided in the instructions to complete the registration. + +### Create an organization + +When your registration is complete, you can create your own organization. + +> If you wish to start using SAP Hybris Profile, creating an organization is mandatory. + +To set up your organization, follow these steps: +1. Sign in to the https://builder.yaas.io using your YaaS account. +2. Select **Create your own organization**. +3. Provide the name of your organization. +4. Select your country of origin. +5. Click **Try for free** to create a non-commercial organization. + +### Create a project + +To start using SAP Hybris Profile, you need a project, also known as a tenant. Follow these steps to set up your project: +1. In the Builder, select your organization and click **Create your first project**. +2. Complete the **Display Name** and **Identifier** fields to define your project. Optionally, provide the description of your project. +3. Click **Save** to create your own project. + +## Step 2: Subscribe to packages + +To use SAP Hybris Profile as described in this document, subscribe your project to the following packages: +1. Profile Core Services +2. Profile Services for Commerce + +To subscribe to the Profile Core Services and Profile Services for Commerce packages, follow these steps: +1. In the Builder, select Your Organization > Your Project. +2. Navigate to Administration > Subscriptions. +3. Click Subscribe. The YaaS Market opens. +4. Find the Profile Core Services and Profile Services for Commerce packages. +5. To subscribe to the packages, click on the icons representing each, and select Subscribe now. +6. Select a project to subscribe to the packages, and click Continue. +7. Review your order and click Subscribe now. + +## Step 3: Set up a simplified storefront + +To start using SAP Hybris Profile for the purpose described in this document, you need a storefront. For your convenience, it is recommended that you download and install a simplified storefront version created specially to introduce you to the basic SAP Hybris Profile functionality. The simplified storefront project is located here. + +To download and install the simplified storefront, and parametrize it with your tenant, execute the following commands. Replace the **{tenant}** parameter with your tenant identifier. + +> You must have npm and Git installed locally. For more information about installing Git. + +``` +git clone -b SAP_Hybris_Profile_Quickstart https://github.com/hybris/yaas-storefront-profile-quickstart.git +cd yaas-storefront-profile-quickstart +npm install +npm start -- --pid={tenant} +``` + +If you are located in the EU region, modify the last command to look as follows: + +``` +npm start -- --pid={tenant} --region=eu +``` + +After executing these commands, open the storefront in your browser by clicking the following link: http://localhost:9000. + +> Use this storefront to send basic events such as `PageViewEvent` and `ProductDetailPageViewEvent` to SAP Hybris Profile, track the events in the Trace Explorer, and see the visual representation of the graph changes caused by the events in the Graph Explorer. For more complex operations, use the [YaaS Storefront](https://github.com/SAP/yaas-storefront). For more details about the YaaS Storefront, and how to set it up, see the Getting Started guide. + + +## Step 4: Send events + +Now that the simplified storefront is installed locally on your machine and ready to use, you can send events to SAP Hybris Profile just by clicking on the storefront. + +> The simplified storefront supports two event types only: `PageViewEvent` and `ProductDetailPageViewEvent`. + +Interactions with the storefront create events. Open or refresh the storefront website to create a `PageViewEvent`, or click on the product of your choice to create a `ProductDetailPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the graph, for example by creating nodes or relationships. + + +## Step 5: Track events + +You can track the events sent from the storefront to SAP Hybris Profile in the Trace Explorer. Tracking allows you to collect details about the exact time of calls between services, and the duration of specific operations. Additionally, it allows you to detect any latency problems. + +> In the simplified storefront version, tracing is enabled by default. Therefore, no action from you is required to activate event tracking. + +To navigate to the Trace Explorer, click a **contextTraceId** link that appears at the top of the screen each time the storefront sends an event to SAP Hybris Profile. + +## Step 6: View the graph changes in the Graph Explorer + +The events that you send to SAP Hybris Profile from your storefront trigger the enrichers. Depending on the event type, the triggered enrichers introduce various changes to the profile graph. + +For example, by clicking on a product in the storefront, you send the `ProductDetailPageViewEvent` to SAP Hybris Profile. This event triggers the specified enricher, which modifies the graph database by creating nodes and/or relationships, such as a `VIEWED` relationship between the `Session` and the `Product` nodes. + +Follow these instructions to see a visual representation of the graph modifications associated with the Profile node. +1. Go to the Trace Explorer to see the initial logs from context adapters that pre-process the event sent from the storefront before dispatching it to the enrichers. +2. In the **Context Transition** component of the Trace Explorer, click one of the displayed links, which represent a given **contextTraceId**. Each storefront event triggers various enrichers. What you see is a list of enricher-generated logs resulting from the storefront activity. +3. Find the log with a message about node creation that displays the schema `nodes/commerce/Session`. The log indicates that an enricher created a node defined by the schema `nodes/commerce/Session`. +4. Click the `nodes/commerce/Session` link to go to the **Graph Explorer** and view the created nodes and relations in a visualized form. +5. Find the node **Identity**, and double-click it. Double clicking loads more data dynamically. +6. See the node for **Profile**. Hover your mouse over the node to display the node's details, or click the node to display them under the graph. \ No newline at end of file From da6553e99fbfa444671bafefd82873219b23cc73 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Wed, 11 Oct 2017 11:46:36 +0200 Subject: [PATCH 17/23] Added quickstart guide --- Quickstart.md | 2 +- Readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Quickstart.md b/Quickstart.md index c0cd1dec6..dd4c8d021 100644 --- a/Quickstart.md +++ b/Quickstart.md @@ -1,5 +1,5 @@ # Profile Quick Start Guide -## Step 1: Create and configure a YaaS account' +## Step 1: Create and configure a YaaS account This section explains how to create a YaaS account, an organization, and a project. diff --git a/Readme.md b/Readme.md index 1e1329bfb..7c4b0c899 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,7 @@ ## Description This project aims to introduce the user to working with the Profile system by showing the user how actions such as page enter, or product view are processed by the system. This project is based on the [YaaS Storefront](https://github.com/SAP/yaas-storefront), but it does not use the commerce packages, so it does not need any additional configuration before start. The Tracing Debug mode is enabled by default, which makes it possible to track the events sent to the system. ## Documentation -To learn more about how to start working with the Profile system, follow this guide: https://devportal.yaas.io/solutions/saphybrisprofile/index.html#Quickstartguide +To learn more about how to start working with the Profile system, follow this guide: https://github.com/hybris/yaas-storefront-profile-quickstart/blob/SAP_Hybris_Profile_Quickstart/Quickstart.md ## Running First, install the project dependencies. ``` From e4c7b9540e026ef0bfd418fb56531402dd761550 Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Mon, 16 Oct 2017 10:51:40 +0200 Subject: [PATCH 18/23] - Fixed getting consent reference (blocked getting consent after each page refresh) - Added link to profile visualization ui --- public/js/app/shared/directives/y-profile.js | 25 ++++++++++++++----- public/js/app/shared/directives/y-tracking.js | 17 ++++++------- .../app/shared/templates/profile-toolbox.html | 1 + 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/public/js/app/shared/directives/y-profile.js b/public/js/app/shared/directives/y-profile.js index 7cc969143..7ed0600b3 100644 --- a/public/js/app/shared/directives/y-profile.js +++ b/public/js/app/shared/directives/y-profile.js @@ -36,11 +36,7 @@ angular.module('ds.yprofile', []) console.log('Last event contextTraceId:', contextTraceId, 'url:', href); }); - scope.isGranted = function () { - return ytrackingSvc.isGranted(); - }; - - scope.goToConsentUI = function () { + function getCustomerConsentManagementServiceParams() { var consentReference = CookieSvc.getConsentReferenceCookie(); var consentReferenceToken = CookieSvc.getConsentReferenceTokenCookie(); @@ -49,12 +45,30 @@ angular.module('ds.yprofile', []) cr: consentReference, t: tenantId }); + return params; + } + + + scope.isGranted = function () { + return ytrackingSvc.isGranted(); + }; + + scope.goToConsentUI = function () { + var params = getCustomerConsentManagementServiceParams(); var url = consentUrl + '?' + params; var win = window.open(url, '_blank'); win.focus(); }; + scope.goToVisualizationUI = function () { + var params = getCustomerConsentManagementServiceParams(); + + var url = consentUrl + '/profile?' + params; + var win = window.open(url, '_blank'); + win.focus(); + }; + scope.getProfileUrl = function () { var params = $.param({ project: tenantId, @@ -63,7 +77,6 @@ angular.module('ds.yprofile', []) var href = builderPath + '#?' + params; return href; }; - } }; }]) diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js index 96749eadf..1c733b689 100644 --- a/public/js/app/shared/directives/y-tracking.js +++ b/public/js/app/shared/directives/y-tracking.js @@ -169,8 +169,9 @@ angular.module('ds.ytracking', []) .factory('ytrackingSvc', ['yTrackingLocalStorageKey', '$http', 'localStorage', '$window', '$timeout', 'GlobalData', 'settings', 'appConfig', 'CookieSvc', function (yTrackingLocalStorageKey, $http, localStorage, $window, $timeout, GlobalData, settings, appConfig, CookieSvc) { - - var consentGranted = !!CookieSvc.getConsentReferenceCookie() && !!CookieSvc.getConsentReferenceTokenCookie(); + var isGranted = function () { + return !!CookieSvc.getConsentReferenceCookie() && !!CookieSvc.getConsentReferenceTokenCookie(); + }; var internalCart = {}; @@ -187,6 +188,7 @@ angular.module('ds.ytracking', []) var consentUrl = 'https://' + apiPath + '/hybris/profile-consent/v1/' + tenantId + '/consentReferences'; + // We could do this in ConfigSvc. This way, consent-reference will be fetched before piwik starts tracking and sending // events. When done in ConfigSvc then the code should probably also detect if ytracking is enabled before attmepting // to fetch the consent-reference. @@ -248,21 +250,18 @@ angular.module('ds.ytracking', []) }; var grantConsent = function () { + if (isGranted()) { + return; + } var tenYears = 3600 * 24 * 365 * 10; makeOptInRequest().success(function (response) { if (!!response.id) { CookieSvc.setConsentReferenceCookie(response.id, tenYears); CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken, tenYears); } - consentGranted = true; }); }; - var isGranted = function () { - return consentGranted; - }; - - /** * Create object from piwik GET request */ @@ -285,7 +284,7 @@ angular.module('ds.ytracking', []) * Function that process piwik requests */ var processPiwikRequest = function (e) { - if (consentGranted && getConsentReference()) { + if (isGranted() && getConsentReference()) { var obj = getPiwikQueryParameters(e); makePiwikRequest(obj); } diff --git a/public/js/app/shared/templates/profile-toolbox.html b/public/js/app/shared/templates/profile-toolbox.html index e94c21bbd..ed93d980a 100644 --- a/public/js/app/shared/templates/profile-toolbox.html +++ b/public/js/app/shared/templates/profile-toolbox.html @@ -2,6 +2,7 @@

SAP Hybris Profile Toolbox

From 7b51c2292ce4c44eb5de96b0e21cea132951d24c Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Wed, 25 Oct 2017 09:53:36 +0200 Subject: [PATCH 19/23] - Added ProfileTagConfiguration - Added categories --- gruntfile.js | 71 +- products.json | 805 ------------------ public/index.html | 509 ++++++----- public/js/app/app.js | 2 +- .../controllers/browse-products-ctrl.js | 92 +- .../controllers/product-detail-ctrl.js | 34 +- .../app/products/services/category-service.js | 127 +-- .../app/products/services/product-service.js | 7 +- .../products/templates/product-detail.html | 60 +- .../app/products/templates/product-list.html | 132 +-- public/js/app/shared/app-config.js | 5 +- .../controllers/sidebar-navigation-ctrl.js | 4 +- public/js/app/shared/directives/y-profile.js | 105 ++- public/js/app/shared/i18n/dev/dev_en.json | 2 + public/js/app/shared/router.js | 2 +- public/js/app/shared/services/cookie-svc.js | 40 +- public/js/app/shared/settings.js | 3 +- .../app/shared/templates/profile-header.html | 49 +- .../app/shared/templates/profile-toolbox.html | 2 +- .../shared/templates/sidebar-navigation.html | 44 - .../app/shared/templates/top-navigation.html | 2 - public/less/_global.less | 2 +- public/less/_navigation.less | 7 +- public/less/_products-list.less | 2 +- public/less/_y-profile.less | 29 +- public/products.json | 12 +- public/profiletag-config.json | 190 +++++ 27 files changed, 916 insertions(+), 1423 deletions(-) delete mode 100644 products.json create mode 100644 public/profiletag-config.json diff --git a/gruntfile.js b/gruntfile.js index 18c06510a..cb26c120b 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -18,7 +18,7 @@ module.exports = function (grunt) { var host = process.env.VCAP_APP_HOST || process.env.HOST || '0.0.0.0'; var port = process.env.VCAP_APP_PORT || process.env.PORT || 9000; //process.env.VCAP_APP_PORT is deprecated - + var ALLOWED_REGIONS = ['eu', 'us'], REGION_INVALID_MSG = 'Selected region is not valid. Please provide one of valid regions: [' + ALLOWED_REGIONS.join(',') + ']'; @@ -36,7 +36,7 @@ module.exports = function (grunt) { var JS_DIR = 'public/js/app', LESS_DIR = 'public/less', TRANSLATIONS_DIR = 'public/js/app/shared/i18n/dev', - + //--Set Parameters for Server Configuration---------------------------------------------------- // Read npm argument and set the dynamic server environment or use default configuration. // Syntax example for npm 2.0 parameters: $ npm run-script singleProd -- --pid=xxx --cid=123 --ruri=http://example.com @@ -49,7 +49,6 @@ module.exports = function (grunt) { SERVER_FILES = ['./server.js', './server/singleProdServer.js', './multi-tenant/multi-tenant-server.js'], - PROJECT_ID_PATH = './public/js/app/shared/app-config.js', INDEX_PATH = './public/index.html', API_DOMAIN_PATH = './public/js/app/shared/app-config.js', TRANSLATE_FILES_PATH = './public/js/app/shared/i18n/lang/lang_*.json'; @@ -87,6 +86,16 @@ module.exports = function (grunt) { return 'https://profile-manager.us-east.modules.yaas.io'; }; + var getProfileTagUrl = function () { + if (ENV_ID === 'STAGE') { + return 'https://s3.amazonaws.com/profile-tag/js/us-stage/profile-tag.js'; + } + if (REGION_CODE === 'EU') { + return 'https://s3.amazonaws.com/profile-tag/js/eu-prod/profile-tag.js'; + } + return 'https://s3.amazonaws.com/profile-tag/js/us-prod/profile-tag.js'; + }; + require('load-grunt-tasks')(grunt); grunt.loadNpmTasks('grunt-text-replace'); grunt.loadNpmTasks('grunt-angular-templates'); //combines templates into cache @@ -193,10 +202,11 @@ module.exports = function (grunt) { urls: { src: [API_DOMAIN_PATH, INDEX_PATH], overwrite: true, - replacements: [{ - from: /StartDynamicDomain(.*)EndDynamicDomain/g, - to: 'StartDynamicDomain*/ \'' + getServicesBaseUrl() + '\' /*EndDynamicDomain' - }, + replacements: [ + { + from: /StartDynamicDomain(.*)EndDynamicDomain/g, + to: 'StartDynamicDomain*/ \'' + getServicesBaseUrl() + '\' /*EndDynamicDomain' + }, { from: /StartBuilderUrl(.*)EndBuilderUrl/g, to: 'StartBuilderUrl*/ \'' + getBuilderUrl() + '/\' /*EndBuilderUrl' @@ -208,31 +218,28 @@ module.exports = function (grunt) { { from: /StartPiwikUrl(.*)EndPiwikUrl/g, to: 'StartPiwikUrl*/ \'' + getPiwikUrl() + '\' /*EndPiwikUrl' - }] - }, - projectId: { - src: [PROJECT_ID_PATH], - overwrite: true, - replacements: [{ - from: /StartProjectId(.*)EndProjectId/g, - to: 'StartProjectId*/ \'' + PROJECT_ID + '\' /*EndProjectId' - }] - }, - clientId: { - src: [PROJECT_ID_PATH], - overwrite: true, - replacements: [{ - from: /StartClientId(.*)EndClientId/g, - to: 'StartClientId*/ \'' + CLIENT_ID + '\' /*EndClientId' - }] - }, - redirectURI: { - src: [PROJECT_ID_PATH], - overwrite: true, - replacements: [{ - from: /StartRedirectURI(.*)EndRedirectURI/g, - to: 'StartRedirectURI*/ \'' + REDIRECT_URI + '\' /*EndRedirectURI' - }] + }, + { + from: /StartRegion(.*)EndRegion/g, + to: 'StartRegion*/ \'' + REGION_CODE + '\' /*EndRegion' + }, + { + from: /StartProfileTagUrl(.*)EndProfileTagUrl/g, + to: 'StartProfileTagUrl*/ \'' + getProfileTagUrl() + '\' /*EndProfileTagUrl' + }, + { + from: /StartClientId(.*)EndClientId/g, + to: 'StartClientId*/ \'' + CLIENT_ID + '\' /*EndClientId' + }, + { + from: /StartProjectId(.*)EndProjectId/g, + to: 'StartProjectId*/ \'' + PROJECT_ID + '\' /*EndProjectId' + }, + { + from: /StartRedirectURI(.*)EndRedirectURI/g, + to: 'StartRedirectURI*/ \'' + REDIRECT_URI + '\' /*EndRedirectURI' + } + ] }, useHttps: { src: SERVER_FILES, diff --git a/products.json b/products.json deleted file mode 100644 index 62091b339..000000000 --- a/products.json +++ /dev/null @@ -1,805 +0,0 @@ -[ - { - "id": "540751ee394edbc101ff20f5", - "sku": "TatteredBowls1399317852972", - "name": { - "en": "Tattered Bowls", - "de": "Tattered Schüsseln" - }, - "description": { - "en": "These bowls look like they were thrown across the kitchen. Why would anyone want to buy these broken bowls?", - "de": "Diese Schalen schauen, wie sie durch die Küche geworfen wurden. Warum sollte jemand, diese gebrochenen Schalen kaufen?" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540751ee394edbc101ff20f5/media/540751f5be0eb3ae86828d9f/content?tenant=3xsfuichdoum", - "id": "540751f5be0eb3ae86828d9f" - } - ], - "inStock": true, - "created": "2014-09-03T17:37:50.004+0000", - "price": 10, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075212394edbc101ff20ff", - "sku": "WhiteCupwithHoles1396974928135", - "name": { - "en": "White Cup with Holes", - "de": "Weißen Tasse mit Bohrungen" - }, - "description": { - "en": "This cup has holes in it. Not recommended for use with coffee or oatmeal.", - "de": "Diese Schale hat Löcher. Nicht für die Verwendung mit Kaffee oder Haferflocken empfohlen." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075212394edbc101ff20ff/media/54075216be0eb3ae86828da9/content?tenant=3xsfuichdoum", - "id": "54075216be0eb3ae86828da9" - } - ], - "inStock": false, - "created": "2014-09-03T17:38:26.219+0000", - "price": 3, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075232394edbc101ff210b", - "sku": "LittleClayBirds1399405518320", - "name": { - "en": "Little Clay Birds", - "de": "Wenig Lehm Vögel" - }, - "description": { - "en": "Little Clay Birds", - "de": "Wenig Lehm Vögel" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075232394edbc101ff210b/media/54075235be0eb3ae86828db5/content?tenant=3xsfuichdoum", - "id": "54075235be0eb3ae86828db5" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:58.372+0000", - "price": 2.9, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "5407524a394edbc101ff2113", - "sku": "BeadedNecklace1399405613951", - "name": { - "en": "Beaded Necklace", - "de": "Perlen Halskette" - }, - "description": { - "en": "Beaded Necklace", - "de": "Perlen Halskette" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/5407524a394edbc101ff2113/media/5407524ebe0eb3ae86828dbd/content?tenant=3xsfuichdoum", - "id": "5407524ebe0eb3ae86828dbd" - } - ], - "inStock": true, - "created": "2014-09-03T17:39:22.562+0000", - "price": 13.98, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "5407526a394edbc101ff2119", - "sku": "WhiteBowlswithRedCirclessetof41396975021144", - "name": { - "en": "White Bowls with Red Circles (set of 4)", - "de": "Schwarz Schüssel mit Herzen" - }, - "description": { - "en": "Our best selling set of four white bowls with red circles on them.", - "de": "Unser Verkaufs Satz von vier weißen Schalen mit roten Kreisen auf sie." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/5407526a394edbc101ff2119/media/5407526dbe0eb3ae86828dc3/content?tenant=3xsfuichdoum", - "id": "5407526dbe0eb3ae86828dc3" - } - ], - "created": "2014-09-03T17:39:54.501+0000", - "price": 15, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075270394edbc101ff211b", - "sku": "PotsandPans1399395920078", - "name": { - "en": "Pots and Pans", - "de": "Geschirr" - }, - "description": { - "en": "YOU NEED THIS FOR COOKIN'", - "de": "Sie benötigen diese FÜR COOKIN" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075270394edbc101ff211b/media/54075278be0eb3ae86828dc5/content?tenant=3xsfuichdoum", - "id": "54075278be0eb3ae86828dc5" - } - ], - "inStock": true, - "created": "2014-09-03T17:40:00.444+0000", - "price": 7, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "5407527a394edbc101ff211d", - "sku": "VinylLPdeluxe1399400219218", - "name": { - "en": "Vinyl LP deluxe", - "de": "Vinyl LP deluxe" - }, - "description": { - "en": "These make you look like you know the best music. Impress your friends and family with your diverse collection of tunes.", - "de": "Diese machen Sie aussehen, wie Sie die beste Musik kennen. Beeindrucken Sie Ihre Freunde und Familie mit Ihren vielfältigen Sammlung von Melodien." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/5407527a394edbc101ff211d/media/5407527ebe0eb3ae86828dc7/content?tenant=3xsfuichdoum", - "id": "5407527ebe0eb3ae86828dc7" - } - ], - "inStock": true, - "created": "2014-09-03T17:40:10.789+0000", - "price": 90.09, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "5407528a394edbc101ff2121", - "sku": "cookingutenils1399400975226", - "name": { - "en": "Cooking utensils", - "de": "Kochutensilien" - }, - "description": { - "en": "These ones are great for flipping steaks, omellettes", - "de": "Diese hier sind für Flipping Steaks, Omeletts" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/5407528a394edbc101ff2121/media/5407528ebe0eb3ae86828dcb/content?tenant=3xsfuichdoum", - "id": "5407528ebe0eb3ae86828dcb" - } - ], - "inStock": false, - "created": "2014-09-03T17:40:26.070+0000", - "price": 19.99, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752a4394edbc101ff2128", - "sku": "TalktomeCoffeeMugs1399405423217", - "name": { - "en": "Talk to me Coffee Mugs", - "de": "Sprechen Sie mich Kaffeetassen" - }, - "description": { - "en": "Flirtatious set of mugs for the urban lady who watches too much sex in the city", - "de": "Kokett Set Becher für die städtische Dame, die zu viel Sex in der Stadt Uhren" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752a4394edbc101ff2128/media/540752a7be0eb3ae86828dd2/content?tenant=3xsfuichdoum", - "id": "540752a7be0eb3ae86828dd2" - } - ], - "inStock": true, - "created": "2014-09-03T17:40:52.110+0000", - "price": 16.98, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752ff394edbc101ff2144", - "sku": "Handpaintedbowwithhearts1399405413764", - "name": { - "en": "Hand-painted bow with hearts", - "de": "Handbemalte Bogen mit Herzen" - }, - "description": { - "en": "Super cute, my wife would probably dig this. Do you think they sell it at Ross?", - "de": "Super nett, meine Frau würde wahrscheinlich graben diese. Glaubst du, sie verkaufen sie zu Ross?" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752ff394edbc101ff2144/media/54075303be0eb3ae86828dee/content?tenant=3xsfuichdoum", - "id": "54075303be0eb3ae86828dee" - } - ], - "inStock": true, - "created": "2014-09-03T17:42:23.206+0000", - "price": 7.95, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540751d0394edbc101ff20ef", - "sku": "FrenchPress1399402118414", - "name": { - "en": "French Press", - "de": "Französisch Presse" - }, - "description": { - "en": "This will make the best coffee you ever had.", - "de": "Dies wird den besten Kaffee Sie jemals machen." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540751d0394edbc101ff20ef/media/54089d0bbe0eb3ae8682a12b/content?tenant=3xsfuichdoum", - "id": "54089d0bbe0eb3ae8682a12b" - } - ], - "inStock": true, - "created": "2014-09-04T17:01:11.467+0000", - "price": 0, - "customAttributes": [] - }, - { - "id": "540751e0394edbc101ff20f3", - "sku": "EspressoMachine1399405141207", - "name": { - "en": "Espresso Machine", - "de": "Espressomaschine" - }, - "description": { - "en": "Most famous way to make a cuppa", - "de": "Berühmteste Weg, um eine Tasse Tee zu machen" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540751e0394edbc101ff20f3/media/540751eabe0eb3ae86828d9d/content?tenant=3xsfuichdoum", - "id": "540751eabe0eb3ae86828d9d" - } - ], - "inStock": false, - "created": "2014-09-03T17:37:36.709+0000", - "price": 599.95, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540751f8394edbc101ff20f7", - "sku": "RingHeartBowl1396976297636", - "name": { - "en": "Ring Heart Bowl", - "de": "Ring Herz Bowl" - }, - "description": { - "en": "Ring not included", - "de": "Ring nicht im Lieferumfang enthalten" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540751f8394edbc101ff20f7/media/540751fbbe0eb3ae86828da1/content?tenant=3xsfuichdoum", - "id": "540751fbbe0eb3ae86828da1" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:00.809+0000", - "price": 2, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540751fd394edbc101ff20f9", - "sku": "LittlePlateBowl1396976734843", - "name": { - "en": "Little Plate Bowl", - "de": "Kleine Teller Schüssel" - }, - "description": { - "en": "Tiny white plate.", - "de": "Kleine weiße Platte." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540751fd394edbc101ff20f9/media/54075200be0eb3ae86828da3/content?tenant=3xsfuichdoum", - "id": "54075200be0eb3ae86828da3" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:05.858+0000", - "price": 3, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075203394edbc101ff20fb", - "sku": "BlackRingHolder1396977261772", - "name": { - "en": "Black Ring Holder", - "de": "Schwarz-Ring-Halter" - }, - "description": { - "en": "Black ring holder with hearts", - "de": "Schwarz Ringhalter mit Herzen" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075203394edbc101ff20fb/media/54075207be0eb3ae86828da5/content?tenant=3xsfuichdoum", - "id": "54075207be0eb3ae86828da5" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:11.638+0000", - "price": 6, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "5407520b394edbc101ff20fd", - "sku": "WhiteRingHolder1396977287703", - "name": { - "en": "White Ring Holder", - "de": "Weißer Ring Halter" - }, - "description": { - "en": "White ring holder bowl with blue hearts.", - "de": "Weiß Ringhalter Schüssel mit blauen Herzen." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/5407520b394edbc101ff20fd/media/5407520fbe0eb3ae86828da7/content?tenant=3xsfuichdoum", - "id": "5407520fbe0eb3ae86828da7" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:19.273+0000", - "price": 6, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075218394edbc101ff2101", - "sku": "Birdhouse1399405396665", - "name": { - "en": "Birdhouse", - "de": "Vogelhaus" - }, - "description": { - "en": "Awesome if your pet bird likes harlem", - "de": "Genial, wenn Ihr Haustier Vogel mag Harlem" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075218394edbc101ff2101/media/5407521bbe0eb3ae86828dab/content?tenant=3xsfuichdoum", - "id": "5407521bbe0eb3ae86828dab" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:32.238+0000", - "price": 27.5, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "5407521d394edbc101ff2103", - "sku": "Raindropsoapholder1399405432328", - "name": { - "en": "Raindrop soap holder", - "de": "Regentropfen Seifenhalter" - }, - "description": { - "en": "Raindrop soap holder", - "de": "Regentropfen Seifenhalter" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/5407521d394edbc101ff2103/media/54075220be0eb3ae86828dad/content?tenant=3xsfuichdoum", - "id": "54075220be0eb3ae86828dad" - } - ], - "inStock": true, - "created": "2014-09-03T17:38:37.362+0000", - "price": 6.98, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075251394edbc101ff2115", - "sku": "WoodboundChemexCoffeeMaker1398100549696", - "name": { - "en": "Wood-bound Chemex Coffee Maker", - "de": "Holz-gebundenen Chemex Kaffeemaschine" - }, - "description": { - "en": "Awesome brew. Totally bright and delicious", - "de": "Genial brauen. Ganz hell und lecker" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075251394edbc101ff2115/media/5407525dbe0eb3ae86828dbf/content?tenant=3xsfuichdoum", - "id": "5407525dbe0eb3ae86828dbf" - } - ], - "inStock": false, - "created": "2014-09-03T17:39:29.692+0000", - "price": 18.95, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075260394edbc101ff2117", - "sku": "BlackBowlwithHearts1396974669725", - "name": { - "en": "Black Bowl with Hearts", - "de": "Schwarz Schüssel mit Herzen" - }, - "description": { - "en": "Express your love of cereal with this amazing bowl.", - "de": "Drücken Sie Ihre Liebe von Getreide mit diesem erstaunlichen Schüssel." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075260394edbc101ff2117/media/54075267be0eb3ae86828dc1/content?tenant=3xsfuichdoum", - "id": "54075267be0eb3ae86828dc1" - } - ], - "inStock": true, - "created": "2014-09-03T17:39:44.879+0000", - "price": 5, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075281394edbc101ff211f", - "sku": "Beautifulchina1399400709439", - "name": { - "en": "Beautiful china", - "de": "Schöne China" - }, - "description": { - "en": "These make you look like you know how to set the table.", - "de": "Diese machen Sie aussehen, wie Sie wissen, wie man den Tisch." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075281394edbc101ff211f/media/54075287be0eb3ae86828dc9/content?tenant=3xsfuichdoum", - "id": "54075287be0eb3ae86828dc9" - } - ], - "inStock": true, - "created": "2014-09-03T17:40:17.166+0000", - "price": 190, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075291394edbc101ff2123", - "sku": "Coffee1399402203294", - "name": { - "en": "Coffee", - "de": "Kaffee" - }, - "description": { - "en": "What more could you want or need.", - "de": "Was kann man mehr wollen oder brauchen." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075291394edbc101ff2123/media/54075298be0eb3ae86828dce/content?tenant=3xsfuichdoum", - "id": "54075298be0eb3ae86828dce" - } - ], - "inStock": true, - "created": "2014-09-03T17:40:33.363+0000", - "price": 2.57, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752b1394edbc101ff212c", - "sku": "Mugandbreakfastbowl1399405379592", - "name": { - "en": "Mug and breakfast bowl", - "de": "Becher und Frühstück Schüssel" - }, - "description": { - "en": "Great swag for a single lady. grapes not included", - "de": "Große Beute für eine einzelne Dame. Trauben nicht im Lieferumfang enthalten" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752b1394edbc101ff212c/media/540752b4be0eb3ae86828dd6/content?tenant=3xsfuichdoum", - "id": "540752b4be0eb3ae86828dd6" - } - ], - "inStock": true, - "created": "2014-09-03T17:41:05.445+0000", - "price": 8.95, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752c5394edbc101ff2132", - "sku": "LargeSetOfHeartCoffeeMugs1399405542698", - "name": { - "en": "Large Set Of Heart Coffee Mugs", - "de": "Große Reihe von Herz-Kaffeetasse" - }, - "description": { - "en": "This is the best deal", - "de": "Dies ist das beste Angebot" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752c5394edbc101ff2132/media/540752c9be0eb3ae86828ddc/content?tenant=3xsfuichdoum", - "id": "540752c9be0eb3ae86828ddc" - } - ], - "inStock": true, - "created": "2014-09-03T17:41:25.832+0000", - "price": 19.99, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752da394edbc101ff2138", - "sku": "LargesetofEspressoCoffeeMugs1399405575815", - "name": { - "en": "Large set of Espresso & Coffee Mugs", - "de": "Großen Satz von Espresso & Kaffeetassen" - }, - "description": { - "en": "Large Variable set of espresso mugs", - "de": "Große Variable Set Espresso-Tassen" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752da394edbc101ff2138/media/540752ddbe0eb3ae86828de2/content?tenant=3xsfuichdoum", - "id": "540752ddbe0eb3ae86828de2" - } - ], - "inStock": true, - "created": "2014-09-03T17:41:46.511+0000", - "price": 199.99, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752e6394edbc101ff213c", - "sku": "Setofraindropsoapdishes1399405600154", - "name": { - "en": "Set of raindrop soap dishes", - "de": "Set von Regentropfen Seifenschalen" - }, - "description": { - "en": "Set of raindrop soap dishes", - "de": "Set von Regentropfen Seifenschalen" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752e6394edbc101ff213c/media/540752e9be0eb3ae86828de6/content?tenant=3xsfuichdoum", - "id": "540752e9be0eb3ae86828de6" - } - ], - "inStock": true, - "created": "2014-09-03T17:41:58.797+0000", - "price": 27.99, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752ec394edbc101ff213e", - "sku": "RedandWhiteCreamerset1399405606933", - "name": { - "en": "Red and White Creamer set", - "de": "Rot und Weiß Milchkännchen-Set" - }, - "description": { - "en": "Red and White Creamer set", - "de": "Rot und Weiß Milchkännchen-Set" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752ec394edbc101ff213e/media/540752f1be0eb3ae86828de8/content?tenant=3xsfuichdoum", - "id": "540752f1be0eb3ae86828de8" - } - ], - "inStock": true, - "created": "2014-09-03T17:42:04.318+0000", - "price": 16.98, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752f4394edbc101ff2140", - "sku": "YellowandWhiteCreamerset1399405621623", - "name": { - "en": "Yellow and White Creamer set", - "de": "Gelbe und weiße Milchkännchen-Set" - }, - "description": { - "en": "Red and White Creamer set", - "de": "Gelbe und weiße Milchkännchen-Set" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752f4394edbc101ff2140/media/540752f7be0eb3ae86828dea/content?tenant=3xsfuichdoum", - "id": "540752f7be0eb3ae86828dea" - } - ], - "inStock": true, - "created": "2014-09-03T17:42:12.551+0000", - "price": 16.98, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752fa394edbc101ff2142", - "sku": "Redraindropsoapdish1399405629100", - "name": { - "en": "Red raindrop soap dish", - "de": "Roter Regentropfen Seifenschale" - }, - "description": { - "en": "Red raindrop soap dish", - "de": "Roter Regentropfen Seifenschale" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752fa394edbc101ff2142/media/540752fcbe0eb3ae86828dec/content?tenant=3xsfuichdoum", - "id": "540752fcbe0eb3ae86828dec" - } - ], - "inStock": true, - "created": "2014-09-03T17:42:18.010+0000", - "price": 1.98, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "540752b7394edbc101ff212e", - "sku": "Yellowraindropsoapdish1399405491841", - "name": { - "en": "Yellow raindrop soap dish", - "de": "Gelber Regentropfen Seifenschale" - }, - "description": { - "en": "Yellow raindrop soap dish", - "de": "Gelber Regentropfen Seifenschale" - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/540752b7394edbc101ff212e/media/540752bcbe0eb3ae86828dd8/content?tenant=3xsfuichdoum", - "id": "540752bcbe0eb3ae86828dd8" - } - ], - "inStock": true, - "created": "2014-09-03T17:41:11.682+0000", - "price": 3.95, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - }, - { - "id": "54075306394edbc101ff2146", - "sku": "Test1396454831925", - "name": { - "en": "Bicycle!", - "de": "Fahrrad" - }, - "description": { - "en": "A bicycle, often called a bike, is a human-powered, pedal-driven, single-track vehicle, having two wheels attached to a frame, one behind the other. A bicycle rider is called a cyclist, or bicyclist.", - "de": "Ein Fahrrad, ein Fahrrad oft genannt wird, ist ein muskelbetriebenes, pedalbetriebene, eingleisige Fahrzeug mit zwei Rädern zu einem Rahmen, einer hinter dem anderen verbunden ist. Ein Fahrradfahrer wird als Radfahrer oder Radfahrer." - }, - "published": true, - "externalImages": [], - "images": [ - { - "url": "http://product-v1-4-1.test.cf.hybris.com/products/54075306394edbc101ff2146/media/5407530ebe0eb3ae86828df0/content?tenant=3xsfuichdoum", - "id": "5407530ebe0eb3ae86828df0" - } - ], - "inStock": true, - "created": "2014-09-03T17:42:30.973+0000", - "price": 9.5, - "itemCondition": "NEW", - "adult": false, - "customAttributes": [] - } -] \ No newline at end of file diff --git a/public/index.html b/public/index.html index b6553a489..ec892062d 100644 --- a/public/index.html +++ b/public/index.html @@ -23,204 +23,321 @@ -
- -
-
-
-
-
-
-
- +
+ +
+
+
+
+
- +
+
+ +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/js/app/app.js b/public/js/app/app.js index c83656f68..348c1dff7 100644 --- a/public/js/app/app.js +++ b/public/js/app/app.js @@ -1,6 +1,7 @@ /** * [y] hybris Platform * + * * Copyright (c) 2000-2015 hybris AG * All rights reserved. * @@ -39,7 +40,6 @@ window.app = angular.module('ds.app', [ 'ui.select', 'ui-notification', 'ds.ybreadcrumb', - 'ds.ytracking', 'ds.localstorage', 'ds.appconfig', 'ds.searchlist', diff --git a/public/js/app/products/controllers/browse-products-ctrl.js b/public/js/app/products/controllers/browse-products-ctrl.js index 249e9bf20..f8930b2a7 100644 --- a/public/js/app/products/controllers/browse-products-ctrl.js +++ b/public/js/app/products/controllers/browse-products-ctrl.js @@ -13,10 +13,16 @@ 'use strict'; angular.module('ds.products') - /** Controller for the 'browse products' view. */ +/** Controller for the 'browse products' view. */ .controller('BrowseProductsCtrl', ['$scope', '$rootScope', 'ProductSvc', 'GlobalData', 'CategorySvc', 'settings', 'category', '$state', '$location', '$timeout', '$anchorScroll', 'MainMediaExtractor', 'PriceSvc', '$q', function ($scope, $rootScope, ProductSvc, GlobalData, CategorySvc, settings, category, $state, $location, $timeout, $anchorScroll, MainMediaExtractor, PriceSvc, $q) { + + CategorySvc.getCategories().then(function (categories) { + var catConf = _.findWhere(categories, {slug: category}); + $scope.categories = categories; + $scope.category = catConf ? catConf.name : ''; + }); $scope.pageSize = GlobalData.products.pageSize; $scope.pageNumber = 0; $scope.setSortedPageSize = void 0; @@ -28,7 +34,7 @@ angular.module('ds.products') $scope.requestInProgress = false; $scope.PLACEHOLDER_IMAGE = settings.placeholderImage; $scope.sortParams = GlobalData.getProductRefinements(); - $scope.sort = { selected: GlobalData.getProductRefinements()[0].id }; + $scope.sort = {selected: GlobalData.getProductRefinements()[0].id}; $scope.currencySymbol = GlobalData.getCurrency(); $scope.pagination = { @@ -36,40 +42,13 @@ angular.module('ds.products') productsTo: 1 }; - $scope.category = category || {}; - if (!!category) { $scope.$emit('category:opened', category); } - $scope.lastCatId = $scope.category.id || 'allProducts'; - $scope.loadedPages = 1; $scope.loadMorePages = false; - if (category !== null) { - $scope.mainCategoryImage = MainMediaExtractor.extract(category.media); - } - - // ensure category path is localized - var pathSegments = $location.path().split('/'); - if ($scope.category.slug && pathSegments[pathSegments.length - 1] !== $scope.category.slug) { - pathSegments[pathSegments.length - 1] = $scope.category.slug; - $location.path(pathSegments.join('/')); - } - - $rootScope.$emit('category:selected', { category: category }); - - function getProductIdsFromAssignments(assignments) { - - return assignments.map(function (assignment) { - if (assignment.ref.type === 'product') { - return assignment.ref.id; - } else { - return ''; - } - }); - } function setMainImage(product) { var mainMedia = MainMediaExtractor.extract(product.media); @@ -112,7 +91,7 @@ angular.module('ds.products') product.metadata.variants && product.metadata.variants.options && Object.keys(product.metadata.variants.options).length > 0) { - promise = ProductSvc.getProductVariants({ productId: product.id }) + promise = ProductSvc.getProductVariants({productId: product.id}) .then(function (result) { product.hasVariants = result.length > 0; }); @@ -193,19 +172,6 @@ angular.module('ds.products') This function is only for infinite scrolling, which is the default state. It is disabled once a sort is applied. */ $scope.addMore = function () { - // category selected, but no products associated with category - leave blank for time being - if ($scope.category.assignments && $scope.category.assignments.length === 0) { - $scope.products = []; - $scope.pagination = { - productsFrom: 0, - productsTo: 0 - }; - $scope.total = 0; - return; - } - /* - this function is only for infinite scrolling, which is disabled when a sort is applied. - */ // prevent additional API calls if all products are retrieved // infinite scroller initiates lots of API calls when scrolling to the bottom of the page @@ -214,14 +180,13 @@ angular.module('ds.products') $scope.pageNumber = $scope.pageNumber + 1; var qSpec = 'published:true'; - if ($scope.category.assignments && $scope.category.assignments.length > 0) { - qSpec = qSpec + ' ' + 'id:(' + getProductIdsFromAssignments($scope.category.assignments) + ')'; - } // If no category assignment (rather than length = 0), we're showing "all" products + var query = { pageNumber: $scope.pageNumber, pageSize: $scope.pageSize, q: qSpec, - sort: $scope.sort.selected + sort: $scope.sort.selected, + category: category }; queryProducts(query, true); } @@ -248,7 +213,7 @@ angular.module('ds.products') if (!!$location.search().page) { $scope.loadedPages = parseInt($location.search().page); $scope.pageSize = $scope.pageSize * $scope.loadedPages; - $scope.sort = GlobalData.products.lastSort || { selected: GlobalData.getProductRefinements()[0].id }; + $scope.sort = GlobalData.products.lastSort || {selected: GlobalData.getProductRefinements()[0].id}; $scope.loadMorePages = true; } @@ -261,37 +226,6 @@ angular.module('ds.products') GlobalData.products.lastSort = $scope.sort; }; - $scope.setSortedPage = function () { - - $scope.setSortedPageSize = void 0; - $scope.setSortedPageNumber = 1; - if (($scope.pageSize > $scope.total) && ($scope.total !== 0)) { - $scope.setSortedPageSize = $scope.total; - } - - //check to see if the current page number times the page size is going to be greater than the total product count - //if it is then we need to set caps on the pageSize and page number - $scope.setSortedPageSize = ($scope.pageNumber * $scope.pageSize > $scope.total) ? $scope.total : $scope.pageNumber * $scope.pageSize; - - /* - it is important to note that the $scope.pageNumber and $scope.pageSize are not being modified as they need - to be unmidified for the addMore() method to work for the inifinte scroll functionality - */ - //we only want to show published products on this list - var qSpec = 'published:true'; - if ($scope.category.assignments && $scope.category.assignments.length > 0) { - qSpec = qSpec + ' ' + 'id:(' + getProductIdsFromAssignments($scope.category.assignments) + ')'; - } - - var query = { - pageNumber: $scope.setSortedPageNumber, - pageSize: $scope.setSortedPageSize, - sort: $scope.sort.selected, - q: qSpec - }; - queryProducts(query, false); - }; - $scope.showRefineContainer = function () { $scope.refineContainerShowing = !$scope.refineContainerShowing; }; diff --git a/public/js/app/products/controllers/product-detail-ctrl.js b/public/js/app/products/controllers/product-detail-ctrl.js index 5d6f510bc..8ad1bd98c 100644 --- a/public/js/app/products/controllers/product-detail-ctrl.js +++ b/public/js/app/products/controllers/product-detail-ctrl.js @@ -39,11 +39,15 @@ angular.module('ds.products') $scope.noShippingRates = true; $scope.currencySymbol = GlobalData.getCurrencySymbol(); // used by breadcrumb directive - $scope.category = product.categories; $scope.breadcrumbData = angular.copy($scope.category); $scope.taxConfiguration = GlobalData.getCurrentTaxConfiguration(); + CategorySvc.getCategories().then(function (categories) { + $scope.category = _.findWhere(categories, {slug: product.product.category}).name; + }); + + /* we need to shorten the tax label if it contains more than 60 characters, and give users the option of clicking a 'see more' link to view the whole label. @@ -159,32 +163,8 @@ angular.module('ds.products') }; - function filterPricesForVariant(variantId) { - return variantPrices.filter(function (price) { - var foundVariantId = price.yrn.split(';').pop(); - return variantId === foundVariantId; - }); - } - - $scope.onActiveVariantChanged = function (activeVariant) { - if (_.isObject(activeVariant)) { - var prices = filterPricesForVariant(activeVariant.id); - $scope.product = productFactory.fromProductVariant(product.product, activeVariant, prices); - // Update the selected variant - $scope.selectedVariant = activeVariant; - // The selected variant was updated by the user, check if this new select variant has a fee bound to it - // As this action is asynchronous, hide the fees in the front-end first - $scope.productFees = []; - FeeSvc.getFeesForItemYrn($scope.selectedVariant.yrn) - .then(function(productFees) { - $scope.productFees = productFees; - }) - .catch(function() { - $scope.productFees = []; - }); - } else { - $scope.product = productFactory.fromProduct(product.product, product.prices, false); - } + $scope.onActiveVariantChanged = function () { + $scope.product = productFactory.fromProduct(product.product, product.prices, false); }; // Helper functions to check if the currently displayed product is a base product or a product variant diff --git a/public/js/app/products/services/category-service.js b/public/js/app/products/services/category-service.js index 545cae29d..e570249b3 100644 --- a/public/js/app/products/services/category-service.js +++ b/public/js/app/products/services/category-service.js @@ -17,95 +17,62 @@ */ angular.module('ds.products') - .factory('CategorySvc', ['$rootScope', '$state', 'PriceProductREST', 'GlobalData', '$q', - function($rootScope, $state, PriceProductREST, GlobalData, $q){ + .factory('CategorySvc', ['$rootScope', '$state', 'PriceProductREST', 'GlobalData', '$q', 'ProductSvc', + function ($rootScope, $state, PriceProductREST, GlobalData, $q, ProductSvc) { - var categoryMap; - var catList; + var categoryMap; + var catList; - function getCategory(slug){ - var tildeIndex = slug.indexOf('~'); - if(tildeIndex < 0) { - return null; - } - var catId = slug.substring(tildeIndex+1, slug.length); - return categoryMap[catId]; - } + return { - return { - - /** Returns a promise over the category list as loaded from the service. Fires event "categories:updated". - * @param source - indicates source/reason for update, eg. 'languageUpdate' - see setting.eventSource. - * */ - getCategories: function () { - return $q.resolve([]); - }, - - /** Returns categories from cache.*/ - getCategoriesFromCache: function(){ - return catList; - }, - - getCategoryById: function(categoryId){ - var catDef = $q.defer(); + /** Returns a promise over the category list as loaded from the service. Fires event "categories:updated". + * @param source - indicates source/reason for update, eg. 'languageUpdate' - see setting.eventSource. + * */ + getCategories: function () { + var categories = [ + { + name: 'Dark', + slug: 'dark' + }, + { + name: 'Light', + slug: 'light' + } + ]; + catList = categories; + return $q.resolve(catList); + }, - if(categoryMap){ - catDef.resolve(categoryMap[categoryId]); - } else { - this.getCategories().then(function(){ - catDef.resolve(categoryMap[categoryId]); - }); - } - return catDef.promise; - }, + /** Returns categories from cache.*/ + getCategoriesFromCache: function () { + return catList; + }, - /** Returns the category along with "element list". - * If category will be retrieved from cache if existing. - * @param categorySlug ("sluggified" name per logic in this service - name, ~, categoryId, e.g. 'green-bottles~3456') - * @returns {*} - */ - getCategoryWithProducts: function (categorySlug) { - var compositeDef = $q.defer(); + getCategoryById: function (categoryId) { + var catDef = $q.defer(); - if (!categorySlug) { - compositeDef.resolve(null); - } else { - var cdef = $q.defer(); if (categoryMap) { - var category = getCategory(categorySlug); - if(category){ - cdef.resolve(category); - } else { - cdef.reject(); - $state.go('errors', { errorId : '404' }); - } + catDef.resolve(categoryMap[categoryId]); } else { this.getCategories().then(function () { - var category = getCategory(categorySlug); - if(category){ - cdef.resolve(category); - } else { - cdef.reject(); - } + catDef.resolve(categoryMap[categoryId]); }); } - cdef.promise.then(function (category) { - PriceProductREST.Categories.all('categories').one(category.id).all('assignments').getList({recursive: true}).then( - function(assignments){ - category.assignments = assignments.plain(); - compositeDef.resolve(category); - }, function(){ - compositeDef.resolve(category); - } - ); - }); - } - return compositeDef.promise; - }, + return catDef.promise; + }, - /** Remove local category storage to force retrieval from server on next request.*/ - resetCategoryCache: function(){ - categoryMap = null; - } - }; -}]); + /** Returns the category along with "element list". + * If category will be retrieved from cache if existing. + * @param categorySlug ("sluggified" name per logic in this service - name, ~, categoryId, e.g. 'green-bottles~3456') + * @returns {*} + */ + getCategoryWithProducts: function () { + return ProductSvc.queryProductList(); + }, + + /** Remove local category storage to force retrieval from server on next request.*/ + resetCategoryCache: function () { + categoryMap = null; + } + }; + }]); diff --git a/public/js/app/products/services/product-service.js b/public/js/app/products/services/product-service.js index 2130e8481..ef8488c6a 100644 --- a/public/js/app/products/services/product-service.js +++ b/public/js/app/products/services/product-service.js @@ -27,8 +27,13 @@ angular.module('ds.products') }); return { - queryProductList: function () { + queryProductList: function (query) { return listPromise.then(function (data) { + if(query && query.category){ + return _.filter(data, function (item) { + return item.category.toUpperCase() === query.category.toUpperCase(); + }); + } return data; }); }, diff --git a/public/js/app/products/templates/product-detail.html b/public/js/app/products/templates/product-detail.html index e422c0841..eedd0a6cf 100644 --- a/public/js/app/products/templates/product-detail.html +++ b/public/js/app/products/templates/product-detail.html @@ -1,10 +1,27 @@ - - +
+
+
+ + +
+
+
-
- + -
+
- - + +
@@ -95,14 +116,18 @@

{{product.name}}

- +
- +
@@ -117,7 +142,8 @@

{{product.name}}

- - diff --git a/public/js/app/shared/templates/top-navigation.html b/public/js/app/shared/templates/top-navigation.html index d626e44ef..a0e1f0ebd 100644 --- a/public/js/app/shared/templates/top-navigation.html +++ b/public/js/app/shared/templates/top-navigation.html @@ -26,8 +26,6 @@
- diff --git a/public/less/_global.less b/public/less/_global.less index 2fb1171df..df6272d96 100644 --- a/public/less/_global.less +++ b/public/less/_global.less @@ -292,7 +292,7 @@ section{ } .product-list-sort .ui-select-container .btn-default { - background-color: @sapblue; + background-color: @sapyellow; border-color: @white; color: #fff; } diff --git a/public/less/_navigation.less b/public/less/_navigation.less index 8eb7200f0..4bc881e9f 100644 --- a/public/less/_navigation.less +++ b/public/less/_navigation.less @@ -755,9 +755,10 @@ a.siteSelectorIcon{ padding-top: 0; padding-bottom: 4px; background-color: transparent; - color: @black; + color: @white; font-weight: 500; border-bottom: 2px solid transparent; + } li.open{ @@ -770,13 +771,15 @@ a.siteSelectorIcon{ li{ a.mainCategory.mactive{ - color: @brand-primary; + color: @white; } a.mainCategory:hover{ background-color: transparent !important; border-bottom: 2px solid @brand-primary; + color: @white; + } } diff --git a/public/less/_products-list.less b/public/less/_products-list.less index d50802260..8faa414f2 100644 --- a/public/less/_products-list.less +++ b/public/less/_products-list.less @@ -96,7 +96,7 @@ section .hero-unit{ .refine-section{ - background-color: @sapblue; + background-color: @sapyellow; margin: 0; .title{ font:34px @font-family-sans-serif, sans-serif; diff --git a/public/less/_y-profile.less b/public/less/_y-profile.less index ae0fa67f9..dcf422d77 100644 --- a/public/less/_y-profile.less +++ b/public/less/_y-profile.less @@ -2,14 +2,16 @@ position: fixed; right: 0; top: 40%; - width: 220px; + width: 240px; color: #fff; - background: @sapyellow; + background: @sapblue; z-index: 100; - padding: 10px; + padding: 20px; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; & h4 { - color: #624907; + color: #fff; } & ul { @@ -37,17 +39,24 @@ .y-profile-header { width: 100%; - background: @sapyellow; - color: #fff; - padding: 10px; + background: #000; + color: @sapyellow; + padding: 10px 60px 10px 60px; z-index: 100001; font-weight: 500; - margin-bottom: 10px; & h1 { & a { - color: #624907; + color: #fff; text-decoration: none; } - + & .logo { + float: right; + width: 183px; + display: block; + margin-top: 5px; + } + } + & .check { + color: #fff; } } \ No newline at end of file diff --git a/public/products.json b/public/products.json index 92212dd6d..ee8ccf34b 100644 --- a/public/products.json +++ b/public/products.json @@ -31,7 +31,8 @@ "inventory" : { "inStock" : true } - } + }, + "category": "light" }, { "id" : "58108f5c37e7ad001dfe6148", "yrn" : "urn:yaas:hybris:product:product:quickstart;58108f5c37e7ad001dfe6148", @@ -65,7 +66,8 @@ "inventory" : { "inStock" : true } - } + }, + "category": "dark" }, { "id" : "58108f9a7b4041001db36ac9", "yrn" : "urn:yaas:hybris:product:product:quickstart;58108f9a7b4041001db36ac9", @@ -99,7 +101,8 @@ "inventory" : { "inStock" : true } - } + }, + "category": "dark" }, { "id" : "5810aa84ff10e1001d60b06a", "yrn" : "urn:yaas:hybris:product:product:quickstart;5810aa84ff10e1001d60b06a", @@ -133,5 +136,6 @@ "inventory" : { "inStock" : true } - } + }, + "category": "light" } ] \ No newline at end of file diff --git a/public/profiletag-config.json b/public/profiletag-config.json new file mode 100644 index 000000000..d8374f502 --- /dev/null +++ b/public/profiletag-config.json @@ -0,0 +1,190 @@ +{ + "siteId": "58d28bd24947d5000eb550f5", + "siteConsent": "implicit", + "pages": [ + { + "pageId": 1, + "pageType": "General", + "consent": "inherit", + "selectedRegexObject": "location.href", + "regexValue": ".*\\/products", + "mappings": [ + { + "postProcessing": [ + { + "func": "map_get", + "params": [ + "href" + ] + }, + { + "func": "split", + "params": [ + "/" + ] + }, + { + "func": "pop", + "params": [] + } + ], + "type": "js_variable", + "selector": "location", + "key": "productSku" + }, + { + "postProcessing": [], + "key": "productName", + "selector": "h1.pageTitle", + "type": "text" + }, + { + "postProcessing": [ + { + "func": "split", + "params": [ + "$" + ] + }, + { + "func": "pop", + "params": [] + } + ], + "key": "productPrice", + "selector": "#effective-amount", + "type": "text" + }, + { + "postProcessing": [], + "key": "productCategory", + "type": "text", + "selector": "#product-categories > li > a" + }, + { + "postProcessing": [], + "selector": ".cc-compliance.cc-highlight", + "type": "consent", + "key": "cookie" + } + ] + }, + { + "pageId": 2, + "pageType": "General", + "consent": "inherit", + "selectedRegexObject": "location.href", + "regexValue": ".*\\/ct\\/\\w+", + "mappings": [ + { + "postProcessing": [ + { + "func": "trim", + "params": [] + } + ], + "key": "productCategory", + "selector": "#categoryName", + "type": "text" + } + ] + }, + { + "pageId": 3, + "pageType": "General", + "consent": "inherit", + "selectedRegexObject": "location.href", + "regexValue": ".*/ct/\\?", + "mappings": [ + { + "type": "custom_interaction", + "interactionDetails": { + "domSrc": [ + { + "attributeKey": "name", + "selector": "#asset_1 > div > div.productInfoContainer.text-center > div", + "attribute": "textContent" + }, + { + "attributeKey": "assetUrl", + "selector": "#asset_1", + "attribute": "href" + }, + { + "attributeKey": "assetId", + "selector": "#asset_1", + "attribute": "data-asset-id" + }, + { + "attributeKey": "interest", + "selector": "#asset_1", + "attribute": "data-interest" + }, + { + "attributeKey": "accessType", + "selector": "#asset_1", + "attribute": "data-access-type" + }, + { + "attributeKey": "type", + "selector": "#asset_1", + "attribute": "data-type" + } + ], + "eventType": "PageViewEvent", + "domElement": "#asset_1", + "domElementEvent": "click", + "schema": "context/commerce/FrontendEntered" + } + }, + { + "postProcessing": [], + "selector": ".cc-compliance.cc-highlight", + "type": "consent", + "key": "cookie" + }, + { + "type": "custom_interaction", + "interactionDetails": { + "domSrc": [ + { + "attributeKey": "assetUrl", + "selector": "#asset_2", + "attribute": "href" + }, + { + "attributeKey": "assetId", + "selector": "#asset_2", + "attribute": "data-asset-id" + }, + { + "attributeKey": "interest", + "selector": "#asset_2", + "attribute": "data-interest" + }, + { + "attributeKey": "accessType", + "selector": "#asset_2", + "attribute": "data-access-type" + }, + { + "attributeKey": "type", + "selector": "#asset_2", + "attribute": "data-type" + }, + { + "attributeKey": "name", + "selector": "#asset_1 > div > div.productInfoContainer.text-center > div", + "attribute": "textContent" + } + ], + "domElement": "#asset_2", + "eventType": "PageViewEvent", + "domElementEvent": "click", + "schema": "context/commerce/FrontendEntered" + } + } + ] + } + ] +} \ No newline at end of file From bc80d45b5bf2c25987097e02c6702c20199b739d Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Wed, 25 Oct 2017 12:31:51 +0200 Subject: [PATCH 20/23] updated readme --- Quickstart.md | 6 +++--- Readme.md | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Quickstart.md b/Quickstart.md index dd4c8d021..d77df54d4 100644 --- a/Quickstart.md +++ b/Quickstart.md @@ -74,9 +74,9 @@ After executing these commands, open the storefront in your browser by clicking Now that the simplified storefront is installed locally on your machine and ready to use, you can send events to SAP Hybris Profile just by clicking on the storefront. -> The simplified storefront supports two event types only: `PageViewEvent` and `ProductDetailPageViewEvent`. +> The simplified storefront supports three event types only: `PageViewEvent`, `ProductDetailPageViewEvent` and `CategoryPageViewEvent`. -Interactions with the storefront create events. Open or refresh the storefront website to create a `PageViewEvent`, or click on the product of your choice to create a `ProductDetailPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the graph, for example by creating nodes or relationships. +Interactions with the storefront create events. Click on the product of your choice to create a `ProductDetailPageViewEvent`, click on the youtube video linkt to send a `PageViewEvent`, or switch the category to send a `CategoryPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the graph, for example by creating nodes or relationships. ## Step 5: Track events @@ -85,7 +85,7 @@ You can track the events sent from the storefront to SAP Hybris Profile in the T > In the simplified storefront version, tracing is enabled by default. Therefore, no action from you is required to activate event tracking. -To navigate to the Trace Explorer, click a **contextTraceId** link that appears at the top of the screen each time the storefront sends an event to SAP Hybris Profile. +To navigate to the Trace Explorer, click a **contextTraceId** link that appears in the SAP Hybris Profile Toolbox each time the storefront sends an event to SAP Hybris Profile. ## Step 6: View the graph changes in the Graph Explorer diff --git a/Readme.md b/Readme.md index 7c4b0c899..c9c32423e 100644 --- a/Readme.md +++ b/Readme.md @@ -26,4 +26,10 @@ npm start -- --pid= --env=stage ``` ## Using -Tracing debug bar should show a link to the Tracing UI. Click a product to create a "Product View" event, and get the new link. The generated links are also visible in the browser developer console. +SAP Hybris Profile Toolbox should show links to the: +- Consent UI +- Profile Visualization UI +- Profile Explorer +- Tracing Explorer + +Events are send when you view the product, select category, or visit the youtube video in the "You ma also like" section. \ No newline at end of file From b71d956883240976e277d56cd1a44f0d03c1e17e Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Thu, 26 Oct 2017 08:21:38 +0200 Subject: [PATCH 21/23] Updated Quickstart.md --- Quickstart.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Quickstart.md b/Quickstart.md index d77df54d4..330936f2b 100644 --- a/Quickstart.md +++ b/Quickstart.md @@ -67,7 +67,7 @@ npm start -- --pid={tenant} --region=eu After executing these commands, open the storefront in your browser by clicking the following link: http://localhost:9000. -> Use this storefront to send basic events such as `PageViewEvent` and `ProductDetailPageViewEvent` to SAP Hybris Profile, track the events in the Trace Explorer, and see the visual representation of the graph changes caused by the events in the Graph Explorer. For more complex operations, use the [YaaS Storefront](https://github.com/SAP/yaas-storefront). For more details about the YaaS Storefront, and how to set it up, see the Getting Started guide. +> Use this storefront to send basic events such as `PageViewEvent` and `ProductDetailPageViewEvent` to SAP Hybris Profile, track the events in the Trace Explorer, and see the profile document changes caused by the events in the Profile Explorer. For more complex operations, use the [YaaS Storefront](https://github.com/SAP/yaas-storefront). For more details about the YaaS Storefront, and how to set it up, see the Getting Started guide. ## Step 4: Send events @@ -76,7 +76,7 @@ Now that the simplified storefront is installed locally on your machine and read > The simplified storefront supports three event types only: `PageViewEvent`, `ProductDetailPageViewEvent` and `CategoryPageViewEvent`. -Interactions with the storefront create events. Click on the product of your choice to create a `ProductDetailPageViewEvent`, click on the youtube video linkt to send a `PageViewEvent`, or switch the category to send a `CategoryPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the graph, for example by creating nodes or relationships. +Interactions with the storefront create events. Click on the product of your choice to create a `ProductDetailPageViewEvent`, click on the youtube video link to send a `PageViewEvent`, or switch the category to send a `CategoryPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the profile, for example by creating or updating whole sections or properties. ## Step 5: Track events @@ -87,16 +87,14 @@ You can track the events sent from the storefront to SAP Hybris Profile in the T To navigate to the Trace Explorer, click a **contextTraceId** link that appears in the SAP Hybris Profile Toolbox each time the storefront sends an event to SAP Hybris Profile. -## Step 6: View the graph changes in the Graph Explorer +## Step 6: View the profile changes in Profile Explorer -The events that you send to SAP Hybris Profile from your storefront trigger the enrichers. Depending on the event type, the triggered enrichers introduce various changes to the profile graph. +The events that you send to SAP Hybris Profile from your storefront trigger the enrichers. Depending on the event type, the triggered enrichers introduce various changes to the profile document. -For example, by clicking on a product in the storefront, you send the `ProductDetailPageViewEvent` to SAP Hybris Profile. This event triggers the specified enricher, which modifies the graph database by creating nodes and/or relationships, such as a `VIEWED` relationship between the `Session` and the `Product` nodes. +For example, by clicking on a product in the storefront, you send the `ProductDetailPageViewEvent` to SAP Hybris Profile. This event triggers the specified enricher, which modifies the profile document by adding sections such as `observations` or `insights`. -Follow these instructions to see a visual representation of the graph modifications associated with the Profile node. +Follow these instructions to see the profile modifications. 1. Go to the Trace Explorer to see the initial logs from context adapters that pre-process the event sent from the storefront before dispatching it to the enrichers. 2. In the **Context Transition** component of the Trace Explorer, click one of the displayed links, which represent a given **contextTraceId**. Each storefront event triggers various enrichers. What you see is a list of enricher-generated logs resulting from the storefront activity. -3. Find the log with a message about node creation that displays the schema `nodes/commerce/Session`. The log indicates that an enricher created a node defined by the schema `nodes/commerce/Session`. -4. Click the `nodes/commerce/Session` link to go to the **Graph Explorer** and view the created nodes and relations in a visualized form. -5. Find the node **Identity**, and double-click it. Double clicking loads more data dynamically. -6. See the node for **Profile**. Hover your mouse over the node to display the node's details, or click the node to display them under the graph. \ No newline at end of file +3. Find the log with a message about profile creation or updation. The log indicates that an enricher created or updated a profile with specified identifier. +4. Click the link in that log to go to the **Profile Explorer** and view the created profile document. \ No newline at end of file From 5b6fe32be4c221f53104759468143dc7b9ce36ca Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Thu, 26 Oct 2017 11:49:58 +0200 Subject: [PATCH 22/23] Updated Quickstart.md --- Quickstart.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Quickstart.md b/Quickstart.md index 330936f2b..0a81e5a71 100644 --- a/Quickstart.md +++ b/Quickstart.md @@ -76,7 +76,7 @@ Now that the simplified storefront is installed locally on your machine and read > The simplified storefront supports three event types only: `PageViewEvent`, `ProductDetailPageViewEvent` and `CategoryPageViewEvent`. -Interactions with the storefront create events. Click on the product of your choice to create a `ProductDetailPageViewEvent`, click on the youtube video link to send a `PageViewEvent`, or switch the category to send a `CategoryPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the profile, for example by creating or updating whole sections or properties. +Interactions with the storefront create events. Click on the product of your choice to create a `ProductDetailPageViewEvent`, click on the youtube video link to send a `PageViewEvent`, or switch the category to send a `CategoryPageViewEvent`. The enrichers contained within the **Profile Services for Commerce** package that your tenant is subscribed to react to these events, and introduce changes to the profile. ## Step 5: Track events @@ -85,16 +85,19 @@ You can track the events sent from the storefront to SAP Hybris Profile in the T > In the simplified storefront version, tracing is enabled by default. Therefore, no action from you is required to activate event tracking. -To navigate to the Trace Explorer, click a **contextTraceId** link that appears in the SAP Hybris Profile Toolbox each time the storefront sends an event to SAP Hybris Profile. +To navigate to the Trace Explorer, click a **Show the last event in Trace Explorer** link that appears in the SAP Hybris Profile Toolbox each time the storefront sends an event to SAP Hybris Profile. + +For example, by clicking on a product in the storefront, you send the `ProductDetailPageViewEvent` to SAP Hybris Profile. This event triggers the specified enricher, which modifies the profile document by adding sections such as `observations`. ## Step 6: View the profile changes in Profile Explorer The events that you send to SAP Hybris Profile from your storefront trigger the enrichers. Depending on the event type, the triggered enrichers introduce various changes to the profile document. -For example, by clicking on a product in the storefront, you send the `ProductDetailPageViewEvent` to SAP Hybris Profile. This event triggers the specified enricher, which modifies the profile document by adding sections such as `observations` or `insights`. +To navigate to the Profile Explorer, click a **Go to Profile Explorer** link that appears in the SAP Hybris Profile Toolbox each time the storefront sends an event to SAP Hybris Profile. -Follow these instructions to see the profile modifications. +Alternatively, follow these instructions to trace the path of the event in the Trace Explorer and navigate from there to the Profile Explorer. 1. Go to the Trace Explorer to see the initial logs from context adapters that pre-process the event sent from the storefront before dispatching it to the enrichers. 2. In the **Context Transition** component of the Trace Explorer, click one of the displayed links, which represent a given **contextTraceId**. Each storefront event triggers various enrichers. What you see is a list of enricher-generated logs resulting from the storefront activity. 3. Find the log with a message about profile creation or updation. The log indicates that an enricher created or updated a profile with specified identifier. -4. Click the link in that log to go to the **Profile Explorer** and view the created profile document. \ No newline at end of file +4. Click the link in that log to go to the **Profile Explorer** and view the created profile document. + From 713fea90b8e6c49048e8cf335d1949675341bbff Mon Sep 17 00:00:00 2001 From: Artur Nowakowski Date: Thu, 23 Aug 2018 11:17:30 +0200 Subject: [PATCH 23/23] Added Demo Toolbox integration --- public/index.html | 103 +--- public/js/app/shared/directives/y-tracking.js | 530 ------------------ .../app/shared/templates/profile-toolbox.html | 9 - public/less/_y-profile.less | 39 -- test/unit/shared/y-tracking-spec.js | 332 ----------- 5 files changed, 1 insertion(+), 1012 deletions(-) delete mode 100644 public/js/app/shared/directives/y-tracking.js delete mode 100644 public/js/app/shared/templates/profile-toolbox.html delete mode 100644 test/unit/shared/y-tracking-spec.js diff --git a/public/index.html b/public/index.html index ec892062d..ab70ee583 100644 --- a/public/index.html +++ b/public/index.html @@ -36,105 +36,6 @@
- - - @@ -335,9 +236,7 @@ }); - - + - diff --git a/public/js/app/shared/directives/y-tracking.js b/public/js/app/shared/directives/y-tracking.js deleted file mode 100644 index 1c733b689..000000000 --- a/public/js/app/shared/directives/y-tracking.js +++ /dev/null @@ -1,530 +0,0 @@ -/** - * [y] hybris Platform - * - * Copyright (c) 2000-2015 hybris AG - * All rights reserved. - * - * This software is the confidential and proprietary information of hybris - * ("Confidential Information"). You shall not disclose such Confidential - * Information and shall use it only in accordance with the terms of the - * license agreement you entered into with hybris. - */ - -'use strict'; - -angular.module('ds.ytracking', []) - .constant('yTrackingLocalStorageKey', 'ytracking') - .config(function ($httpProvider) { - $httpProvider.interceptors.push(function ($injector, $rootScope) { - return { - 'request': function (config) { - var svc = $injector.get('ytrackingSvc'); - if (config.method === 'POST' && svc.isGranted()) { - config.headers['X-B3-Sampled'] = 1; - } - return config; - }, - 'response': function (response) { - if (response.headers('Hybris-Context-Trace-Id')) { - $rootScope.$broadcast('tracing:response', response.headers('Hybris-Context-Trace-Id')); - } - return response; - } - }; - }); - }) - .directive('ytrackingCookieNotice', ['ytrackingSvc', '$window', '$timeout', function (ytrackingSvc, $window, $timeout) { - return { - restrict: 'E', - scope: {}, - link: function (scope) { - - function grant() { - ytrackingSvc.grantConsent(); - $timeout(function () { - scope.$apply(); - }); - } - - function revoke() { - ytrackingSvc.revoke(); - $timeout(function () { - scope.$apply(); - }); - } - - window.cookieconsent.initialise({ - palette: { - popup: { - background: '#000' - }, - button: { - background: '#f1d600' - } - }, - compliance: { - 'opt-in': '
{{allow}}
' - }, - elements: { - messagelink: '{{message}}' - }, - type: 'opt-in', - revokable: false, - revokeBtn: '
', - animateRevokable: false, - onInitialise: function () { - var didConsent = this.hasConsented(); - if (didConsent) { - grant(); - } - }, - onStatusChange: function () { - var didConsent = this.hasConsented(); - if (didConsent) { - grant(); - } - if (!didConsent) { - revoke(); - } - } - }); - - } - }; - }]) - .directive('ytracking', ['ytrackingSvc', '$rootScope', '$document', - function (ytrackingSvc, $rootScope, $document) { - return { - restrict: 'A', - compile: function () { - - //Init tracking - ytrackingSvc.init(); - - //Handlers for events - $rootScope.$on('product:opened', function (arg, obj) { - - var name = !!obj.product && !!obj.product.name ? obj.product.name : ''; - var category = !!obj.categories && !!obj.categories[0] ? obj.categories[0].name : ''; - var price = !!obj.prices && !!obj.prices[0] ? obj.prices[0].effectiveAmount : ''; - - ytrackingSvc.setProductViewed(obj.product.id, name, category, price); - }); - $rootScope.$on('category:opened', function (arg, obj) { - var path = ''; - //For now we are only sending last category name, in future we will send array of categories - for (var i = 0; i < obj.path.length; i++) { - path = obj.path[i].name; - } - ytrackingSvc.setCategoryViewed(path); - }); - - $rootScope.$on('customer:login', function (arg, customer) { - ytrackingSvc.customerLogIn(customer); - }); - - $rootScope.$on('search:performed', function (arg, obj) { - ytrackingSvc.searchEvent(obj.searchTerm, obj.numberOfResults); - }); - - $rootScope.$on('checkout:opened', function (arg, cart) { - ytrackingSvc.proceedToCheckout(cart); - }); - - $rootScope.$on('order:placed', function (arg, obj) { - //Send ordered cart items to piwik - for (var i = 0; i < obj.cart.items.length; i++) { - //sku, name, categoryName, unitPrice, amount - var item = obj.cart.items[i]; - var price = !!item.itemPrice && !!item.itemPrice.amount ? item.itemPrice.amount : ''; - ytrackingSvc.addEcommerceItem(item.product.id, item.product.name, '', price, item.quantity); - } - //Send order details to piwik - var orderId = obj.orderId || ''; - var totalPrice = !!obj.cart && !!obj.cart.totalPrice ? obj.cart.totalPrice.amount : ''; - var subTotalPrice = !!obj.cart && !!obj.cart.subTotalPrice.amount ? obj.cart.subTotalPrice.amount : ''; - var tax = !!obj.cart && !!obj.cart.totalTax ? obj.cart.totalTax.amount : ''; - var shippingCost = !!obj.cart && !!obj.cart.shippingCost ? obj.cart.shippingCost.amount : ''; - var discountOffered = false; - - ytrackingSvc.orderPlaced(orderId, totalPrice, subTotalPrice, tax, shippingCost, discountOffered); - }); - - $rootScope.$on('cart:updated', function (arg, obj) { - ytrackingSvc.cartUpdated(obj.cart); - }); - - // This should be maybe changed, and user should put ng-click to all banners that we want to look for - // or say to user to give to all banners that they want to follow specific class - $document.on('click', '.banner', function () { - var element = angular.element(this); - var id = element.attr('id') || ''; - var url = element.attr('href') || ''; - - ytrackingSvc.bannerClick(id, url); - }); - } - }; - }]) - .factory('ytrackingSvc', ['yTrackingLocalStorageKey', '$http', 'localStorage', '$window', '$timeout', 'GlobalData', 'settings', 'appConfig', 'CookieSvc', - function (yTrackingLocalStorageKey, $http, localStorage, $window, $timeout, GlobalData, settings, appConfig, CookieSvc) { - - var isGranted = function () { - return !!CookieSvc.getConsentReferenceCookie() && !!CookieSvc.getConsentReferenceTokenCookie(); - }; - - var internalCart = {}; - - /** - * Url for piwik service. - * appConfig dependency should be refactored out maybe and tenant and domain - * should be provided for example as parameters to ytracking directive so this tracking - * can also work for any other storefront (not just this template) - */ - var apiPath = appConfig.dynamicDomain(); - var tenantId = appConfig.storeTenant(); - - var piwikUrl = 'https://' + apiPath + '/hybris/profile-edge/v1' + '/events'; - var consentUrl = 'https://' + apiPath + '/hybris/profile-consent/v1/' + tenantId + '/consentReferences'; - - - - // We could do this in ConfigSvc. This way, consent-reference will be fetched before piwik starts tracking and sending - // events. When done in ConfigSvc then the code should probably also detect if ytracking is enabled before attmepting - // to fetch the consent-reference. - var makeOptInRequest = function () { - var req = { - method: 'POST', - url: consentUrl, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - }; - - return $http(req); - }; - - var getConsentReference = function () { - var consentReferenceCookie = CookieSvc.getConsentReferenceCookie(); - if (!!consentReferenceCookie) { - return consentReferenceCookie; - } else { - return ''; - } - }; - - /** - * Function that creates POST request to CDM endpoint - */ - var makePiwikRequest = function (obj) { - - var req = { - method: 'POST', - url: piwikUrl, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'consent-reference': getConsentReference() - }, - data: JSON.stringify(obj) - }; - - //pass 'piwik' as event type if the tracking endpoint is the edge endpoint - if (piwikUrl.indexOf('edge') >= 0) { - req.headers['event-type'] = 'piwik'; - req.headers[settings.headers.hybrisTenant] = appConfig.storeTenant(); - } - - $http(req).success(function () { - //Get all items that failed before and resend them to PIWIK server - var items = localStorage.getAllItems(yTrackingLocalStorageKey); - for (var i = 0; i < items.length; i++) { - makePiwikRequest(items[i]); - } - }).error(function () { - //Store request to localstorage so it can be sent again when possible - localStorage.addItemToArray(yTrackingLocalStorageKey, obj); - }); - - }; - - var grantConsent = function () { - if (isGranted()) { - return; - } - var tenYears = 3600 * 24 * 365 * 10; - makeOptInRequest().success(function (response) { - if (!!response.id) { - CookieSvc.setConsentReferenceCookie(response.id, tenYears); - CookieSvc.setConsentReferenceTokenCookie(response.consentReferenceToken, tenYears); - } - }); - }; - - /** - * Create object from piwik GET request - */ - var getPiwikQueryParameters = function (hash) { - var split = hash.split('&'); - - var obj = {}; - for (var i = 0; i < split.length; i++) { - var kv = split[i].split('='); - obj[kv[0]] = decodeURIComponent(kv[1] ? kv[1].replace(/\+/g, ' ') : kv[1]); - } - - //Set date for this request to current datetime when request processed. Needed from CDM for order of events. - obj.date = new Date().getTime(); - - return obj; - }; - - /** - * Function that process piwik requests - */ - var processPiwikRequest = function (e) { - if (isGranted() && getConsentReference()) { - var obj = getPiwikQueryParameters(e); - makePiwikRequest(obj); - } - }; - - /** - * Initialization of piwik - */ - var init = function () { - $window._paq = $window._paq || []; - - //Make requests to service custom - $window._paq.push(['setCustomRequestProcessing', processPiwikRequest]); - - //Set document title - $window._paq.push(['setDocumentTitle', 'PageViewEvent']); - - //Set user id to equal the user token - //$window._paq.push(['setUserId', TokenSvc.getToken().getAccessToken().toString()]); - - $window._paq.push(['setTrackerUrl', piwikUrl]); - - //Add site code. It should be . - $window._paq.push(['setSiteId', GlobalData.store.tenant + '.' + GlobalData.getSiteCode()]); - - $window._paq.push(['trackPageView']); - $window._paq.push(['enableLinkTracking']); - - }; - - /** - * Method that is setting current url - */ - var setCustomUrl = function () { - if (!!$window._paq) { - $window._paq.push(['setCustomUrl', $window.document.URL]); - } - }; - - /** - * User viewed product - */ - var setProductViewed = function (sku, name, category, price) { - if (!!$window._paq) { - //Wait current digest loop to finish, and then when the page is changed update values to PIWIK - $timeout(function () { - setCustomUrl(); - - $window._paq.push(['setEcommerceView', - sku, //(required) SKU: Product unique identifier - name, //(optional) Product name - category, //(optional) Product category, or array of up to 5 categories - price //(optional) Product Price as displayed on the page - ]); - - $window._paq.push(['trackPageView', 'ProductDetailPageViewEvent']); - }); - } - }; - - /** - * User viewed category - */ - var setCategoryViewed = function (categoryPage) { - if (!!$window._paq) { - //Wait current digest loop to finish, and then when the page is changed update values to PIWIK - $timeout(function () { - setCustomUrl(); - - $window._paq.push(['setEcommerceView', - false, //No product on Category page - false, //No product on Category page - categoryPage // Category Page, or array of up to 5 categories - ]); - - $window._paq.push(['trackPageView', 'CategoryPageViewEvent']); - }); - } - }; - - /** - * User searched event - */ - //Category missing? There is no way to set SearchEvent and SearchNoResultsEvent as action_view - var searchEvent = function (searchTerm, numberOfResults) { - if (!!$window._paq) { - if (numberOfResults > 0) { - $window._paq.push(['trackSiteSearch', - searchTerm, - false, //This is search category selected in our search. At the moment we dont have this - numberOfResults - ]); - } - else { - $window._paq.push(['trackSiteSearch', - searchTerm, - false, //This is search category selected in our search. At the moment we dont have this - 0 - ]); - } - } - }; - - /** - * User clicked on element with class banner - */ - var bannerClick = function (bannerId, url) { - if (!!$window._paq) { - $timeout(function () { - setCustomUrl(); - $window._paq.push(['setCustomVariable', 1, bannerId, url, 'page']); - $window._paq.push(['trackLink', 'BannerClickEvent', 'action_name']); - }); - } - }; - - var getCartId = function (cart) { - return !!cart.id ? cart.id : ''; - }; - - - /** - * Function for adding item to cart - */ - var addEcommerceItem = function (id, name, categoryName, unitPrice, amount) { - if (!!$window._paq) { - $window._paq.push(['addEcommerceItem', - id, // (required) SKU: Product unique identifier - name, // (optional) Product name - categoryName, // (optional) Product category. You can also specify an array of up to 5 categories eg. ["Books", "New releases", "Biography"] - unitPrice, // (recommended) Product price - amount // (optional, default to 1) Product quantity - ]); - } - }; - - /** - * User updated cart - */ - var cartUpdated = function (cart) { - var i = 0; - //Check if there is some item that is removed in new cart?? - if (!!internalCart.items) { - if (!!cart.items) { - - var productFound = false; - for (i = 0; i < internalCart.items.length; i++) { - //Check if this item exists in new cart - for (var j = 0; j < cart.items.length; j++) { - if (internalCart.items[i].product.id === cart.items[j].product.id) { - productFound = true; - break; - } - } - if (!productFound) { - //If it didn't break before that means that the item is not found and deleted - addEcommerceItem(internalCart.items[i].product.id, internalCart.items[i].product.name, '', internalCart.items[i].itemPrice.amount, 0); - } - productFound = false; - } - } - else { - //All items are removed - for (i = 0; i < internalCart.items.length; i++) { - addEcommerceItem(internalCart.items[i].product.id, internalCart.items[i].product.name, '', internalCart.items[i].itemPrice.amount, 0); - } - } - } - - if (!!cart.items) { - for (i = 0; i < cart.items.length; i++) { - //sku, name, categoryName, unitPrice, amount - var item = cart.items[i]; - addEcommerceItem(item.product.id, item.product.name, '', item.itemPrice.amount, item.quantity); - } - } - - //Records the cart for this visit - var cartId = getCartId(cart); - $window._paq.push(['setCustomVariable', 1, 'cart_id', cartId, 'page']); - $window._paq.push(['trackEcommerceCartUpdate', !!cart.totalPrice ? cart.totalPrice.amount : 0]); // (required) Cart amount - - //Save previous state for later comparasion (checking if objects are removed from cart) - internalCart = cart; - }; - - - /** - * User opened checkout page - */ - var proceedToCheckout = function (cart) { - if (!!$window._paq) { - $timeout(function () { - setCustomUrl(); - - var cartId = getCartId(cart); - $window._paq.push(['setCustomVariable', 1, 'cart_id', cartId, 'page']); - $window._paq.push(['trackLink', 'ProceedToCheckoutEvent', 'action_name']); - }); - } - }; - - /** - * User created order - */ - var orderPlaced = function (orderId, orderGrandTotal, orderSubTotal, taxAmount, shippingAmount, isDiscountOffered) { - if (!!$window._paq) { - $window._paq.push(['trackEcommerceOrder', - orderId, // (required) Unique Order ID - orderGrandTotal, // (required) Order Revenue grand total (includes tax, shipping, and subtracted discount) - orderSubTotal, // (optional) Order sub total (excludes shipping) - taxAmount, // (optional) Tax amount - shippingAmount, // (optional) Shipping amount - isDiscountOffered // (optional) Discount offered (set to false for unspecified parameter) - ]); - } - }; - - /** - * User created order - */ - var customerLogIn = function () { - if (!!$window._paq) { - $window._paq.push(['trackPageView', 'CustomerLogin']); - } - }; - - return { - cartUpdated: cartUpdated, - init: init, - addEcommerceItem: addEcommerceItem, - orderPlaced: orderPlaced, - setProductViewed: setProductViewed, - setCategoryViewed: setCategoryViewed, - setCustomUrl: setCustomUrl, - searchEvent: searchEvent, - bannerClick: bannerClick, - proceedToCheckout: proceedToCheckout, - customerLogIn: customerLogIn, - grantConsent: grantConsent, - isGranted: isGranted - }; - }]); diff --git a/public/js/app/shared/templates/profile-toolbox.html b/public/js/app/shared/templates/profile-toolbox.html deleted file mode 100644 index 019551f04..000000000 --- a/public/js/app/shared/templates/profile-toolbox.html +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/public/less/_y-profile.less b/public/less/_y-profile.less index dcf422d77..14ee8ef58 100644 --- a/public/less/_y-profile.less +++ b/public/less/_y-profile.less @@ -1,42 +1,3 @@ -.y-profile-toolbox { - position: fixed; - right: 0; - top: 40%; - width: 240px; - color: #fff; - background: @sapblue; - z-index: 100; - padding: 20px; - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - - & h4 { - color: #fff; - } - - & ul { - list-style-type: square; - padding-left: 10px; - - & li { - margin-bottom: 5px; - } - } - - & a { - color: #fff; - } - - & .truncate { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: block; - font-weight: 300; - } -} - .y-profile-header { width: 100%; background: #000; diff --git a/test/unit/shared/y-tracking-spec.js b/test/unit/shared/y-tracking-spec.js deleted file mode 100644 index b639627e6..000000000 --- a/test/unit/shared/y-tracking-spec.js +++ /dev/null @@ -1,332 +0,0 @@ -/** - * [y] hybris Platform - * - * Copyright (c) 2000-2014 hybris AG - * All rights reserved. - * - * This software is the confidential and proprietary information of hybris - * ("Confidential Information"). You shall not disclose such Confidential - * Information and shall use it only in accordance with the terms of the - * license agreement you entered into with hybris. - */ - -describe('ytracking', function () { - - var $q, $httpBackend, $scope, $compile, $rootScope, $timeout, mockedYtrackingSvc, mockedLocalStorage, mockedYtrackingDirective; - var mockedSettings = { - headers: { - hybrisTenant: 'hybris-tenant' - } - }; - var mockedAppConfig = { - storeTenant: function () { - return 'default'; - }, - dynamicDomain: jasmine.createSpy().andReturn('api.yaas.io') - }; - var consentReference = 'consent-reference'; - - var mockedGlobalData = { - store: { - tenant: 'default' - }, - getSiteCode: function () { - return 'site'; - } - }; - var mockedCookieSvc = { - setConsentReferenceCookie: jasmine.createSpy(), - getConsentReferenceCookie: jasmine.createSpy() - }; - var mockedLocalStorage = {}; - - var openedCategory = { - path: [{ name: 'cat1' }, { name: 'cat2' }] - }; - var openedProduct = { - product: { - id: 1, - name: { - en: 'prod1' - } - }, - categories: [{ - id: "62256128" - }], - prices: [{ - effectiveAmount: 24.99 - }] - }; - - var order = { - cart: { - id : 123, - items: [{ - product: { - id: '02318421', - name: 'product1' - }, - itemPrice: { - amount: 5 - }, - quantity: 2 - }], - totalPrice: 0, - subTotalPrice: { - amount: 0 - } - } - }; - - function createYTracking() { - var elem, compiledElem; - elem = angular.element('
'); - compiledElem = $compile(elem)($scope); - $scope.$digest(); - - return compiledElem; - } - - - beforeEach(angular.mock.module('ds.ytracking', function ($provide) { - $provide.value('GlobalData', mockedGlobalData); - $provide.value('localStorage', mockedLocalStorage); - $provide.value('settings', mockedSettings); - $provide.value('appConfig', mockedAppConfig); - $provide.value('CookieSvc', mockedCookieSvc); - })); - - beforeEach(inject(function (_$httpBackend_, _$q_, _$rootScope_, _$window_, _$compile_, _$timeout_) { - $httpBackend = _$httpBackend_; - $q = _$q_; - $rootScope = _$rootScope_; - $scope = $rootScope.$new(); - $window = _$window_; - $compile = _$compile_; - $timeout = _$timeout_; - - $httpBackend.whenPOST().respond(201, {id: consentReference}); - - inject(function ($injector) { - mockedYtrackingSvc = $injector.get('ytrackingSvc'); - }); - - //Create element - var element = createYTracking(); - })); - - describe('yTracking init', function () { - - it('should create $window._paq object when initialized', function () { - expect($window._paq).toBeDefined(); - }); - - it('should use custom request processing for piwik', function () { - var containsCustomProcessing = false; - for (var i = 0; i < $window._paq.length; i++) { - if ($window._paq[i][0] == 'setCustomRequestProcessing') { - containsCustomProcessing = true; - } - } - expect(containsCustomProcessing).toBe(true); - }); - }); - - describe('yTracking events', function () { - - - /*Category viewed*/ - it('should call setCategoryViewed when user navigated to category page', function () { - mockedYtrackingSvc.setCategoryViewed = jasmine.createSpy('setCategoryViewed'); - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - - $scope.$emit('category:opened', openedCategory); - - expect($scope.$emit).toHaveBeenCalledWith("category:opened", openedCategory); - expect(mockedYtrackingSvc.setCategoryViewed).toHaveBeenCalled(); - }); - - it('should add new items to _paq array when category loaded', function () { - - var lengthBefore = $window._paq.length; - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - spyOn(mockedYtrackingSvc, "setCategoryViewed").andCallThrough(); - - $scope.$emit('category:opened', openedCategory); - - $timeout.flush(); - - var lengthAfter = $window._paq.length; - expect(lengthAfter > lengthBefore).toBeTruthy(); - }); - - - - /*Product viewed*/ - it('should call setProductViewed when user navigated to product page', function () { - - mockedYtrackingSvc.setProductViewed = jasmine.createSpy('setProductViewed'); - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - - $scope.$emit('product:opened', openedProduct); - - expect($scope.$emit).toHaveBeenCalledWith("product:opened", openedProduct); - expect(mockedYtrackingSvc.setProductViewed).toHaveBeenCalled(); - }); - - it('should add new items to _paq array when product loaded', function () { - - var lengthBefore = $window._paq.length; - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - spyOn(mockedYtrackingSvc, "setProductViewed").andCallThrough(); - - $scope.$emit('product:opened', openedProduct); - - $timeout.flush(); - - var lengthAfter = $window._paq.length; - expect(lengthAfter > lengthBefore).toBeTruthy(); - }); - - - /*Site search*/ - it('should call searchEvent when user searched site', function () { - var searchObj = { - searchTerm: 'term', - numberOfResults: 3 - }; - mockedYtrackingSvc.searchEvent = jasmine.createSpy('searchEvent'); - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - - $scope.$emit('search:performed', searchObj); - - expect($scope.$emit).toHaveBeenCalledWith("search:performed", searchObj); - expect(mockedYtrackingSvc.searchEvent).toHaveBeenCalled(); - }); - it('should add new items to _paq array when user searched page', function () { - var searchObj = { - searchTerm: 'term', - numberOfResults: 3 - }; - var lengthBefore = $window._paq.length; - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - spyOn(mockedYtrackingSvc, "searchEvent").andCallThrough(); - - $scope.$emit('search:performed', searchObj); - - var lengthAfter = $window._paq.length; - expect(lengthAfter > lengthBefore).toBeTruthy(); - }); - - - /*Checkout open*/ - it('should call proceedToCheckout when user opened checkout page', function () { - var cart = {}; - mockedYtrackingSvc.proceedToCheckout = jasmine.createSpy('proceedToCheckout'); - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - - $scope.$emit('checkout:opened', cart); - - expect($scope.$emit).toHaveBeenCalledWith("checkout:opened", cart); - expect(mockedYtrackingSvc.proceedToCheckout).toHaveBeenCalled(); - }); - it('should add new items to _paq array when user opened checkout page', function () { - var cart = {}; - var lengthBefore = $window._paq.length; - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - spyOn(mockedYtrackingSvc, "proceedToCheckout").andCallThrough(); - - $scope.$emit('checkout:opened', cart); - - $timeout.flush(); - - var lengthAfter = $window._paq.length; - expect(lengthAfter > lengthBefore).toBeTruthy(); - }); - - - - - /*Order placed*/ - it('should call orderPlaced when user placed order', function () { - - mockedYtrackingSvc.orderPlaced = jasmine.createSpy('orderPlaced'); - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - - $scope.$emit('order:placed', order); - - expect($scope.$emit).toHaveBeenCalledWith("order:placed", order); - expect(mockedYtrackingSvc.orderPlaced).toHaveBeenCalled(); - }); - it('should add new items to _paq array when user opened checkout page', function () { - - var lengthBefore = $window._paq.length; - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - spyOn(mockedYtrackingSvc, "orderPlaced").andCallThrough(); - - $scope.$emit('order:placed', order); - - var lengthAfter = $window._paq.length; - expect(lengthAfter > lengthBefore).toBeTruthy(); - }); - - /*Cart updated*/ - it('should call cartUpdated when user added/removed something from cart', function () { - - mockedYtrackingSvc.cartUpdated = jasmine.createSpy('cartUpdated'); - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - - $scope.$emit('cart:updated', order); - - expect($scope.$emit).toHaveBeenCalledWith("cart:updated", order); - expect(mockedYtrackingSvc.cartUpdated).toHaveBeenCalled(); - }); - it('should add new items to _paq array when user added/removed something from cart', function () { - - var lengthBefore = $window._paq.length; - - spyOn($scope, "$emit").andCallThrough(); - spyOn($rootScope, "$on").andCallThrough(); - spyOn(mockedYtrackingSvc, "cartUpdated").andCallThrough(); - - $scope.$emit('cart:updated', order); - - var lengthAfter = $window._paq.length; - expect(lengthAfter > lengthBefore).toBeTruthy(); - - var found = false; - for (var i = 0; i < lengthAfter; i++) { - if (JSON.stringify($window._paq[i]) === JSON.stringify(['setCustomVariable', 1, 'cart_id', 123, 'page'])) { - found = true; - } - } - expect(found).toBeTruthy(); - - }); - - - }); - -});