diff --git a/.angular-cli.json b/.angular-cli.json index 85477ad2b..6739f3927 100644 --- a/.angular-cli.json +++ b/.angular-cli.json @@ -32,6 +32,7 @@ "styles.scss" ], "scripts": [ + "../node_modules/save-svg-as-png/saveSvgAsPng.js", "../node_modules/tinymce/tinymce.js", "../node_modules/tinymce/themes/modern/theme.js", "../node_modules/tinymce/plugins/lists/plugin.js", diff --git a/README.md b/README.md index 9da201950..ba4c411c3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# Pia +# Le logiciel PIA / The PIA Software + Le logiciel PIA est un outil distribué librement par la [CNIL](https://www.cnil.fr/fr/outil-pia-telechargez-et-installez-le-logiciel-de-la-cnil) afin de faciliter la réalisation d’analyses d’impact sur la protection des données prévues par le RGPD. +Version web front office de l’application PIA à déployer sur un serveur afin d’en donner l’accès via un navigateur web. Ce projet est généré avec [Angular CLI](https://github.com/angular/angular-cli). + +The PIA software is a free tool published by the [CNIL](https://www.cnil.fr/en/open-source-pia-software-helps-carry-out-data-protection-impact-assesment) which aims to help data controllers build and demonstrate compliance to the GDPR. +Front office of the PIA application to be deployed on a server in order to access it through a web browser. This project was generated with [Angular CLI](https://github.com/angular/angular-cli). -This project was generated with [Angular CLI](https://github.com/angular/angular-cli). ## Development server @@ -13,7 +17,12 @@ Run `ng generate component component-name` to generate a new component. You can ## Build Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. -Use `ng build --prod --build-optimizer --sourcemaps` for a production build. + +## Build for production + +First you need to rename the file `src/environments/environment.prod.ts.example` to `src/environments/environment.prod.ts`. +Then set the version number inside this file. +And use the command `ng build --prod --build-optimizer --sourcemaps` for a production build. ## Running unit tests @@ -24,6 +33,10 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github. Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). Before running the tests make sure you are serving the app via `ng serve`. +## Run in production mode +Run `ng build --prod` +Put `dist/` directory which is generated by ng build into your `www/` directory (like /var/www/html for default in Apache) + ## Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/bin/ci-scripts/build.sh b/bin/ci-scripts/build.sh new file mode 100755 index 000000000..443dc7392 --- /dev/null +++ b/bin/ci-scripts/build.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -ex + +if [ -f ${NVM_DIR}/nvm.sh ] +then + . ${NVM_DIR}/nvm.sh +else + echo "NVM_DIR have to be set" + exit 42 +fi + +# todo : add build option env variable +ng build + diff --git a/bin/ci-scripts/gen_archive.sh b/bin/ci-scripts/gen_archive.sh new file mode 100755 index 000000000..26117df2b --- /dev/null +++ b/bin/ci-scripts/gen_archive.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -ex + +Name=Pialab-front + +if [ -z ${Branch} ] +then + Branch=$(git name-rev --name-only $(git rev-parse HEAD) | sed -e s/\\^.*//g | awk -F'/' '{print $(NF)}') +fi + +# Clean current git dir +git clean -df +git checkout -- . + +Filename=${Name}_${Branch}.tar.gz + +rm -f ${Filename} + +rm -rf \ + *.log \ + *.nbr \ + *.dist + +tar --exclude-vcs \ + --exclude=build \ + --exclude=bin/git-scripts \ + -czhf ${Filename} ./* + +sha256sum ${Filename} > ${Filename}.sha256.txt diff --git a/bin/ci-scripts/install.sh b/bin/ci-scripts/install.sh new file mode 100755 index 000000000..6c8bd08fa --- /dev/null +++ b/bin/ci-scripts/install.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -ex + +if [ -f ${NVM_DIR}/nvm.sh ] +then + . ${NVM_DIR}/nvm.sh +else + echo "NVM_DIR have to be set" + exit 42 +fi + + +npm install --no-interaction diff --git a/bin/ci-scripts/run_test.sh b/bin/ci-scripts/run_test.sh new file mode 100755 index 000000000..764d8fd8e --- /dev/null +++ b/bin/ci-scripts/run_test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -ex + + +echo "waiting for test creation" + diff --git a/bin/ci-scripts/set_env_with_etcd.sh b/bin/ci-scripts/set_env_with_etcd.sh new file mode 100755 index 000000000..48bad8052 --- /dev/null +++ b/bin/ci-scripts/set_env_with_etcd.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -ex + +export ETCDCTL_API=3 + +# rand number to avoid build colision (same db used by two build) +if [ ! -f shuf.nbr ] +then + shuf -i 200-600 -n 1 > shuf.nbr +fi + +if [ -z "$Suffix" ] + then + #RND may contain branch with '-' or upper case char which may not work as database name for postgre + Suffix=$(echo $RND|sed -e s/-/_/g|tr '[:upper:]' '[:lower:]')$(echo -n $(cat shuf.nbr )) +fi + +if [ -z "$Prefix" ] +then + Prefix="/pialab/build/$Suffix" +fi + +if [ -z "$ETCDHOST" ] +then + ETCDHOST="etcd.host" +fi +ETCDENDPOINT="--endpoints=http://${ETCDHOST}:2379" + +if [ -z "$ETCDCTLCMD" ] +then + ETCDCTLCMD="etcdctl" +fi + +if [ -z "${APICLIENTID}" ] +then + APICLIENTID=1234 +fi + +if [ -z "${APICLIENTSECRET}" ] +then + APICLIENTSECRET=4321 +fi + +if [ -z "${BACKURL}" ] +then + BACKURL='http://localhost:8000' +fi + +# todo add env management (prod and dev) + +$ETCDCTLCMD put $Prefix/api/client/id ${APICLIENTID} $ETCDENDPOINT +$ETCDCTLCMD put $Prefix/api/client/secret ${APICLIENTSECRET} $ETCDENDPOINT +$ETCDCTLCMD put $Prefix/api/host/url ${BACKURL} $ETCDENDPOINT + +confd -onetime -backend etcdv3 -node http://${ETCDHOST}:2379 -confdir ./etc/confd -log-level debug -prefix $Prefix +cat src/environments/environment.ts diff --git a/etc/confd/conf.d/environment.toml b/etc/confd/conf.d/environment.toml new file mode 100644 index 000000000..ce96dc23d --- /dev/null +++ b/etc/confd/conf.d/environment.toml @@ -0,0 +1,8 @@ +[template] +src = "environment.ts.tmpl" +dest = "src/environments/environment.ts" +keys = [ + "/api/client/id", + "/api/client/secret", + "/api/host/url", +] diff --git a/etc/confd/templates/environment.ts.tmpl b/etc/confd/templates/environment.ts.tmpl new file mode 100644 index 000000000..a2dd95518 --- /dev/null +++ b/etc/confd/templates/environment.ts.tmpl @@ -0,0 +1,14 @@ + +export const environment = { + name: 'development', + production: false, + version: 'DEV', + rollbar_key: '', + date_format: 'DD MM YY HH:mm:ss', + api: { + client_id: '{{getv "/api/client/id"}}', + client_secret: '{{getv "/api/client/secret"}}', + host: '{{getv "/api/host/url"}}', + token_path: '/oauth/v2/token' + }, +}; diff --git a/etc/dockerfile.jenkins b/etc/dockerfile.jenkins new file mode 100644 index 000000000..c94e35c27 --- /dev/null +++ b/etc/dockerfile.jenkins @@ -0,0 +1,42 @@ +FROM debian:stable + +RUN apt-get update && apt-get install --no-install-recommends -y apt-transport-https lsb-release ca-certificates net-tools lsof postgresql-client wget \ + && apt-get install --no-install-recommends -y git curl build-essential unzip python-pip python-setuptools \ + && apt-get install --no-install-recommends -y dnsutils vim-nox\ + && apt-get autoremove -y && apt-get clean + +ENV HOME=/home/jenkins +ENV USER=jenkins +ENV GROUP=users + +ARG UID +ARG GID +RUN useradd -d $HOME -g ${GID} -u ${UID} -m $USER -s /bin/bash \ + && mkdir -p $HOME/bin \ + && chown -R $USER:$GROUP $HOME + +ARG ETCDVER=3.3.1 +RUN wget -q https://github.com/coreos/etcd/releases/download/v${ETCDVER}/etcd-v${ETCDVER}-linux-amd64.tar.gz -O /tmp/etcd.tar.gz \ + && tar -xzf /tmp/etcd.tar.gz -C /tmp \ + && mv /tmp/etcd-v${ETCDVER}-linux-amd64/etcd* /usr/local/bin/ \ + && chmod 755 /usr/local/bin/etcd* \ + && rm -rf /tmp/etcd* + +ARG CONFDVER=0.15.0 +RUN wget -q https://github.com/kelseyhightower/confd/releases/download/v${CONFDVER}/confd-${CONFDVER}-linux-amd64 -O /usr/local/bin/confd \ + && chmod 755 /usr/local/bin/confd \ + && mkdir -p /etc/confd/conf.d \ + && mkdir -p /etc/confd/templates + +USER $USER:$GROUP +WORKDIR $HOME + +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin/:$HOME/bin:$HOME/.local/bin/ + +ENV NVM_DIR="$HOME/.nvm" +RUN curl -so- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash \ + && [ -s "$NVM_DIR/nvm.sh" ] \ + && . "$NVM_DIR/nvm.sh" \ + && nvm install 8.11.1 \ + && npm install -g @angular/cli + diff --git a/etc/pipeline.jenkins b/etc/pipeline.jenkins new file mode 100644 index 000000000..4d685e1a3 --- /dev/null +++ b/etc/pipeline.jenkins @@ -0,0 +1,71 @@ +pipeline { + agent { + dockerfile { + filename "etc/dockerfile.jenkins" + additionalBuildArgs '--build-arg UID=$(id -u) --build-arg GID=$(id -g)' + args '--network=ci.network' + } + } + + environment { + NVM_DIR="/home/jenkins/.nvm" + ETCDHOST="etcd.host" + /* warnings, can't use "$HOME/.nvm" as it is not the same in docker and host ... */ + } + + options { + timeout(time: 1, unit: 'HOURS') + timestamps() + disableConcurrentBuilds() + ansiColor('xterm') + } + + stages { + stage ('Where Am I') { + steps { + sh "uname -a" + sh ". ${env.NVM_DIR}/nvm.sh && npm -v" + sh ". ${env.NVM_DIR}/nvm.sh && node -v" + } + } + + stage ('Set Env') { + steps { + sh "./bin/ci-scripts/set_env_with_etcd.sh" + } + } + + stage ('Composer Install') { + steps { + sh "./bin/ci-scripts/install.sh" + } + } + + stage ('Build Project') { + steps { + sh "./bin/ci-scripts/build.sh" + } + } + + stage ('Run Test') { + steps { + sh "./bin/ci-scripts/run_test.sh" + } + } + + stage ('Create Archive') { + steps { + sh 'Branch=${BRANCH_NAME} ./bin/ci-scripts/gen_archive.sh' + archiveArtifacts artifacts: "*.tar.gz*", fingerprint: true + build job: 'Copy Artifact', parameters: [string(name: 'Job', value: "${JOB_NAME}"), string(name: 'Project', value: "Pialab"), string(name: 'Branch', value: "${BRANCH_NAME}"), string(name: 'DoDeploy', value: "false")], wait: false + } + } + + } + + post { + always { + cleanWs() + } + } +} diff --git a/package-lock.json b/package-lock.json index 67c7a025f..22af4bd83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -317,6 +317,11 @@ "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", "dev": true }, + "@types/url-template": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/@types/url-template/-/url-template-2.0.28.tgz", + "integrity": "sha512-1i/YtOhvlWDbMDTWhCfvhyUwBS9vNFs78sJOyahoruJCcDbwaSH73AlnuCp7luKPm6qqdCg4VKq/IHUncl6gZA==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -420,6 +425,11 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular2-csv": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/angular2-csv/-/angular2-csv-0.2.5.tgz", + "integrity": "sha1-gXn0OS7VxitAQ7+Bnjv8Huir+PM=" + }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", @@ -737,11 +747,27 @@ "babel-runtime": "6.26.0" } }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.3", + "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + } + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "2.5.3", "regenerator-runtime": "0.11.1" @@ -842,8 +868,7 @@ "base64-arraybuffer": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" }, "base64-js": { "version": "1.2.3", @@ -1291,7 +1316,7 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", - "fsevents": "1.1.3", + "fsevents": "1.2.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -1875,6 +1900,14 @@ "randomfill": "1.0.4" } }, + "css-line-break": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.0.1.tgz", + "integrity": "sha1-GfIGOjPpX7KDG4ZEbAuAwYivRQo=", + "requires": { + "base64-arraybuffer": "0.1.5" + } + }, "css-parse": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", @@ -3446,39 +3479,29 @@ "dev": true }, "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.3.tgz", + "integrity": "sha512-X+57O5YkDTiEQGiw8i7wYc2nQgweIekqkepI8Q3y4wVlurgBt2SuwxTeYUYMZIGpLZH3r/TsMjczCMXE5ZOt7Q==", "dev": true, "optional": true, "requires": { "nan": "2.9.2", - "node-pre-gyp": "0.6.39" + "node-pre-gyp": "0.9.1" }, "dependencies": { "abbrev": { - "version": "1.1.0", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, "ansi-regex": { "version": "2.1.1", "bundled": true, "dev": true }, "aproba": { - "version": "1.1.1", + "version": "1.2.0", "bundled": true, "dev": true, "optional": true @@ -3490,91 +3513,25 @@ "optional": true, "requires": { "delegates": "1.0.0", - "readable-stream": "2.2.9" + "readable-stream": "2.3.6" } }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, "balanced-match": { - "version": "0.4.2", + "version": "1.0.0", "bundled": true, "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, "brace-expansion": { - "version": "1.1.7", + "version": "1.1.11", "bundled": true, "dev": true, "requires": { - "balanced-match": "0.4.2", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", + "chownr": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true @@ -3584,14 +3541,6 @@ "bundled": true, "dev": true }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, "concat-map": { "version": "0.0.1", "bundled": true, @@ -3605,35 +3554,11 @@ "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } + "optional": true }, "debug": { - "version": "2.6.8", + "version": "2.6.9", "bundled": true, "dev": true, "optional": true, @@ -3647,11 +3572,6 @@ "dev": true, "optional": true }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, "delegates": { "version": "1.0.0", "bundled": true, @@ -3659,74 +3579,25 @@ "optional": true }, "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", + "version": "1.0.3", "bundled": true, "dev": true, "optional": true }, - "form-data": { - "version": "2.1.4", + "fs-minipass": { + "version": "1.2.5", "bundled": true, "dev": true, "optional": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" + "minipass": "2.2.4" } }, "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } + "optional": true }, "gauge": { "version": "2.7.4", @@ -3734,7 +3605,7 @@ "dev": true, "optional": true, "requires": { - "aproba": "1.1.1", + "aproba": "1.2.0", "console-control-strings": "1.1.0", "has-unicode": "2.0.1", "object-assign": "4.1.1", @@ -3744,27 +3615,11 @@ "wide-align": "1.1.2" } }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "glob": { "version": "7.1.2", "bundled": true, "dev": true, + "optional": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -3774,64 +3629,35 @@ "path-is-absolute": "1.0.1" } }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, "has-unicode": { "version": "2.0.1", "bundled": true, "dev": true, "optional": true }, - "hawk": { - "version": "3.1.3", + "iconv-lite": { + "version": "0.4.21", "bundled": true, "dev": true, + "optional": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "safer-buffer": "2.1.2" } }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", + "ignore-walk": { + "version": "3.0.1", "bundled": true, "dev": true, "optional": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" + "minimatch": "3.0.4" } }, "inflight": { "version": "1.0.6", "bundled": true, "dev": true, + "optional": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -3843,7 +3669,7 @@ "dev": true }, "ini": { - "version": "1.3.4", + "version": "1.3.5", "bundled": true, "dev": true, "optional": true @@ -3856,111 +3682,43 @@ "number-is-nan": "1.0.1" } }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, "isarray": { "version": "1.0.0", "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, "dev": true, "optional": true }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", + "minimatch": { + "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } + "brace-expansion": "1.1.11" } }, - "mime-db": { - "version": "1.27.0", + "minimist": { + "version": "0.0.8", "bundled": true, "dev": true }, - "mime-types": { - "version": "2.1.15", + "minipass": { + "version": "2.2.4", "bundled": true, "dev": true, "requires": { - "mime-db": "1.27.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, - "minimatch": { - "version": "3.0.4", + "minizlib": { + "version": "1.1.0", "bundled": true, "dev": true, + "optional": true, "requires": { - "brace-expansion": "1.1.7" + "minipass": "2.2.4" } }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, "mkdirp": { "version": "0.5.1", "bundled": true, @@ -3975,23 +3733,33 @@ "dev": true, "optional": true }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" + } + }, "node-pre-gyp": { - "version": "0.6.39", + "version": "0.9.1", "bundled": true, "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.2", - "hawk": "3.1.3", + "detect-libc": "1.0.3", "mkdirp": "0.5.1", + "needle": "2.2.0", "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.6", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" } }, "nopt": { @@ -4000,12 +3768,28 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { - "version": "4.1.0", + "version": "4.1.2", "bundled": true, "dev": true, "optional": true, @@ -4021,12 +3805,6 @@ "bundled": true, "dev": true }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, "object-assign": { "version": "4.1.1", "bundled": true, @@ -4054,7 +3832,7 @@ "optional": true }, "osenv": { - "version": "0.1.4", + "version": "0.1.5", "bundled": true, "dev": true, "optional": true, @@ -4066,39 +3844,23 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", + "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, "rc": { - "version": "1.2.1", + "version": "1.2.6", "bundled": true, "dev": true, "optional": true, "requires": { "deep-extend": "0.4.2", - "ini": "1.3.4", + "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, @@ -4112,112 +3874,63 @@ } }, "readable-stream": { - "version": "2.2.9", + "version": "2.3.6", "bundled": true, "dev": true, + "optional": true, "requires": { - "buffer-shims": "1.0.0", "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, "rimraf": { - "version": "2.6.1", + "version": "2.6.2", "bundled": true, "dev": true, + "optional": true, "requires": { "glob": "7.1.2" } }, "safe-buffer": { - "version": "5.0.1", + "version": "5.1.1", "bundled": true, "dev": true }, - "semver": { - "version": "5.3.0", + "safer-buffer": { + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, - "set-blocking": { - "version": "2.0.0", + "sax": { + "version": "1.2.4", "bundled": true, "dev": true, "optional": true }, - "signal-exit": { - "version": "3.0.2", + "semver": { + "version": "5.5.0", "bundled": true, "dev": true, "optional": true }, - "sntp": { - "version": "1.0.9", + "set-blocking": { + "version": "2.0.0", "bundled": true, "dev": true, - "requires": { - "hoek": "2.16.3" - } + "optional": true }, - "sshpk": { - "version": "1.13.0", + "signal-exit": { + "version": "3.0.2", "bundled": true, "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } + "optional": true }, "string-width": { "version": "1.0.2", @@ -4230,19 +3943,14 @@ } }, "string_decoder": { - "version": "1.0.1", + "version": "1.1.1", "bundled": true, "dev": true, + "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "5.1.1" } }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, "strip-ansi": { "version": "3.0.1", "bundled": true, @@ -4258,81 +3966,26 @@ "optional": true }, "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", + "version": "4.4.1", "bundled": true, "dev": true, "optional": true, "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, "dev": true, "optional": true }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, "wide-align": { "version": "1.1.2", "bundled": true, @@ -4346,6 +3999,11 @@ "version": "1.0.2", "bundled": true, "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true } } }, @@ -4882,6 +4540,14 @@ } } }, + "html2canvas": { + "version": "1.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz", + "integrity": "sha1-OxmS48mz9WBjw1/WIElPN+uohRM=", + "requires": { + "css-line-break": "1.0.1" + } + }, "htmlparser2": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", @@ -6434,6 +6100,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", + "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -7961,8 +7632,7 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regex-cache": { "version": "0.4.4", @@ -8286,6 +7956,11 @@ "https-proxy-agent": "1.0.0" } }, + "save-svg-as-png": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/save-svg-as-png/-/save-svg-as-png-1.3.2.tgz", + "integrity": "sha512-aNm/UNIZ/E9XzhbQCSAUJqY6PfTpdRod1DC2mdNaPUQ4NhFi4JkAceaGPXhDk8cm6bF+c+EAtMpmzdwt9IdUhA==" + }, "sax": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", @@ -10009,6 +9684,11 @@ } } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "use": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", @@ -10312,7 +9992,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.1", - "fsevents": "1.1.3", + "fsevents": "1.2.3", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -11053,7 +10733,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.1", - "fsevents": "1.1.3", + "fsevents": "1.2.3", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", diff --git a/package.json b/package.json index fa73d810b..393fa6bd2 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,22 @@ "@angular/router": "^4.4.3", "@ngx-translate/core": "^8.0.0", "@ngx-translate/http-loader": "^1.0.2", + "@types/url-template": "^2.0.28", + "angular2-csv": "^0.2.5", + "babel-polyfill": "^6.26.0", "core-js": "^2.4.1", "d3": "^4.10.2", "font-awesome": "^4.7.0", "foundation-sites": "^6.3.1", + "html2canvas": "^1.0.0-alpha.10", + "moment": "^2.22.1", "ngx-chips": "~1.5.11", "normalize.css": "^7.0.0", "rollbar": "^2.2.8", "rxjs": "^5.1.0", + "save-svg-as-png": "^1.3.1", "tinymce": "^4.6.7", + "url-template": "^2.0.8", "zone.js": "^0.8.16" }, "devDependencies": { diff --git a/src/api/api.module.ts b/src/api/api.module.ts new file mode 100644 index 000000000..ec6644d70 --- /dev/null +++ b/src/api/api.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { HttpModule } from '@angular/http'; + +import { AnswerService } from '@api/service/answer.service'; +import { AttachmentService } from '@api/service/attachment.service'; +import { CommentService } from '@api/service/comment.service'; +import { EvaluationService } from '@api/service/evaluation.service'; +import { MeasureService } from '@api/service/measure.service'; +import { PiaService } from '@api/service/pia.service'; + +@NgModule({ + declarations: [], + imports: [ + HttpModule + ], + providers: [ + AnswerService, + AttachmentService, + CommentService, + EvaluationService, + MeasureService, + PiaService + ] +}) + +export class ApiModule { } diff --git a/src/api/model/answer.model.ts b/src/api/model/answer.model.ts new file mode 100644 index 000000000..7a5f9977e --- /dev/null +++ b/src/api/model/answer.model.ts @@ -0,0 +1,10 @@ + +import {BaseModel} from '@api/model/base.model' + +export class Answer extends BaseModel { + public id: any; + public pia_id: any; + public reference_to: string; + public data: { text: string, gauge: number, list: string[] }; + public answer_type: string; +} diff --git a/src/api/model/attachment.model.ts b/src/api/model/attachment.model.ts new file mode 100644 index 000000000..dc04e25d5 --- /dev/null +++ b/src/api/model/attachment.model.ts @@ -0,0 +1,14 @@ + +import { BaseModel } from '@api/model/base.model' + +export class Attachment extends BaseModel { + + public id: any; + public pia_id: any; + public file: Blob; + public name: string; + public mime_type: string; + public pia_signed = 0; + public comment: string; + +} diff --git a/src/api/model/base.model.ts b/src/api/model/base.model.ts new file mode 100644 index 000000000..bc4325eb1 --- /dev/null +++ b/src/api/model/base.model.ts @@ -0,0 +1,35 @@ +import { Timestampable } from '@api/model/timestampable.model' +import * as moment from 'moment'; + +export class BaseModel implements Timestampable { + + public id: any; + public created_at: Date; + public updated_at: Date; + + public fromJson(json: any): this { + if (typeof json === 'string') { + return this.fromJson(JSON.parse(json)); + } else { + return Object.assign(this, json, this.mapFromJson(json)); + } + } + + protected mapFromJson(json:any):any{ + return { + created_at: json.created_at ? moment(json.created_at).toDate():null, + updated_at: json.updated_at ? moment(json.updated_at).toDate():null + } + } + + public toJson(): any { + return Object.assign({}, this, this.mapToJson()); + } + + protected mapToJson():any{ + return { + created_at: this.created_at ? moment(this.created_at).format() : null, + updated_at: this.updated_at ? moment(this.updated_at).format() : null + } + } +} diff --git a/src/api/model/comment.model.ts b/src/api/model/comment.model.ts new file mode 100644 index 000000000..47c276bf2 --- /dev/null +++ b/src/api/model/comment.model.ts @@ -0,0 +1,9 @@ +import { BaseModel } from '@api/model/base.model' + +export class Comment extends BaseModel { + public id: any; + public pia_id: any; + public reference_to: string; + public description: string; + public for_measure: boolean; +} diff --git a/src/api/model/evaluation.model.ts b/src/api/model/evaluation.model.ts new file mode 100644 index 000000000..bf770d5c3 --- /dev/null +++ b/src/api/model/evaluation.model.ts @@ -0,0 +1,38 @@ + +import { BaseModel } from '@api/model/base.model' +import * as moment from 'moment'; + +export class Evaluation extends BaseModel { + + public id: any; + public pia_id: any; + public reference_to: string; + public status = 0; // 0: pending, 1: toBeFixed, 2: improvable, 3: acceptable + public action_plan_comment: string; + public evaluation_comment: string; + public evaluation_date: Date; + public gauges: { x: number, y: number } = { x: 0, y: 0 }; + public estimated_implementation_date: Date; + public person_in_charge: string; + public global_status = 0; // 0: No evaluation, 1: Evaluation started, 2: Evaluation completed + + + public getStatusLabel(): string { + return this.status >= 0 ? `evaluations.status.${this.status}` : ''; + } + + public mapFromJson(json: any): any { + return Object.assign({},super.mapFromJson(json),{ + evaluation_date : json.evaluation_date ? moment(json.evaluation_date).toDate():null, + estimated_implementation_date : json.estimated_implementation_date?moment(json.estimated_implementation_date).toDate():null + }); + } + + public mapToJson(): any { + return Object.assign({},super.mapToJson(),{ + evaluation_date : this.evaluation_date ? moment(this.evaluation_date).format() : moment().format(), + estimated_implementation_date : this.estimated_implementation_date ? moment(this.estimated_implementation_date).format() : moment().format(), + }); + } + +} diff --git a/src/api/model/measure.model.ts b/src/api/model/measure.model.ts new file mode 100644 index 000000000..2d3ba73b6 --- /dev/null +++ b/src/api/model/measure.model.ts @@ -0,0 +1,12 @@ + +import { BaseModel } from '@api/model/base.model' + +export class Measure extends BaseModel { + + public id: any; + public pia_id: any; + public title: string; + public content: string; + public placeholder: string; + +} diff --git a/src/api/model/pia.model.ts b/src/api/model/pia.model.ts new file mode 100644 index 000000000..12afa4dd2 --- /dev/null +++ b/src/api/model/pia.model.ts @@ -0,0 +1,34 @@ + +import { BaseModel } from '@api/model/base.model' + +export class Pia extends BaseModel { + public id: any; + public status = 0; // 0: doing, 1: refused, 2: simple_validation, 3: signed_validation, 4: archived + public name: string; + public author_name: string; + public evaluator_name: string; + public validator_name: string; + public dpo_status: number; // 0: NOK, 1: OK + public dpo_opinion: string; + public concerned_people_opinion: string; + public concerned_people_status: number; // 0: NOK, 1: OK + public concerned_people_searched_opinion: boolean; // 0 : false, 1: true + public concerned_people_searched_content: string; + public rejected_reason: string; + public applied_adjustements: string; + public dpos_names: string; + public people_names: string; + public progress: number; + public is_example = false; + + public numberOfQuestions = 36; // TODO Auto compute questions number + + + public getStatusLabel(): string { + return this.status >= 0 ? `pia.statuses.${this.status}` : ''; + } + + getGaugeLabel(value: any): string { + return value ? `summary.gauges.${value}` : ''; + } +} diff --git a/src/api/model/timestampable.model.ts b/src/api/model/timestampable.model.ts new file mode 100644 index 000000000..b96f1364f --- /dev/null +++ b/src/api/model/timestampable.model.ts @@ -0,0 +1,7 @@ + +export interface Timestampable { + + created_at: Date; + updated_at: Date; + +} diff --git a/src/api/models.ts b/src/api/models.ts new file mode 100644 index 000000000..25a4bc97c --- /dev/null +++ b/src/api/models.ts @@ -0,0 +1,6 @@ +export { Pia as PiaModel } from '@api/model/pia.model'; +export { Answer as AnswerModel } from '@api/model/answer.model'; +export { Comment as CommentModel } from '@api/model/comment.model'; +export { Evaluation as EvaluationModel } from '@api/model/evaluation.model'; +export { Measure as MeasureModel } from '@api/model/measure.model'; +export { Attachment as AttachmentModel } from '@api/model/attachment.model'; diff --git a/src/api/service/answer.service.ts b/src/api/service/answer.service.ts new file mode 100644 index 000000000..4839364d6 --- /dev/null +++ b/src/api/service/answer.service.ts @@ -0,0 +1,46 @@ + +import { BaseService } from '@api/service/base.service'; +import { Observable } from 'rxjs/Observable'; +import { Http } from '@angular/http'; +import { Answer } from '@api/model/answer.model'; +import { Injectable } from '@angular/core'; +import { BaseModel } from '@api/model/base.model'; + +@Injectable() +export class AnswerService extends BaseService { + + protected modelClass = Answer; + + protected routing: any = { + all: '/pias/{piaId}/answers', + one: '/pias/{piaId}/answers/{id}' + }; + + public getAll(piaId: any): Observable { + return this.httpGetAll(this.routing.all, { piaId: piaId }); + } + + public get(piaId: any, id: any): Observable { + return this.httpGetOne(this.routing.one, { piaId: piaId, id: id }); + } + + public getByRef(piaId: any, ref: any): Observable { + return this.httpGetFirst(this.routing.all, { piaId: piaId }, { reference_to: ref }); + } + + public update(model: Answer): Observable { + return this.httpPut(this.routing.one, { piaId: model.pia_id, id: model.id }, model); + } + + public create(model: Answer): Observable { + return this.httpPost(this.routing.all, { piaId: model.pia_id }, model); + } + + public deleteById(piaId: any, id: any): Observable { + return this.httpDelete(this.routing.one, { piaId: piaId, id: id }); + } + + public delete(model: Answer): Observable { + return this.deleteById(model.pia_id, model.id); + } +} diff --git a/src/api/service/attachment.service.ts b/src/api/service/attachment.service.ts new file mode 100644 index 000000000..64b553cff --- /dev/null +++ b/src/api/service/attachment.service.ts @@ -0,0 +1,42 @@ + +import { BaseService } from '@api/service/base.service'; +import { Observable } from 'rxjs/Observable'; +import { Http} from '@angular/http'; +import { Attachment } from '@api/model/attachment.model'; +import { Injectable } from '@angular/core'; +import { BaseModel } from '@api/model/base.model'; + +@Injectable() +export class AttachmentService extends BaseService { + + protected modelClass = Attachment; + + protected routing: any = { + all: '/pias/{piaId}/attachments', + one: '/pias/{piaId}/attachments/{id}' + }; + + public getAll(piaId: any): Observable { + return this.httpGetAll(this.routing.all, {piaId: piaId}); + } + + public get(piaId: any, id: any): Observable { + return this.httpGetOne(this.routing.one, {piaId: piaId, id: id }); + } + + public update(model: Attachment): Observable { + return this.httpPut(this.routing.one, {piaId: model.pia_id, id: model.id }, model); + } + + public create(model: Attachment): Observable { + return this.httpPost(this.routing.all, {piaId: model.pia_id}, model); + } + + public deleteById(piaId: any, id: any): Observable { + return this.httpDelete(this.routing.one, {piaId: piaId, id: id }); + } + + public delete(model: Attachment): Observable { + return this.deleteById(model.pia_id, model.id); + } +} diff --git a/src/api/service/base.service.ts b/src/api/service/base.service.ts new file mode 100644 index 000000000..07340d824 --- /dev/null +++ b/src/api/service/base.service.ts @@ -0,0 +1,80 @@ + +import { BaseModel } from '@api/model/base.model'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import * as UrlTemplate from 'url-template'; +import { Injectable } from '@angular/core'; +import { environment } from 'environments/environment'; + +@Injectable() +export class BaseService { + protected modelClass: any; + protected routing: any; + protected host = environment.api.host; + + constructor(protected http: HttpClient) { } + + protected httpGetAll(routeTpl: string, params: any = {}, query: any = {}): Observable { + query = this.buildQuery(query); + const route = this.buildRoute(routeTpl, params); + + return this.http.get(route, {params: query}).map(res => this.mapToCollection(res, this.modelClass)); + } + + protected httpGetOne(routeTpl: string, params: any = {}, query: any = {}): Observable { + query = this.buildQuery(query); + const route = this.buildRoute(routeTpl, params); + + return this.http.get(route, query).map(res => this.mapToModel(res, this.modelClass)); + } + + protected httpGetFirst(routeTpl: string, params: any = {}, query: any = {}): Observable { + + return this.httpGetAll(routeTpl, params, query).first(null, res => res[0], null); + } + + protected httpPut(routeTpl: string, params: any = {}, model: T, query: any = {}): Observable { + query = this.buildQuery(query); + const route = this.buildRoute(routeTpl, params); + + return this.http.put(route, model.toJson(), {params: query}).map(res => this.mapToModel(res, this.modelClass)); + } + + protected httpPost(routeTpl: string, params: any = {}, model: T, query: any = {}): Observable { + query = this.buildQuery(query); + const route = this.buildRoute(routeTpl, params); + + return this.http.post(route, model.toJson(), {params: query}).map(res => this.mapToModel(res, this.modelClass)); + } + + protected httpDelete(routeTpl: string, params: any = {}, query: any = {}): Observable { + query = this.buildQuery(query); + const route = this.buildRoute(routeTpl, params); + + return this.http.delete(route, {params: query}).map(res => this.mapToModel(res, this.modelClass)); + } + + + protected buildRoute(route: string, params: any = {}): string { + const tpl = UrlTemplate.parse(this.host + route); + return tpl.expand(params); + } + + protected mapToModel(res: any, modelClass: new () => T) { + return (new modelClass()).fromJson(res); + } + + protected mapToCollection(res: any, modelClass: new () => T) { + return res.map(item => (new modelClass()).fromJson(item)); + } + + protected buildQuery(query: any): HttpParams { + let params = new HttpParams(); + + for (let key in query) { + params = params.set(key, query[key]); + } + + return params; + } +} diff --git a/src/api/service/comment.service.ts b/src/api/service/comment.service.ts new file mode 100644 index 000000000..75c77bed6 --- /dev/null +++ b/src/api/service/comment.service.ts @@ -0,0 +1,46 @@ + +import { BaseService } from '@api/service/base.service'; +import { Observable } from 'rxjs/Observable'; +import { Http} from '@angular/http'; +import { Comment } from '@api/model/comment.model'; +import { Injectable } from '@angular/core'; +import { BaseModel } from '@api/model/base.model'; + +@Injectable() +export class CommentService extends BaseService { + + protected modelClass = Comment; + + protected routing: any = { + all: '/pias/{piaId}/comments', + one: '/pias/{piaId}/comments/{id}' + }; + + public getAll(piaId: any): Observable { + return this.httpGetAll(this.routing.all, {piaId: piaId}); + } + + public get(piaId: any, id: any): Observable { + return this.httpGetOne(this.routing.one, {piaId: piaId, id: id }); + } + + public getAllByRef(piaId: any, ref: any): Observable { + return this.httpGetAll(this.routing.all, { piaId: piaId }, { reference_to: ref }); + } + + public update(model: Comment): Observable { + return this.httpPut(this.routing.one, {piaId: model.pia_id, id: model.id }, model); + } + + public create(model: Comment): Observable { + return this.httpPost(this.routing.all, {piaId: model.pia_id}, model); + } + + public deleteById(piaId: any, id: any): Observable { + return this.httpDelete(this.routing.one, {piaId: piaId, id: id }); + } + + public delete(model: Comment): Observable { + return this.deleteById(model.pia_id, model.id); + } +} diff --git a/src/api/service/evaluation.service.ts b/src/api/service/evaluation.service.ts new file mode 100644 index 000000000..a141d038c --- /dev/null +++ b/src/api/service/evaluation.service.ts @@ -0,0 +1,46 @@ + +import { BaseService } from '@api/service/base.service'; +import { Observable } from 'rxjs/Observable'; +import { Http} from '@angular/http'; +import { Evaluation } from '@api/model/evaluation.model'; +import { Injectable } from '@angular/core'; +import { BaseModel } from '@api/model/base.model'; + +@Injectable() +export class EvaluationService extends BaseService { + + protected modelClass = Evaluation; + + protected routing: any = { + all: '/pias/{piaId}/evaluations', + one: '/pias/{piaId}/evaluations/{id}' + }; + + public getAll(piaId: any): Observable { + return this.httpGetAll(this.routing.all, {piaId: piaId}); + } + + public get(piaId: any, id: any): Observable { + return this.httpGetOne(this.routing.one, {piaId: piaId, id: id }); + } + + public getByRef(piaId: any, ref: any): Observable { + return this.httpGetFirst(this.routing.all, {piaId: piaId}, {reference_to: ref}); + } + + public update(model: Evaluation): Observable { + return this.httpPut(this.routing.one, {piaId: model.pia_id, id: model.id }, model); + } + + public create(model: Evaluation): Observable { + return this.httpPost(this.routing.all, {piaId: model.pia_id}, model); + } + + public deleteById(piaId: any, id: any): Observable { + return this.httpDelete(this.routing.one, {piaId: piaId, id: id }); + } + + public delete(model: Evaluation): Observable { + return this.deleteById(model.pia_id, model.id); + } +} diff --git a/src/api/service/measure.service.ts b/src/api/service/measure.service.ts new file mode 100644 index 000000000..bd9672c60 --- /dev/null +++ b/src/api/service/measure.service.ts @@ -0,0 +1,46 @@ + +import { BaseService } from '@api/service/base.service'; +import { Observable } from 'rxjs/Observable'; +import { Http } from '@angular/http'; +import { Measure } from '@api/model/measure.model'; +import { Injectable } from '@angular/core'; +import { BaseModel } from '@api/model/base.model'; + +@Injectable() +export class MeasureService extends BaseService { + + protected modelClass = Measure; + + protected routing: any = { + all: '/pias/{piaId}/measures', + one: '/pias/{piaId}/measures/{id}' + }; + + public getAll(piaId: any): Observable { + return this.httpGetAll(this.routing.all, { piaId: piaId }); + } + + public get(piaId: any, id: any): Observable { + return this.httpGetOne(this.routing.one, { piaId: piaId, id: id }); + } + + public getByRef(piaId: any, ref: any): Observable { + return this.httpGetFirst(this.routing.all, { piaId: piaId }, { reference_to: ref }); + } + + public update(model: Measure): Observable { + return this.httpPut(this.routing.one, { piaId: model.pia_id, id: model.id }, model); + } + + public create(model: Measure): Observable { + return this.httpPost(this.routing.all, { piaId: model.pia_id }, model); + } + + public deleteById(piaId: any, id: any): Observable { + return this.httpDelete(this.routing.one, { piaId: piaId, id: id }); + } + + public delete(model: Measure): Observable { + return this.deleteById(model.pia_id, model.id); + } +} diff --git a/src/api/service/pia.service.ts b/src/api/service/pia.service.ts new file mode 100644 index 000000000..be2ce3ccf --- /dev/null +++ b/src/api/service/pia.service.ts @@ -0,0 +1,59 @@ + +import { BaseService } from '@api/service/base.service'; +import { Observable } from 'rxjs/Observable'; +import { HttpClient } from '@angular/common/http'; +import { Pia } from '@api/model/pia.model'; +import { Evaluation } from '@api/model/evaluation.model'; +import { Answer } from '@api/model/answer.model'; +import { Injectable } from '@angular/core'; +import { BaseModel } from '@api/model/base.model'; +import { AnswerService } from '@api/service/answer.service'; + +@Injectable() +export class PiaService extends BaseService { + + protected modelClass = Pia; + + protected routing: any = { + all: '/pias', + one: '/pias/{id}', + }; + + constructor(http: HttpClient, protected answerService: AnswerService) { + super(http); + } + + + public computeProgress(model: Pia): Observable { + return this.answerService.getAll(model.id).map((answers:Answer[]) => { + model.progress = Math.round((100 / model.numberOfQuestions) * answers.length); + return model.progress; + }); + + } + + public getAll(): Observable { + return this.httpGetAll(this.routing.all); + } + + public get(id: any): Observable { + return this.httpGetOne(this.routing.one, { id: id }); + } + + public update(model: Pia): Observable { + return this.httpPut(this.routing.one, { id: model.id }, model); + } + + public create(model: Pia): Observable { + return this.httpPost(this.routing.all, {}, model); + } + + public deleteById(id: any): Observable { + return this.httpDelete(this.routing.one, { id: id }); + } + + public delete(model: Pia): Observable { + return this.deleteById(model.id); + } + +} diff --git a/src/api/services.ts b/src/api/services.ts new file mode 100644 index 000000000..828a629e7 --- /dev/null +++ b/src/api/services.ts @@ -0,0 +1,6 @@ +export { PiaService as PiaApi } from '@api/service/pia.service'; +export { AnswerService as AnswerApi } from '@api/service/answer.service'; +export { CommentService as CommentApi } from '@api/service/comment.service'; +export { EvaluationService as EvaluationApi } from '@api/service/evaluation.service'; +export { MeasureService as MeasureApi } from '@api/service/measure.service'; +export { AttachmentService as AttachmentApi } from '@api/service/attachment.service'; diff --git a/src/app/about/about.component.scss b/src/app/about/about.component.scss index 21168e2be..7d85fef52 100644 --- a/src/app/about/about.component.scss +++ b/src/app/about/about.component.scss @@ -7,7 +7,7 @@ } &-piaLogo { padding-left: 260px; - background: url('../../assets/images/pia-auth-logo.png') no-repeat center left transparent; + background: url('../../assets/images/pialab.png') no-repeat center left transparent; height: 123px; margin-bottom: 40px; &-text { diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 0b2b23c03..010aa3ad8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; +import { Routes, RouterModule, CanActivate } from '@angular/router'; import { AuthenticationComponent } from 'app/authentication/authentication.component'; import { SummaryComponent } from 'app/summary/summary.component'; @@ -10,22 +10,38 @@ import { ErrorsComponent } from 'app/errors/errors.component'; import { CardsRoutingModule } from 'app/cards/cards-routing.module'; import { EntryRoutingModule } from 'app/entry/entry-routing.module'; +import { AuthenticationGuardService } from 'app/services/authentication-guard.service'; +//import { AuthenticationCallbackComponent } from 'app/authentication-callback/authentication-callback.component'; +import {PiaResolve} from 'app/services/pia.resolve.service'; +import {PiaService} from 'app/entry/pia.service'; const routes: Routes = [ { path: '', component: AuthenticationComponent }, - { path: 'summary/:id/:type', component: SummaryComponent }, - { path: 'settings', component: SettingsComponent }, + { path: 'logout', component: AuthenticationComponent }, + { path: + 'summary/:id', + component: SummaryComponent , + canActivate: [AuthenticationGuardService, PiaResolve] + }, + { + path: 'settings', + component: SettingsComponent, + canActivate: [AuthenticationGuardService] + }, { path: 'help', component: HelpComponent }, { path: 'about', component: AboutComponent }, - { path: '**', component: ErrorsComponent } + { path: '**', component: ErrorsComponent }, + //{ path: 'auth-callback', component: AuthenticationCallbackComponent } ]; @NgModule({ imports: [ CardsRoutingModule, EntryRoutingModule, - RouterModule.forRoot(routes, { useHash: true }) + RouterModule.forRoot(routes, { useHash: true }), ], - exports: [RouterModule] + exports: [RouterModule], + providers: [PiaService, PiaResolve] }) + export class AppRoutingModule { } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7f1b0347c..3637a6d5e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,8 +2,8 @@ import { Component, Renderer2, Pipe, PipeTransform } from '@angular/core'; import { Http } from '@angular/http'; import { DomSanitizer } from '@angular/platform-browser'; -import { TranslateService } from '@ngx-translate/core'; import { KnowledgeBaseService } from 'app/entry/knowledge-base/knowledge-base.service'; +import { LanguagesService } from 'app/services/languages.service'; @Pipe({ name: 'safeHtml' }) export class SafeHtmlPipe implements PipeTransform { @@ -24,13 +24,14 @@ export class Nl2brPipe implements PipeTransform { @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + styleUrls: ['./app.component.scss'], + providers: [] }) export class AppComponent { constructor(private _renderer: Renderer2, private _http: Http, private _knowledgeBaseService: KnowledgeBaseService, - private _translateService: TranslateService) { + private _languagesService: LanguagesService) { this._knowledgeBaseService.loadData(this._http); const increaseContrast = localStorage.getItem('increaseContrast'); if (increaseContrast === 'true') { @@ -39,15 +40,9 @@ export class AppComponent { this._renderer.removeClass(document.body, 'pia-contrast'); } - // Translations system - this._translateService.addLangs(['en', 'cz', 'it', 'nl', 'fr', 'pl', 'de', 'es']); - this._translateService.setDefaultLang('fr'); - const language = localStorage.getItem('userLanguage'); - if (language && language.length > 0) { - this._translateService.use(language); - } else { - const browserLang = this._translateService.getBrowserLang(); - this._translateService.use(browserLang.match(/en|cs|it|nl|fr|pl|de|es/) ? browserLang : 'fr'); - } + // Languages initialization + this._languagesService.initLanguages(); + this._languagesService.getOrSetCurrentLanguage(); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0fbd1ab55..6a0412272 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -44,6 +44,7 @@ import { ModalsService } from 'app/modals/modals.service'; import { AttachmentsService } from 'app/entry/attachments/attachments.service'; import { KnowledgeBaseService } from 'app/entry/knowledge-base/knowledge-base.service'; import { PaginationService } from 'app/entry/entry-content/pagination.service'; +import { LanguagesService } from 'app/services/languages.service'; import { OverviewRisksComponent } from 'app/entry/entry-content/overview-risks/overview-risks.component'; import { ErrorsComponent } from 'app/errors/errors.component'; import { @@ -55,6 +56,13 @@ import { SummaryComponent } from 'app/summary/summary.component'; import { AboutComponent } from 'app/about/about.component'; import { AppRoutingModule } from 'app/app-routing.module'; import { CardsRoutingModule } from 'app/cards/cards-routing.module'; +import { AuthenticationService } from 'app/services/authentication.service'; +import { AuthenticationGuardService } from 'app/services/authentication-guard.service'; +import { AuthorizationGuardService } from 'app/services/authorization-guard.service'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { TokenInterceptor } from 'app/services/token.interceptor'; + +import { ApiModule } from '@api/api.module'; const providersList: any = [ AppDataService, @@ -65,7 +73,16 @@ const providersList: any = [ ActionPlanService, PaginationService, SidStatusService, - GlobalEvaluationService + LanguagesService, + GlobalEvaluationService, + AuthenticationService, + AuthenticationGuardService, + AuthorizationGuardService, + { + provide: HTTP_INTERCEPTORS, + useClass: TokenInterceptor, + multi: true + } ]; if (environment.rollbar_key.length > 0) { @@ -131,6 +148,7 @@ export function createTranslateLoader(http: HttpClient) { HttpClientModule, BrowserAnimationsModule, TagInputModule, + ApiModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, diff --git a/src/app/authentication/authentication.component.html b/src/app/authentication/authentication.component.html index 3638b50b6..665ff3ac8 100644 --- a/src/app/authentication/authentication.component.html +++ b/src/app/authentication/authentication.component.html @@ -1,112 +1,25 @@ - +
- -
+
- -
-
{{ 'authentication.start_app' | translate }}
-

- {{ 'authentication.start' | translate }} -
diff --git a/src/app/authentication/authentication.component.scss b/src/app/authentication/authentication.component.scss index cc7ae770a..5c3d2a565 100644 --- a/src/app/authentication/authentication.component.scss +++ b/src/app/authentication/authentication.component.scss @@ -1,6 +1,7 @@ @import '../../assets/stylesheets/variables'; .pia-authenticationBlock { + display: flex; padding-top: 110px; &-warning { color: white; @@ -62,7 +63,7 @@ width: 280px; box-shadow: 0 0 50px #aaa; } - &-register, &-enter { + &-enter { background-color: #fff; font-size: 1.4rem; padding: 10px; @@ -89,7 +90,6 @@ } } &-enter { - background-color: $pia-blue; margin-top: 10%; > div { color: #fff; @@ -97,8 +97,15 @@ } } &-login { - background-color: $pia-blue; padding-bottom: 10px; + .error { + color: $pia-red; + font-weight: 500; + font-size: 1.5rem; + width: 100%; + text-align: center; + } + fieldset { border: none; margin: 0; @@ -106,18 +113,18 @@ } legend { - margin: 0; + width: 100%; + text-align: center; font-size: 1.3rem; - color: #fff; + color: $pia-blue; padding-top: 12px; - padding-left: 15px; font-weight: 500; } label { padding-top: 10px; display: block; margin: auto; - color: #fff; + color: $pia-green; font-size: 1rem; } input { @@ -135,7 +142,7 @@ padding-top: 1px; } } - button[type="submit"] { + button { display: block; margin-top: 20px; margin-left: auto; @@ -155,7 +162,7 @@ padding-top: 8px; text-decoration: none; font-size: 0.9rem; - color: #fff; + color: $pia-green; cursor: pointer; } } diff --git a/src/app/authentication/authentication.component.ts b/src/app/authentication/authentication.component.ts index 4a62f4904..5f8f7b9af 100644 --- a/src/app/authentication/authentication.component.ts +++ b/src/app/authentication/authentication.component.ts @@ -1,52 +1,41 @@ -import { Component, OnInit, DoCheck, OnDestroy } from '@angular/core'; -import { Renderer2 } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { Router, CanActivate } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; +import { LanguagesService } from 'app/services/languages.service'; +import { AuthenticationService } from 'app/services/authentication.service'; +import { User } from 'app/authentication/user.model'; @Component({ selector: 'app-authentication', templateUrl: './authentication.component.html', styleUrls: ['./authentication.component.scss'] }) -export class AuthenticationComponent implements OnInit, DoCheck, OnDestroy { - selectedLanguage: string; +export class AuthenticationComponent implements OnInit { + public user = new User('', ''); + public error = false; - constructor(private _renderer: Renderer2, public _translateService: TranslateService) { - this._renderer.addClass(document.body, 'pia-authentication'); - } + constructor(public authService: AuthenticationService, public router: Router) {} - ngOnInit() { - this.getUserLanguage(); - } + onSubmit() { + this.authService.authenticate(this.user).then( + user => { + if (user === undefined) { + this.error = true; - ngDoCheck() { - this.getUserLanguage(); - } + return; + } - ngOnDestroy() { - this._renderer.removeClass(document.body, 'pia-authentication'); + this.router.navigate(['home']); + }, + () => { + this.error = true + } + ); } - /** - * Record the selected language. - * @param {string} selectedLanguage - The selected language. - * @memberof HeaderComponent - */ - updateCurrentLanguage(selectedLanguage: string) { - localStorage.setItem('userLanguage', selectedLanguage); - this._translateService.use(selectedLanguage); + ngOnInit() { + this.authService.logout(); + this.router.navigate(['']); } - /** - * Retrieve the selected language - * @memberof HeaderComponent - */ - getUserLanguage() { - const language = localStorage.getItem('userLanguage'); - if (language && language.length > 0) { - this.selectedLanguage = language; - } else { - const browserLang = this._translateService.getBrowserLang(); - this.selectedLanguage = browserLang.match(/en|cs|it|nl|fr|pl|de|es/) ? browserLang : 'fr'; - } - } } diff --git a/src/app/authentication/user.model.ts b/src/app/authentication/user.model.ts new file mode 100644 index 000000000..5c9b48919 --- /dev/null +++ b/src/app/authentication/user.model.ts @@ -0,0 +1,9 @@ + +export class User { + constructor( + public username: string, + public password: string + ) { + } +} + diff --git a/src/app/cards/card-item/card-item.component.html b/src/app/cards/card-item/card-item.component.html index 7dfaa74a8..4d4d53d8f 100644 --- a/src/app/cards/card-item/card-item.component.html +++ b/src/app/cards/card-item/card-item.component.html @@ -1,6 +1,6 @@ -
+
- {{ pia.getStatusName() | translate }} + {{ pia.getStatusLabel() | translate }}
-
{{ pia.getStatusName() | translate }}
+
{{ pia.getStatusLabel() | translate }}
diff --git a/src/app/cards/card-item/card-item.component.ts b/src/app/cards/card-item/card-item.component.ts index 457a8892b..d382b8411 100644 --- a/src/app/cards/card-item/card-item.component.ts +++ b/src/app/cards/card-item/card-item.component.ts @@ -7,6 +7,9 @@ import { Attachment } from 'app/entry/attachments/attachment.model'; import { ModalsService } from 'app/modals/modals.service'; import { PiaService } from 'app/entry/pia.service'; +import {PiaModel, AttachmentModel} from '@api/models'; +import {PiaApi, AttachmentApi} from '@api/services'; + @Component({ selector: 'app-card-item', templateUrl: './card-item.component.html', @@ -26,7 +29,9 @@ export class CardItemComponent implements OnInit { constructor(private router: Router, private _modalsService: ModalsService, - public _piaService: PiaService) { } + public _piaService: PiaService, + private piaApi: PiaApi, + private attachmentApi: AttachmentApi) { } ngOnInit() { this.piaForm = new FormGroup({ @@ -37,9 +42,7 @@ export class CardItemComponent implements OnInit { validator_name: new FormControl({ value: this.pia.validator_name, disabled: false }) }); - const attachmentModel = new Attachment(); - attachmentModel.pia_id = this.pia.id; - attachmentModel.findAll().then((entries: any) => { + this.attachmentApi.getAll(this.pia.id).subscribe((entries: AttachmentModel[]) => { this.attachments = entries; }); @@ -64,10 +67,10 @@ export class CardItemComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } if (userText !== '') { - const pia = new Pia(); - pia.get(this.piaForm.value.id).then(() => { - pia.name = this.piaForm.value.name; - pia.update(); + + this.piaApi.get(this.piaForm.value.id).subscribe((thePia: PiaModel) => { + thePia.name = this.piaForm.value.name; + this.piaApi.update(thePia).subscribe(); }); } } @@ -90,10 +93,9 @@ export class CardItemComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } if (userText !== '') { - const pia = new Pia(); - pia.get(this.piaForm.value.id).then(() => { - pia.author_name = this.piaForm.value.author_name; - pia.update(); + this.piaApi.get(this.piaForm.value.id).subscribe((thePia: PiaModel) => { + thePia.author_name = this.piaForm.value.author_name; + this.piaApi.update(thePia).subscribe(); }); } } @@ -116,10 +118,9 @@ export class CardItemComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } if (userText !== '') { - const pia = new Pia(); - pia.get(this.piaForm.value.id).then(() => { - pia.evaluator_name = this.piaForm.value.evaluator_name; - pia.update(); + this.piaApi.get(this.piaForm.value.id).subscribe((thePia: PiaModel) => { + thePia.evaluator_name = this.piaForm.value.evaluator_name; + this.piaApi.update(thePia).subscribe(); }); } } @@ -142,10 +143,9 @@ export class CardItemComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } if (userText !== '') { - const pia = new Pia(); - pia.get(this.piaForm.value.id).then(() => { - pia.validator_name = this.piaForm.value.validator_name; - pia.update(); + this.piaApi.get(this.piaForm.value.id).subscribe((thePia: PiaModel) => { + thePia.validator_name = this.piaForm.value.validator_name; + this.piaApi.update(thePia).subscribe(); }); } } diff --git a/src/app/cards/cards-routing.module.ts b/src/app/cards/cards-routing.module.ts index 1a38e6dc1..0b236bf62 100644 --- a/src/app/cards/cards-routing.module.ts +++ b/src/app/cards/cards-routing.module.ts @@ -1,10 +1,19 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { CardsComponent } from 'app/cards/cards.component'; +import { AuthenticationGuardService } from 'app/services/authentication-guard.service'; const routes: Routes = [ - { path: 'home', component: CardsComponent }, - { path: 'home/:view', component: CardsComponent }, + { + path: 'home', + component: CardsComponent, + canActivate: [AuthenticationGuardService] + }, + { + path: 'home/:view', + component: CardsComponent, + canActivate: [AuthenticationGuardService] + } ]; @NgModule({ diff --git a/src/app/cards/cards.component.html b/src/app/cards/cards.component.html index b8ce66447..c625570e2 100644 --- a/src/app/cards/cards.component.html +++ b/src/app/cards/cards.component.html @@ -187,7 +187,7 @@ - +
diff --git a/src/app/cards/cards.component.ts b/src/app/cards/cards.component.ts index b2c3f16e7..ee4dc2c9d 100644 --- a/src/app/cards/cards.component.ts +++ b/src/app/cards/cards.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, ElementRef, OnDestroy, Input} from '@angular/core'; +import { Component, OnInit, ElementRef, OnDestroy, Input } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; @@ -8,11 +8,14 @@ import { Pia } from '../entry/pia.model'; import { ModalsService } from 'app/modals/modals.service'; import { PiaService } from 'app/entry/pia.service'; +import { PiaModel } from '@api/models'; +import { PiaApi } from '@api/services'; + @Component({ selector: 'app-cards', templateUrl: './cards.component.html', styleUrls: ['./cards.component.scss'], - providers: [PiaService] + providers: [] }) export class CardsComponent implements OnInit, OnDestroy { @@ -27,21 +30,24 @@ export class CardsComponent implements OnInit, OnDestroy { paramsSubscribe: Subscription; constructor(private router: Router, - private el: ElementRef, - private route: ActivatedRoute, - public _modalsService: ModalsService, - public _piaService: PiaService) { } + private el: ElementRef, + private route: ActivatedRoute, + public _modalsService: ModalsService, + public _piaService: PiaService, + private piaApi: PiaApi + ) { } ngOnInit() { this.sortOrder = localStorage.getItem('sortOrder'); this.sortValue = localStorage.getItem('sortValue'); + if (!this.sortOrder || !this.sortValue) { this.sortOrder = 'up'; this.sortValue = 'updated_at'; localStorage.setItem('sortOrder', this.sortOrder); localStorage.setItem('sortValue', this.sortValue); } - this.refreshContent(); + //this.refreshContent(); this.piaForm = new FormGroup({ name: new FormControl(), author_name: new FormControl(), @@ -98,9 +104,10 @@ export class CardsComponent implements OnInit, OnDestroy { * @param {*} [event] - Any Event. * @memberof CardsComponent */ - importPia(event?: any) { + async importPia(event?: any) { if (event) { - this._piaService.import(event.target.files[0]); + await this._piaService.import(event.target.files[0]); + this.sortPia(); } else { this.el.nativeElement.querySelector('#import_file').click(); } @@ -112,13 +119,14 @@ export class CardsComponent implements OnInit, OnDestroy { * @memberof CardsComponent */ onSubmit() { - const pia = new Pia(); + const pia = new PiaModel(); pia.name = this.piaForm.value.name; pia.author_name = this.piaForm.value.author_name; pia.evaluator_name = this.piaForm.value.evaluator_name; pia.validator_name = this.piaForm.value.validator_name; - const p = pia.create(); - p.then((id) => this.router.navigate(['entry', id, 'section', 1, 'item', 1])); + const p = this.piaApi.create(pia).subscribe((newPia: PiaModel) => { + this.router.navigate(['entry', newPia.id, 'section', 1, 'item', 1]); + }); } /** @@ -161,14 +169,18 @@ export class CardsComponent implements OnInit, OnDestroy { * @memberof CardsComponent */ async refreshContent() { - const pia = new Pia(); - const data: any = await pia.getAll(); - this._piaService.pias = data; - this.sortOrder = localStorage.getItem('sortOrder'); - this.sortValue = localStorage.getItem('sortValue'); - setTimeout(() => { + + let thePias:PiaModel[] = await this.piaApi.getAll().toPromise(); + if(thePias.length == 0){ + return; + } + thePias.forEach((item)=>{this.piaApi.computeProgress(item).subscribe()}); + this._piaService.pias = thePias; + this.sortOrder = localStorage.getItem('sortOrder'); + this.sortValue = localStorage.getItem('sortValue'); this.sortPia(); - }, 200); + + } /** @@ -185,7 +197,7 @@ export class CardsComponent implements OnInit, OnDestroy { secondValue = new Date(b[this.sortValue]); } if (this.sortValue === 'name' || this.sortValue === 'author_name' || - this.sortValue === 'evaluator_name' || this.sortValue === 'validator_name') { + this.sortValue === 'evaluator_name' || this.sortValue === 'validator_name') { return firstValue.localeCompare(secondValue); } else { if (firstValue < secondValue) { diff --git a/src/app/cards/list-item/list-item.component.html b/src/app/cards/list-item/list-item.component.html index 6633aaa84..c94900911 100644 --- a/src/app/cards/list-item/list-item.component.html +++ b/src/app/cards/list-item/list-item.component.html @@ -11,7 +11,7 @@
- +
{{ pia.author_name }}
{{ pia.evaluator_name }}
{{ pia.validator_name }}
@@ -20,7 +20,7 @@ {{ 'pia.statuses.5' | translate }} - {{ pia.getStatusName() | translate }} + {{ pia.getStatusLabel() | translate }}
@@ -42,11 +42,11 @@ {{ 'homepage.lists.item.tools.duplicate' | translate }} - + {{ 'homepage.lists.item.tools.action_plan' | translate }} - + {{ 'homepage.lists.item.tools.pia' | translate }} diff --git a/src/app/cards/list-item/list-item.component.ts b/src/app/cards/list-item/list-item.component.ts index 803f81c12..cd963319e 100644 --- a/src/app/cards/list-item/list-item.component.ts +++ b/src/app/cards/list-item/list-item.component.ts @@ -5,6 +5,9 @@ import { Attachment } from 'app/entry/attachments/attachment.model'; import { ModalsService } from 'app/modals/modals.service'; import { PiaService } from 'app/entry/pia.service'; +import {PiaModel, AttachmentModel} from '@api/models'; +import {PiaApi, AttachmentApi} from '@api/services'; + @Component({ selector: `.app-list-item`, templateUrl: './list-item.component.html', @@ -17,14 +20,16 @@ export class ListItemComponent implements OnInit { constructor(private router: Router, private route: ActivatedRoute, public _piaService: PiaService, - private _modalsService: ModalsService) { } + private _modalsService: ModalsService, + private piaApi: PiaApi, + private attachmentApi: AttachmentApi, + ) { } ngOnInit() { - const attachmentModel = new Attachment(); - attachmentModel.pia_id = this.pia.id; - attachmentModel.findAll().then((entries: any) => { - this.attachments = entries; + this.attachmentApi.getAll(this.pia.id).subscribe((entries: AttachmentModel[]) => { + this.attachments = entries; }); + } /** @@ -36,7 +41,7 @@ export class ListItemComponent implements OnInit { onFocusOut(attribute: string, event: any) { const text = event.target.innerText; this.pia[attribute] = text; - this.pia.update(); + this.piaApi.update(this.pia).subscribe(); } /** diff --git a/src/app/entry/attachments/attachment.model.ts b/src/app/entry/attachments/attachment.model.ts index 078d355c3..3807af410 100644 --- a/src/app/entry/attachments/attachment.model.ts +++ b/src/app/entry/attachments/attachment.model.ts @@ -21,7 +21,7 @@ export class Attachment extends ApplicationDb { pia_signed: this.pia_signed, file: this.file, comment: this.comment, - created_at: this.created_at + //created_at: this.created_at }; await this.getObjectStore(); return new Promise((resolve, reject) => { @@ -70,7 +70,7 @@ export class Attachment extends ApplicationDb { } } fetch(this.getServerUrl() + '/' + entry.id, { - method: 'PATCH', + method: 'POST', body: formData }).then((response) => { return response.json(); diff --git a/src/app/entry/attachments/attachments.component.ts b/src/app/entry/attachments/attachments.component.ts index 0bd29864b..4fb35b926 100644 --- a/src/app/entry/attachments/attachments.component.ts +++ b/src/app/entry/attachments/attachments.component.ts @@ -2,10 +2,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { Component, OnInit, Input } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import { Pia } from '../pia.model'; -import { Attachment } from './attachment.model'; - import { AttachmentsService } from 'app/entry/attachments/attachments.service'; +import { PiaModel } from '@api/models'; @Component({ selector: 'app-attachments', @@ -14,7 +12,7 @@ import { AttachmentsService } from 'app/entry/attachments/attachments.service'; }) export class AttachmentsComponent implements OnInit { - @Input() pia: Pia; + @Input() pia: PiaModel; attachmentForm: FormGroup; dispplayAttachmentButton = false; @@ -35,7 +33,7 @@ export class AttachmentsComponent implements OnInit { * @memberof AttachmentsComponent */ addAttachment() { - if (this.pia.is_example === 1) { + if (this.pia.is_example) { return false; } else { this._attachmentsService.pia_signed = 0; diff --git a/src/app/entry/attachments/attachments.service.ts b/src/app/entry/attachments/attachments.service.ts index cb02b1aff..8826989e2 100644 --- a/src/app/entry/attachments/attachments.service.ts +++ b/src/app/entry/attachments/attachments.service.ts @@ -1,9 +1,12 @@ import { Injectable } from '@angular/core'; - -import { Attachment } from './attachment.model'; +//import * as b64ToBlob from 'b64toBlob'; import { ModalsService } from 'app/modals/modals.service'; +import { AttachmentModel } from '@api/models'; +import { AttachmentApi } from '@api/services'; + + @Injectable() export class AttachmentsService { @@ -13,7 +16,10 @@ export class AttachmentsService { pia: any; pia_signed = 0; - constructor(private _modalsService: ModalsService) { } + constructor( + private _modalsService: ModalsService, + private attachmentApi: AttachmentApi + ) { } /** * List all attachments. @@ -22,10 +28,8 @@ export class AttachmentsService { */ async listAttachments() { return new Promise((resolve, reject) => { - const attachment = new Attachment(); - attachment.pia_id = this.pia.id; - attachment.findAll().then((data: any[]) => { - this.attachments = data; + this.attachmentApi.getAll(this.pia.id).subscribe((entries: AttachmentModel[]) => { + this.attachments = entries; resolve(); }); }); @@ -39,11 +43,10 @@ export class AttachmentsService { async updateSignedAttachmentsList() { return new Promise((resolve, reject) => { this.signedAttachments = []; - const attachment = new Attachment(); - attachment.pia_id = this.pia.id; - attachment.findAll().then((data: any[]) => { + + this.attachmentApi.getAll(this.pia.id).subscribe((entries: AttachmentModel[]) => { // Store all signed attachments if they are not yet stored - data.forEach(a => { + entries.forEach(a => { if (a.pia_signed && a.pia_signed === 1) { this.signedAttachments.push(a); } @@ -52,9 +55,9 @@ export class AttachmentsService { if (this.signedAttachments && this.signedAttachments.length > 0) { this.signedAttachments.reverse(); // Reverse array (latest signed attachment at first) if (this.signedAttachments[0] && this.signedAttachments[0].file && this.signedAttachments[0].file.length > 0) { - // Store the latest signed attachment only if file isn't empty + // Store the latest signed attachment only if file isn't empty this.attachment_signed = this.signedAttachments[0]; - // Remove it from the signed attachments array so that we get the oldest + // Remove it from the signed attachments array so that we get the oldest this.signedAttachments.splice(0, 1); } } @@ -73,15 +76,16 @@ export class AttachmentsService { const reader = new FileReader(); reader.readAsDataURL(file); reader.onloadend = () => { - const attachment = new Attachment(); + const attachment = new AttachmentModel(); attachment.file = reader.result; attachment.name = attachment_file.name; attachment.mime_type = attachment_file.type; attachment.pia_id = this.pia.id; attachment.pia_signed = this.pia_signed; attachment.comment = ''; - attachment.create().then((id: number) => { - attachment.id = id; + + this.attachmentApi.create(attachment).subscribe((newAttachment: AttachmentModel) => { + attachment.fromJson(newAttachment); this.attachments.unshift(attachment); if (attachment.pia_signed === 1) { // Add the last previous signed attachment in the signed attachments array @@ -92,6 +96,7 @@ export class AttachmentsService { // To refresh signed attachments on validation page this.updateSignedAttachmentsList(); }); + } } @@ -101,19 +106,16 @@ export class AttachmentsService { * @memberof AttachmentsService */ downloadAttachment(id: number) { - const attachment = new Attachment(); - attachment.pia_id = this.pia.id; - attachment.find(id).then((entry: any) => { - fetch(entry.file).then(res => res.blob()).then(blob => { - const a = document.createElement('a'); - a.href = window.URL.createObjectURL(blob); - a.download = entry.name; - const event = new MouseEvent('click', { - view: window - }); - a.dispatchEvent(event); - a.click(); + this.attachmentApi.get(this.pia.id, id).subscribe((entry: any) => { + const blob = this.b64ToBlob(entry.file, entry.mime_type); + const a = document.createElement('a'); + const event = new MouseEvent('click', { + view: window }); + + a.href = URL.createObjectURL(blob); + a.download = entry.name; + a.dispatchEvent(event); }); } @@ -127,9 +129,7 @@ export class AttachmentsService { const attachmentId = parseInt(localStorage.getItem('attachment-id'), 10); // Remove from DB by erasing only the "file" field - const attachment = new Attachment(); - attachment.id = attachmentId; - attachment.remove(comment); + this.attachmentApi.deleteById(this.pia.id, attachmentId).subscribe(); // Deletes from the attachments array. const index = this.attachments.findIndex(p => p.id === attachmentId); @@ -148,4 +148,24 @@ export class AttachmentsService { } } + b64ToBlob(b64Data, contentType = '', sliceSize = 512) { + let byteCharacters = atob(b64Data); + let byteArrays = []; + + for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { + let slice = byteCharacters.slice(offset, offset + sliceSize); + + let byteNumbers = new Array(slice.length); + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + + let byteArray = new Uint8Array(byteNumbers); + + byteArrays.push(byteArray); + } + + return new Blob(byteArrays, {type: contentType}); + }; + } diff --git a/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.html b/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.html index fce26f692..f668df061 100644 --- a/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.html +++ b/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.html @@ -5,7 +5,7 @@

{{ data.short_title | translate }}

-
+
diff --git a/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.ts b/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.ts index 6148fb983..264bdbcda 100644 --- a/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.ts +++ b/src/app/entry/entry-content/action-plan/action-plan-implementation/action-plan-implementation.component.ts @@ -5,6 +5,9 @@ import { Evaluation } from 'app/entry/entry-content/evaluations/evaluation.model import { PiaService } from 'app/entry/pia.service'; +import { EvaluationModel } from '@api/models'; +import { EvaluationApi } from '@api/services'; + @Component({ selector: 'app-action-plan-implementation', templateUrl: './action-plan-implementation.component.html', @@ -13,14 +16,14 @@ import { PiaService } from 'app/entry/pia.service'; export class ActionPlanImplementationComponent implements OnInit { @Input() data: any; - evaluation: Evaluation; + evaluation: EvaluationModel; actionPlanForm: FormGroup; displayEditButton = false; @ViewChild('estimatedEvaluationDate') private estimatedEvaluationDate: ElementRef; @ViewChild('personInCharge') private personInCharge: ElementRef; - constructor(private _piaService: PiaService) { } + constructor(private _piaService: PiaService, private evaluationApi: EvaluationApi) { } ngOnInit() { this.actionPlanForm = new FormGroup({ @@ -66,7 +69,9 @@ export class ActionPlanImplementationComponent implements OnInit { estimatedEvaluationDateFocusOut() { const userText = this.actionPlanForm.controls['estimatedEvaluationDate'].value; this.evaluation.estimated_implementation_date = new Date(userText); - this.evaluation.update().then(() => { + + this.evaluationApi.update(this.evaluation).subscribe((updatedEval: EvaluationModel) => { + this.evaluation.fromJson(updatedEval); if (userText && userText.length > 0) { // TODO Unable to FocusIn with Firefox // this.actionPlanForm.controls['estimatedEvaluationDate'].disable(); @@ -97,7 +102,8 @@ export class ActionPlanImplementationComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } this.evaluation.person_in_charge = userText; - this.evaluation.update().then(() => { + this.evaluationApi.update(this.evaluation).subscribe((updatedEval: EvaluationModel) => { + this.evaluation.fromJson(updatedEval); this.actionPlanForm.controls['personInCharge'].disable(); if (userText && userText.length > 0) { // TODO Unable to FocusIn with Firefox @@ -105,5 +111,4 @@ export class ActionPlanImplementationComponent implements OnInit { } }); } - } diff --git a/src/app/entry/entry-content/action-plan/action-plan.component.html b/src/app/entry/entry-content/action-plan/action-plan.component.html index 10e88ef47..6cbb844b4 100644 --- a/src/app/entry/entry-content/action-plan/action-plan.component.html +++ b/src/app/entry/entry-content/action-plan/action-plan.component.html @@ -67,7 +67,7 @@

{{ 'action_plan.measures' | translate }}

- {{ 'action_plan.no_action_plan' | translate }} +
{{ 'action_plan.no_action_plan' | translate }}
@@ -81,7 +81,7 @@

{{ 'action_plan.risks' | translate }}

- {{ 'action_plan.no_action_plan' | translate }} +
{{ 'action_plan.no_action_plan' | translate }}
diff --git a/src/app/entry/entry-content/action-plan/action-plan.component.scss b/src/app/entry/entry-content/action-plan/action-plan.component.scss index 81acd95b2..3c023405a 100644 --- a/src/app/entry/entry-content/action-plan/action-plan.component.scss +++ b/src/app/entry/entry-content/action-plan/action-plan.component.scss @@ -43,6 +43,11 @@ bottom: 0; right: 0; } + &.summaryMode { + &:after { + display: none; + } + } } &-measures-scroll { display: block; @@ -51,6 +56,10 @@ > div:last-child { margin-bottom: 14px; } + &.summaryMode { + height: initial; + overflow-y: initial; + } } .pia-actionPlanGraphBlock-item { display: flex; diff --git a/src/app/entry/entry-content/action-plan/action-plan.component.ts b/src/app/entry/entry-content/action-plan/action-plan.component.ts index 9962e6840..b87787a69 100644 --- a/src/app/entry/entry-content/action-plan/action-plan.component.ts +++ b/src/app/entry/entry-content/action-plan/action-plan.component.ts @@ -1,6 +1,5 @@ import { Component, ElementRef, OnInit, Input } from '@angular/core'; import { ActionPlanService } from './action-plan.service'; -import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-action-plan', @@ -12,12 +11,11 @@ export class ActionPlanComponent implements OnInit { @Input() pia: any; @Input() data: any; - constructor(public _actionPlanService: ActionPlanService, - private _translateService: TranslateService) { } + constructor(public _actionPlanService: ActionPlanService) { } ngOnInit() { this._actionPlanService.pia = this.pia; this._actionPlanService.data = this.data; - this._actionPlanService.listActionPlan(this._translateService); + this._actionPlanService.listActionPlan(); } } diff --git a/src/app/entry/entry-content/action-plan/action-plan.service.ts b/src/app/entry/entry-content/action-plan/action-plan.service.ts index 02f814679..97e836d73 100644 --- a/src/app/entry/entry-content/action-plan/action-plan.service.ts +++ b/src/app/entry/entry-content/action-plan/action-plan.service.ts @@ -2,6 +2,11 @@ import { Injectable } from '@angular/core'; import { Evaluation } from 'app/entry/entry-content/evaluations/evaluation.model'; import { Measure } from 'app/entry/entry-content/measures/measure.model'; import { TranslateService } from '@ngx-translate/core'; +import { LanguagesService } from 'app/services/languages.service' + +//new import +import { EvaluationModel, AnswerModel, MeasureModel } from '@api/models'; +import { EvaluationApi, AnswerApi, MeasureApi } from '@api/services'; @Injectable() export class ActionPlanService { @@ -20,89 +25,230 @@ export class ActionPlanService { risksActionPlan32Ready = false; risksActionPlan33Ready = false; risksActionPlan34Ready = false; + csvRows = []; + + constructor( + private _translateService: TranslateService, + private _languagesService: LanguagesService, + private evaluationApi: EvaluationApi, + private measureApi: MeasureApi) { } /** * Get action plan. - * @param {TranslateService} translateService - The translate service. * @memberof ActionPlanService */ - listActionPlan(translateService: TranslateService) { + listActionPlan() { this.results = []; this.measures = []; + this.principlesActionPlanReady = false; + this.measuresActionPlanReady = false; + this.risksActionPlan32Ready = false; + this.risksActionPlan33Ready = false; + this.risksActionPlan34Ready = false; const section = this.data.sections.filter((s) => { return s.id === 2; }); section[0].items.forEach((item) => { item.questions.forEach(q => { - const evaluation = new Evaluation(); + const reference_to = '2.' + item.id + '.' + q.id; - evaluation.getByReference(this.pia.id, reference_to).then(() => { + this.evaluationApi.getByRef(this.pia.id, reference_to).subscribe((evaluation: EvaluationModel) => { + if (evaluation.status > 0) { if (evaluation.action_plan_comment && evaluation.action_plan_comment.length > 0) { this.principlesActionPlanReady = true; } - this.results.push({ status: evaluation.status, short_title: q.short_title, - action_plan_comment: evaluation.action_plan_comment, evaluation: evaluation }); + this.results.push({ + status: evaluation.status, + short_title: q.short_title, + action_plan_comment: evaluation.action_plan_comment, + evaluation_comment: evaluation.evaluation_comment, + evaluation: evaluation + }); } else { - this.results.push({ status: null, short_title: q.short_title, - action_plan_comment: null, evaluation: null }); + this.results.push({ + status: null, + short_title: q.short_title, + action_plan_comment: null, + evaluation_comment: null, + evaluation: null + }); } }); }); }); - const measure = new Measure(); - measure.pia_id = this.pia.id; - measure.findAll().then((entries: any) => { + this.measureApi.getAll(this.pia.id).subscribe((entries: MeasureModel[]) => { entries.forEach(m => { - const evaluation2 = new Evaluation(); const reference_to = '3.1.' + m.id; this.measures[reference_to] = null; - evaluation2.getByReference(this.pia.id, reference_to).then(() => { + this.evaluationApi.getByRef(this.pia.id, reference_to).subscribe((evaluation2: EvaluationModel) => { if (evaluation2.status > 0) { if (evaluation2.action_plan_comment && evaluation2.action_plan_comment.length > 0) { this.measuresActionPlanReady = true; } - this.measures.push({ name: m.title, short_title: m.title, status: evaluation2.status, - action_plan_comment: evaluation2.action_plan_comment, evaluation: evaluation2 }); + this.measures.push({ + name: m.title, + short_title: m.title, + status: evaluation2.status, + action_plan_comment: evaluation2.action_plan_comment, + evaluation_comment: evaluation2.evaluation_comment, + evaluation: evaluation2 + }); } else { - this.measures.push({ name: m.title, short_title: null, status: null, action_plan_comment: null, evaluation: null }); + this.measures.push({ + name: m.title, + short_title: null, + status: null, + action_plan_comment: null, + evaluation_comment: null, + evaluation: null + }); } }); }); }); - const evaluation3 = new Evaluation(); - evaluation3.getByReference(this.pia.id, '3.2').then(() => { - if (evaluation3.status > 0) { + this.evaluationApi.getByRef(this.pia.id, '3.2').subscribe((evaluation3: EvaluationModel) => { + if (evaluation3 && evaluation3.status > 0) { if (evaluation3.action_plan_comment && evaluation3.action_plan_comment.length > 0) { this.risksActionPlan32Ready = true; } - this.risks['3.2'] = { status: evaluation3.status, short_title: translateService.instant('action_plan.risk1'), - action_plan_comment: evaluation3.action_plan_comment, evaluation: evaluation3 }; + this.risks['3.2'] = { + status: evaluation3.status, + short_title: this._translateService.instant('action_plan.risk1'), + action_plan_comment: evaluation3.action_plan_comment, + evaluation_comment: evaluation3.evaluation_comment, + evaluation: evaluation3 + }; } }); - const evaluation4 = new Evaluation(); - evaluation4.getByReference(this.pia.id, '3.3').then(() => { - if (evaluation4.status > 0) { + this.evaluationApi.getByRef(this.pia.id, '3.3').subscribe((evaluation4: EvaluationModel) => { + + if (evaluation4 && evaluation4.status > 0) { if (evaluation4.action_plan_comment && evaluation4.action_plan_comment.length > 0) { this.risksActionPlan33Ready = true; } - this.risks['3.3'] = { status: evaluation4.status, short_title: translateService.instant('action_plan.risk2'), - action_plan_comment: evaluation4.action_plan_comment, evaluation: evaluation4 }; + this.risks['3.3'] = { + status: evaluation4.status, + short_title: this._translateService.instant('action_plan.risk2'), + action_plan_comment: evaluation4.action_plan_comment, + evaluation_comment: evaluation4.evaluation_comment, + evaluation: evaluation4 + }; } }); - const evaluation5 = new Evaluation(); - evaluation5.getByReference(this.pia.id, '3.4').then(() => { - if (evaluation5.status > 0) { + + this.evaluationApi.getByRef(this.pia.id, '3.4').subscribe((evaluation5: EvaluationModel) => { + if (evaluation5 && evaluation5.status > 0) { if (evaluation5.action_plan_comment && evaluation5.action_plan_comment.length > 0) { this.risksActionPlan34Ready = true; } - this.risks['3.4'] = { status: evaluation5.status, short_title: translateService.instant('action_plan.risk3'), - action_plan_comment: evaluation5.action_plan_comment, evaluation: evaluation5 }; + this.risks['3.4'] = { + status: evaluation5.status, + short_title: this._translateService.instant('action_plan.risk3'), + action_plan_comment: evaluation5.action_plan_comment, + evaluation_comment: evaluation5.evaluation_comment, + evaluation: evaluation5 + }; } }); } + + /** + * Generate CSV contents + * @memberof ActionPlanService + */ + getCsv() { + this.csvRows = []; + if (this.results && this.results.length > 0) { + this.csvRows.push({ title: this._translateService.instant('action_plan.principles') }); + this.results.forEach(data => { + if (data && data.action_plan_comment && data.action_plan_comment.length > 0) { + this.csvRows.push({ + blank: '', + short_title: data.short_title ? this._translateService.instant(data.short_title) : '', + action_plan_comment: this.filterText(data.action_plan_comment), + evaluation_comment: this.filterText(data.evaluation_comment), + evaluation_date: this.filterText(data.estimated_implementation_date), + evaluation_charge: this.filterText(data.person_in_charge) + }); + } + }); + } + + if (this.measures && this.measures.length > 0) { + this.csvRows.push({ title: this._translateService.instant('action_plan.measures') }); + this.measures.forEach(data => { + if (data && data.action_plan_comment && data.action_plan_comment.length > 0) { + this.csvRows.push({ + blank: '', + short_title: data.short_title ? this._translateService.instant(data.short_title) : '', + action_plan_comment: this.filterText(data.action_plan_comment), + evaluation_comment: this.filterText(data.evaluation_comment), + evaluation_date: this.filterText(data.estimated_implementation_date), + evaluation_charge: this.filterText(data.person_in_charge) + }); + } + }); + } + + if (this.risks['3.2']) { + this.csvRows.push({ title: this._translateService.instant('action_plan.risks') }); + this.csvRows.push({ + blank: '', + short_title: this._translateService.instant('action_plan.risk1'), + action_plan_comment: this.filterText(this.risks['3.2'].action_plan_comment), + evaluation_comment: this.filterText(this.risks['3.2'].evaluation_comment), + evaluation_date: this.filterText(this.risks['3.2'].estimated_implementation_date), + evaluation_charge: this.filterText(this.risks['3.2'].person_in_charge) + }); + } + + if (this.risks['3.3']) { + this.csvRows.push({ title: this._translateService.instant('action_plan.risk2') }); + this.csvRows.push({ + blank: '', + short_title: this._translateService.instant('action_plan.risk2'), + action_plan_comment: this.filterText(this.risks['3.3'].action_plan_comment), + evaluation_comment: this.filterText(this.risks['3.3'].evaluation_comment), + evaluation_date: this.filterText(this.risks['3.3'].estimated_implementation_date), + evaluation_charge: this.filterText(this.risks['3.3'].person_in_charge) + }); + } + + if (this.risks['3.4']) { + this.csvRows.push({ title: this._translateService.instant('action_plan.risk3') }); + this.csvRows.push({ + blank: '', + short_title: this._translateService.instant('action_plan.risk3'), + action_plan_comment: this.filterText(this.risks['3.4'].action_plan_comment), + evaluation_comment: this.filterText(this.risks['3.4'].evaluation_comment), + evaluation_date: this.filterText(this.risks['3.4'].estimated_implementation_date), + evaluation_charge: this.filterText(this.risks['3.4'].person_in_charge) + }); + } + } + + private filterText(data: string, isDate = false) { + if (data && data.length > 0) { + if (isDate) { + const date = Date.parse(data); + if (date !== NaN) { + const locale = this._languagesService.selectedLanguage; + const newDate = new Date(date); + data = new Intl.DateTimeFormat(locale).format(newDate); + } + } else { + const divElement = document.createElement('div'); + divElement.innerHTML = data; + data = divElement.innerText; + } + } else { + data = ''; + } + return data; + } } diff --git a/src/app/entry/entry-content/comments/comment-item/comment-item.component.scss b/src/app/entry/entry-content/comments/comment-item/comment-item.component.scss index f4d00e7a7..5c2c4e17e 100644 --- a/src/app/entry/entry-content/comments/comment-item/comment-item.component.scss +++ b/src/app/entry/entry-content/comments/comment-item/comment-item.component.scss @@ -2,7 +2,7 @@ .pia { &-commentsBlock { &-content { - margin-top: 10px; + margin: 10px 0; border: 1px solid $pia-gray; padding: 12px 8px; position: relative; diff --git a/src/app/entry/entry-content/comments/comments.component.html b/src/app/entry/entry-content/comments/comments.component.html index b3f0040e4..ee04e64eb 100644 --- a/src/app/entry/entry-content/comments/comments.component.html +++ b/src/app/entry/entry-content/comments/comments.component.html @@ -15,7 +15,7 @@
- +
diff --git a/src/app/entry/entry-content/comments/comments.component.ts b/src/app/entry/entry-content/comments/comments.component.ts index 0586658a7..b328aa8c9 100644 --- a/src/app/entry/entry-content/comments/comments.component.ts +++ b/src/app/entry/entry-content/comments/comments.component.ts @@ -1,7 +1,8 @@ import { Component, ElementRef, OnInit, Input } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import { Comment } from './comment.model'; +import { CommentModel } from '@api/models'; +import { CommentApi } from '@api/services'; import { MeasureService } from 'app/entry/entry-content/measures/measures.service'; import { ModalsService } from 'app/modals/modals.service'; @@ -26,8 +27,10 @@ export class CommentsComponent implements OnInit { displayCommentValidateBtn: boolean; constructor(private el: ElementRef, - private _measureService: MeasureService, - private _modalsService: ModalsService) { } + private _measureService: MeasureService, + private _modalsService: ModalsService, + private commentApi: CommentApi + ) { } ngOnInit() { if (this.answer.updated_at && this.answer.updated_at.toString() !== 'Invalid Date') { @@ -36,15 +39,9 @@ export class CommentsComponent implements OnInit { this.questionDate = this.answer.created_at; } this.comments = []; - const commentsModel = new Comment(); - commentsModel.pia_id = this.pia.id; - if (this.measure) { - commentsModel.reference_to = this.measure.id; - } else { - commentsModel.reference_to = this.question.id; - } + const ref_to = this.measure ? this.measure.id : this.question.id; - commentsModel.findAllByReference().then((entries) => { + this.commentApi.getAllByRef(this.pia.id, ref_to).subscribe((entries: CommentModel[]) => { this.comments = entries; this.comments.reverse(); }); @@ -102,7 +99,7 @@ export class CommentsComponent implements OnInit { } else { // Creates the new comment and pushes it as the first comment in list. // Updates accordeon and counter + removes the written comment. - const commentRecord = new Comment(); + const commentRecord = new CommentModel(); commentRecord.for_measure = false; commentRecord.description = this.commentsForm.value.description; commentRecord.pia_id = this.pia.id; @@ -112,8 +109,8 @@ export class CommentsComponent implements OnInit { } else { commentRecord.reference_to = this.question.id; } - commentRecord.create().then((id: number) => { - commentRecord.id = id; + this.commentApi.create(commentRecord).subscribe((newComment: CommentModel) => { + commentRecord.fromJson(newComment); this.comments.unshift(commentRecord); this.commentsForm.controls['description'].setValue(''); this.getCommentsAccordeonStatus(); diff --git a/src/app/entry/entry-content/dpo-people-opinions/dpo-people-opinions.component.ts b/src/app/entry/entry-content/dpo-people-opinions/dpo-people-opinions.component.ts index d4280ae95..6060638fc 100644 --- a/src/app/entry/entry-content/dpo-people-opinions/dpo-people-opinions.component.ts +++ b/src/app/entry/entry-content/dpo-people-opinions/dpo-people-opinions.component.ts @@ -3,14 +3,16 @@ import {FormControl, FormGroup} from '@angular/forms'; import { SidStatusService } from 'app/services/sid-status.service'; import {Pia} from 'app/entry/pia.model'; - import { PiaService } from 'app/entry/pia.service'; +//new import +import {PiaModel} from '@api/models'; + @Component({ selector: 'app-dpo-people-opinions', templateUrl: './dpo-people-opinions.component.html', styleUrls: ['./dpo-people-opinions.component.scss'], - providers: [PiaService] + providers: [] }) export class DPOPeopleOpinionsComponent implements OnInit { DPOForm: FormGroup; @@ -41,7 +43,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { peopleNames: new FormControl() }); - this._piaService.getPIA().then(() => { + // DPO if (this._piaService.pia.dpos_names && this._piaService.pia.dpos_names.length > 0) { this.DPOForm.controls['DPONames'].patchValue(this._piaService.pia.dpos_names); @@ -106,7 +108,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { if (peopleTextarea) { this.autoTextareaResize(null, peopleTextarea); } - }); + } /** @@ -114,7 +116,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ dpoNameFocusIn() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.DPOForm.controls['DPONames'].enable(); @@ -138,7 +140,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { this.DPOForm.controls['DPOStatus'].patchValue(null); this.DPOForm.controls['DPOOpinion'].patchValue(null); } - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); if (this.DPOForm.value.DPONames && this.DPOForm.value.DPONames.length > 0) { this.DPOForm.controls['DPONames'].disable(); @@ -151,7 +153,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ enableDpoStatusRadioButtons() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.DPOForm.controls['DPOStatus'].enable(); @@ -164,7 +166,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { */ dpoStatusFocusOut() { this._piaService.pia.dpo_status = parseInt(this.DPOForm.value.DPOStatus, 10); - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); }); } @@ -174,7 +176,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ dpoOpinionFocusIn() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.DPOForm.controls['DPOOpinion'].enable(); @@ -192,7 +194,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } this._piaService.pia.dpo_opinion = userText; - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); if (userText && userText.length > 0) { this.DPOForm.controls['DPOOpinion'].disable(); @@ -205,7 +207,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ enableConcernedPeopleSearchedOpinionRadioButtons() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.searchedOpinionsForm.controls['searchStatus'].enable(); @@ -227,7 +229,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { this.displayPeopleOpinions = false; this.displayPeopleSearchContent = true; } - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); }); } @@ -238,7 +240,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ peopleSearchContentFocusIn() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.searchedOpinionsForm.controls['searchContent'].enable(); @@ -256,7 +258,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } this._piaService.pia.concerned_people_searched_content = userText; - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); if (userText && userText.length > 0) { this.searchedOpinionsForm.controls['searchContent'].disable(); @@ -269,7 +271,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ concernedPeopleNameFocusIn() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.peopleForm.controls['peopleNames'].enable(); @@ -293,7 +295,8 @@ export class DPOPeopleOpinionsComponent implements OnInit { this.peopleForm.controls['peopleNames'].patchValue(null); this.peopleForm.controls['peopleStatus'].patchValue(null); } - this._piaService.pia.update().then(() => { + + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); if (this.peopleForm.value.peopleNames && this.peopleForm.value.peopleNames.length > 0) { this.peopleForm.controls['peopleNames'].disable(); @@ -306,7 +309,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ enableConcernedPeopleStatusRadioButtons() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.peopleForm.controls['peopleStatus'].enable(); @@ -320,7 +323,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { concernedPeopleStatusFocusOut() { if (this.peopleForm.value.peopleStatus && this.peopleForm.value.peopleStatus >= 0) { this._piaService.pia.concerned_people_status = parseInt(this.peopleForm.value.peopleStatus, 10); - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); }); } @@ -331,7 +334,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { * @memberof DPOPeopleOpinionsComponent */ concernedPeopleOpinionFocusIn() { - if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example === 1) { + if (this._piaService.pia.status >= 2 || this._piaService.pia.is_example) { return false; } else { this.peopleForm.controls['peopleOpinion'].enable(); @@ -349,7 +352,7 @@ export class DPOPeopleOpinionsComponent implements OnInit { userText = userText.replace(/^\s+/, '').replace(/\s+$/, ''); } this._piaService.pia.concerned_people_opinion = userText; - this._piaService.pia.update().then(() => { + this._piaService.saveCurrentPia().subscribe((updatedPia: PiaModel) => { this._sidStatusService.setSidStatus(this._piaService, { id: 4 }, { id: 3 }); if (userText && userText.length > 0) { this.peopleForm.controls['peopleOpinion'].disable(); diff --git a/src/app/entry/entry-content/entry-content.component.html b/src/app/entry/entry-content/entry-content.component.html index 836ccc856..a1ecef342 100644 --- a/src/app/entry/entry-content/entry-content.component.html +++ b/src/app/entry/entry-content/entry-content.component.html @@ -11,10 +11,10 @@ [ngClass]="_sidStatusService.specialIcon[section.id + '.' + item.id]" aria-hidden="true">
-
+
{{ 'pia.header.edition' | translate }}
-
+
{{ 'pia.header.evaluation' | translate }}
@@ -25,26 +25,29 @@

{{ section.short_help | translate }}

{{ item.title | translate }}

{{ item.short_help | translate }}

- +
- +
{{ 'pia.header.preview' | translate }}
-
+ +
{{ 'pia.sections.status.evaluation.title' | translate }}

-
+ + +
{{ 'pia.sections.status.validation.title' | translate }}

@@ -69,12 +72,16 @@

{{ item.short_help | translate }}

- +
+ - + +
+ +
@@ -97,9 +104,9 @@

{{ item.short_help | translate }}

{{ 'pia.footer.mandatory_fields' | translate }}

+ *ngIf="!_piaService.pia.is_example && _globalEvaluationService.status === 4">{{ 'pia.sections.status.evaluation.cancel_button' | translate }} + *ngIf="!_piaService.pia.is_example && _globalEvaluationService.status === 7 && !(section.id == 4 && item.id == 3)">{{ 'pia.sections.status.validation.cancel_button' | translate }}
- @@ -35,10 +34,12 @@

+
+
@@ -46,7 +47,7 @@

- +
@@ -91,7 +92,18 @@

- + +
+ +
+
+
+ +
+
+ +

+
@@ -102,7 +114,18 @@

- + +
+ +
+
+
+ +
+
+ +

+
@@ -113,7 +136,7 @@

- +
@@ -123,13 +146,32 @@ +
+
+

+ +
+ +
+
+
+ +
+
+

+ +
+ +
+
+