Compare commits

...

93 Commits

Author SHA1 Message Date
Tobias Åström
ca1248d36f Merge pull request #111 from qlik-oss/tsm/rename-pkg
chore: rename package
2023-11-16 08:45:00 +01:00
caele
927f1ef921 fix: build and verification 2023-11-16 08:42:44 +01:00
caele
a0623614fc chore: rename package 2023-11-16 08:37:35 +01:00
Tobias Åström
48f90f20b2 Merge pull request #106 from qlik-oss/dependabot/npm_and_yarn/tough-cookie-and-less--removed
chore(deps): bump tough-cookie and less
2023-11-16 08:36:50 +01:00
dependabot[bot]
b1ffb3a9d1 chore(deps): bump tough-cookie and less
Removes [tough-cookie](https://github.com/salesforce/tough-cookie). It's no longer used after updating ancestor dependency [less](https://github.com/less/less.js). These dependencies need to be updated together.


Removes `tough-cookie`

Updates `less` from 3.8.1 to 3.13.1
- [Release notes](https://github.com/less/less.js/releases)
- [Changelog](https://github.com/less/less.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/less/less.js/compare/v3.8.1...v3.13.1)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
- dependency-name: less
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-08 12:51:05 +00:00
Purwa Shrivastava
090a47cffd Merge pull request #96 from qlik-oss/atq/fixDeploy
fix: Environment Setup not needed for deploy
2022-11-14 14:03:28 +01:00
Purwa Shrivastava
43fa4cd380 fix: Environment Setup not needed for deploy 2022-11-11 14:48:15 +01:00
Purwa Shrivastava
233f2a5c7c Merge pull request #95 from qlik-oss/fix/QB-14863
fix: QB-14863-duplicateKeys
2022-11-11 14:28:12 +01:00
Purwa Shrivastava
ca6893296a fix: permission issue 2022-11-10 14:53:53 +01:00
Purwa Shrivastava
f13312c288 fix: yml file 2022-11-10 14:45:37 +01:00
Purwa Shrivastava
6fc73c4cc8 fix: update docker image 2022-11-10 14:34:46 +01:00
Purwa Shrivastava
9424e847c9 fix: use node v16 instead of v17 2022-11-10 14:09:53 +01:00
Purwa Shrivastava
b86a3ae9c6 fix: generated unique keys for data columns and removed blackduck scan 2022-11-10 10:53:19 +01:00
Jingjing Xie
ed50a8f3d3 Merge pull request #93 from qlik-oss/fix/QB-5202-option-in-QCS
fix: Export as XLS in QCS
2021-07-07 16:42:47 +02:00
Jingjing Xie
cca315efbb fix: check for desktop 2021-07-07 14:15:24 +02:00
Jingjing Xie
c888dd9fe4 fix: Export as XLS in QCS 2021-07-06 10:24:00 +02:00
Jingjing Xie
467948a46c Merge pull request #92 from qlik-oss/fcy/QB-5202
fix: enable Export as XLS option on qcs
2021-06-23 15:56:45 +02:00
Jingjing Xie
315191ccfa fix: comment 2021-06-23 12:29:23 +02:00
Jingjing Xie
80a9fdf2a9 fix: enable Export as XLS option on qcs 2021-06-23 10:26:36 +02:00
Tobias Åström
092b89d69f Merge pull request #91 from qlik-oss/tsm/qb-2727
fix: remove infinite loop
2021-01-14 16:51:40 +01:00
caele
f934fa929d chore: add comment 2021-01-14 10:23:22 +01:00
caele
acf6eb7b26 fix: remove infinite loop 2021-01-13 16:09:38 +01:00
Tobias Åström
d6ae86220e Merge pull request #90 from qlik-oss/tsm/increase-style-rows
Tsm/increase style rows
2021-01-05 09:29:18 +01:00
caele
3397bd419d fix: set style rows to 5000 2021-01-05 09:13:46 +01:00
caele
2dd11ad24c fix: set style rows to 5000 2021-01-05 09:10:39 +01:00
Tobias Åström
1099be87fa chore: add comment to explain export in QCS 2020-09-24 13:29:39 +02:00
Tobias Åström
13c5b9613d Merge pull request #89 from qlik-oss/tsm/QB-1634
fix: correctly check data export privileges
2020-06-09 08:54:31 +02:00
Tobias Åström
32890ba3db fix: correctly check data export privileges 2020-06-08 09:27:36 +02:00
Snigdha
8f7465dd8d Merge pull request #88 from qlik-oss/qb-1147-selection-refresh-pivot
pivot selection refresh fixed
2020-03-27 12:20:46 +05:30
Snigdha Snigdha
be710cb17b pivot selection refresh fixed 2020-03-20 18:29:32 +05:30
Avinash
2729321f40 Merge pull request #87 from qlik-oss/bugfix/QB-980
PL Pivot table Error message not showing
2020-02-26 18:06:04 +05:30
Avinash Shinde
2710d4629f fix: PL Pivot table Error message not showing 2020-02-25 13:17:19 +05:30
Purwa Shrivastava
8c093a4692 Merge pull request #86 from qlik-oss/QLIK-98564/sourceMaps
Removing source maps from production mode.
2020-02-14 11:03:05 +01:00
Avinash
ec822b843b Merge pull request #85 from qlik-oss/bugfix/QB-959
bugfix for  Column separator is misaligned
2020-02-14 11:51:42 +05:30
Purwa Shrivastava
5dc8bb49a4 Removing souce maps from production mode. 2020-02-12 08:06:07 +01:00
Avinash Shinde
0d98553a71 fix: removing .lock file 2020-02-11 16:32:58 +05:30
Avinash Shinde
6513a294b3 fix: Column separator is misaligned 2020-02-10 15:32:43 +05:30
Shiben Dutta
79e753c2b2 Merge pull request #84 from qlik-oss/QB262
fix: disable data export as per QMC
2020-02-04 08:26:39 +05:30
Shiben Dutta
468598540f fix: disable data export as per QMC 2020-01-16 17:22:50 +05:30
snigdhaprasad26
045d0db215 Merge pull request #83 from qlik-oss/QB-285-console-error
console error fixed
2020-01-10 13:43:25 +05:30
Snigdha Snigdha
73011d0388 fix: eslint error fixed and yarn.lock file removed 2020-01-09 16:24:15 +05:30
Snigdha Snigdha
41cf77e8d2 fix: console error fixed of qb-285 2020-01-09 15:49:47 +05:30
Snigdha Snigdha
1355381346 fix: console error fixed of qb-285 2020-01-09 15:18:05 +05:30
Snigdha Snigdha
730f35a83c fix: console error fixed of qb-285 2020-01-09 15:11:11 +05:30
Snigdha Snigdha
71bf25e8fb fix: console error fixed of qb-285 2020-01-09 15:08:57 +05:30
Snigdha Snigdha
9b4fe54239 fix: console error of qb-285 2020-01-09 14:56:56 +05:30
Snigdha Snigdha
e59c594215 console error fixed 2019-12-31 19:44:54 +05:30
Purwa Shrivastava
97a54e6f5a Merge pull request #82 from qlik-oss/qb-612/missingvaluesfix
Fix missing values resulting in data being rendered in wrong columns.
2019-12-10 14:40:45 +01:00
Purwa Shrivastava
eac9fd2a5f Fix missing values resulting in data being rendered in wrong columns. 2019-12-10 12:29:28 +01:00
Purwa Shrivastava
96f09f9323 Merge pull request #81 from qlik-oss/QB-377/errorMessage
React rendering Errors for few apps.
2019-12-09 08:42:31 +01:00
Purwa Shrivastava
2ab340f3f1 React rendering Errors for few apps. 2019-12-06 11:11:25 +01:00
Purwa Shrivastava
58d0f542eb Merge pull request #80 from qlik-oss/QB-377/errorMessage
Corrected the react error when componentDidUpdate is called. So the e…
2019-12-06 10:15:05 +01:00
Purwa Shrivastava
8ba826a0ea Corrected the react error when componentDidUpdate is called. So the error message is uniformly shown in both analysis and edit mode. 2019-12-06 10:12:07 +01:00
Purwa Shrivastava
d2446395e2 Merge pull request #77 from qlik-oss/QB-331/PivotPagination
Pivot Pagination Fix.
2019-12-05 13:41:29 +01:00
Purwa Shrivastava
f17a7b7714 Merge pull request #79 from qlik-oss/QB-493/wrongDataDisplay
Qb 493/wrong data display
2019-12-05 09:15:42 +01:00
Purwa Shrivastava
8aa86275f0 Changed the rendering of each data cell to check for the headers of each dimesnsion and measure where it shall be written. 2019-12-04 11:15:14 +01:00
Purwa Shrivastava
3ebc2b9e29 Adding Dim 2 to the rendering logic. 2019-11-29 16:17:49 +01:00
Purwa Shrivastava
ce81549011 Merge pull request #78 from qlik-oss/QB-498/disableConversion
Conversion led to different sort orders of data in different objects …
2019-11-28 13:40:45 +01:00
Purwa Shrivastava
e64af66dab Conversion led to different sort orders of data in different objects so disabling it. 2019-11-27 14:54:42 +01:00
Purwa Shrivastava
fd9e645fc1 Pivot Pagination Fix. 2019-11-21 10:59:28 +01:00
Philip Olsén
ca1b1a9b53 Merge pull request #75 from qlik-oss/pol/bd
Update black duck link
2019-09-20 15:33:56 +02:00
Philip Olsén
a65f843008 Update black duck link 2019-09-20 14:23:37 +02:00
Purwa Shrivastava
be9570e0aa Merge pull request #74 from qlik-oss/atq/about
Updated the About info for the smart pivot.
2019-07-12 12:40:29 +02:00
Purwa Shrivastava
41d3a7c9af Updated the About info for the smart pivot. 2019-07-11 11:01:42 +02:00
Albert Backenhof
1e0a7c1204 Merge pull request #72 from qlik-oss/DEB-211/ContextMenuExportExcel
Moved Excel export to context menu
2019-06-27 12:35:04 +02:00
Albert Backenhof
17b5df296c Moved Excel export to context menu
-Not showing context menu option for desktop
 users.

Issue: DEB-211
2019-06-26 13:41:07 +02:00
Albert Backenhof
64b778b1af Merge pull request #71 from qlik-oss/DEB-244/ExcelRow
Suppress missing values and use correct cube size
2019-06-17 13:37:05 +02:00
Albert Backenhof
be6718ccf4 Merge pull request #73 from qlik-oss/DEB-250/ImprovedSelection
Selecting with backendApi instead of Field
2019-06-17 09:30:27 +02:00
Albert Backenhof
28e6237a43 Selecting with backendApi instead of Field
-Makes it possible to select master fields with
 different names than the original field.

-Alternate state is no longer a problem when using
 this technique to select.

Issue: DEB-250
2019-06-14 08:30:57 +02:00
Albert Backenhof
4fab3b5933 Suppress missing values and use correct cube size
Issue: DEB-244
2019-05-29 15:17:17 +02:00
Albert Backenhof
79339a8963 Merge pull request #70 from qlik-oss/QLIK-95962/SpanWidth
Add option to fit cells to chart width
2019-05-29 12:19:59 +02:00
Albert Backenhof
a6faeeb656 Add option to fit cells to chart width
-To achieve pixel perfection the container of both
 header and data needs to calculate the size of the
 child tables' cells.

Issue: QLIK-95962
2019-05-28 07:44:40 +02:00
Albert Backenhof
c22b7f5c6b Merge pull request #61 from qlik-oss/DEB-232/DoubleMeasure
Made the table cell keys more unique
2019-05-27 14:37:07 +02:00
Albert Backenhof
80f97602e4 Merge pull request #69 from qlik-oss/DEB-242/ExportInContainer
Excel export inside container
2019-05-27 14:36:31 +02:00
Albert Backenhof
f745656b4c Excel export inside container
-No tid-element available when inside native container.
 Therefore, use root element to find table cells instead.

Issue: DEB-242
2019-05-27 14:33:43 +02:00
Albert Backenhof
f7e780b92e Merge pull request #68 from qlik-oss/excelType
Specifying excel type of excel download
2019-05-27 14:28:26 +02:00
Albert Backenhof
d7a76c7db9 Merge pull request #63 from qlik-oss/QLIK-95802/AltStateFix
Fixed alternate state support
2019-05-27 14:26:58 +02:00
Albert Backenhof
16c380e1c6 Fixed alternate state support
-Previously, the shown data always followed
 default state, and not the set state.
-Previously, the selections made in smart pivot
 sometimes used the previous alternate state.

Issue: QLIK-95802
2019-05-27 14:24:02 +02:00
Albert Backenhof
072a3b80c4 Merge pull request #62 from qlik-oss/DEB-233/Selections
Handle selection toggle properly
2019-05-27 14:12:43 +02:00
Albert Backenhof
5572b7ce67 Merge pull request #64 from qlik-oss/DEB-221/WidthSlider
Slider for cell width without presets
2019-05-27 14:11:01 +02:00
Albert Backenhof
71cf92c217 Slider for cell width without presets
-Previous slider only has three presets, this new
 solution sets the actual width.

Issue: DEB-221
2019-05-27 14:08:25 +02:00
Albert Backenhof
e26d5fded8 Specifying excel type of excel download 2019-05-20 10:12:22 +02:00
Albert Backenhof
20282b0b99 Merge pull request #65 from qlik-oss/QLIK-95907/QlikFormatting
Using qlik formatting measurements
2019-05-20 10:05:48 +02:00
Albert Backenhof
5f18321ccf Merge pull request #67 from qlik-oss/DEB-136/readme
Updated github readme
2019-05-20 09:07:54 +02:00
Albert Backenhof
d14f5951ac Using qlik formatting measurements
Issue: QLIK-95907
2019-05-20 08:02:15 +02:00
Albert Backenhof
5872ee7b58 Updated github readme
Issue: DEB-136
2019-05-20 07:02:45 +02:00
Albert Backenhof
1436463f59 Made the table cell keys more unique
-This makes it possible to use the same measurement
 multiple times within the same smart-pivot chart.

Issue: DEB-232
2019-05-13 13:12:32 +02:00
Albert Backenhof
729a31920d Handle selection toggle properly
-Previously, the cell selection would toggle
 the current selection. This meant, if a column
 is already selected when making a cell selection,
 the column selection would toggle off. With this
 fix the column selection stays on.

Issue: DEB-233
2019-05-13 09:43:41 +02:00
Purwa Shrivastava
6d305b21b2 Merge pull request #60 from qlik-oss/revert-59-DEB-217/CellFormatting
Revert "Fixed cell number formatting"
2019-05-10 12:48:04 +02:00
Purwa Shrivastava
0b210e0d35 Revert "Fixed cell number formatting" 2019-05-10 12:44:32 +02:00
Albert Backenhof
c8afb83130 Merge pull request #59 from qlik-oss/DEB-217/CellFormatting
Fixed cell number formatting
2019-05-10 10:19:47 +02:00
Albert Backenhof
49981f6ae3 Fixed cell number formatting
-Now will always use the built in Qlik value format.

Issue: DEB-217, DEB-218, DEB-219, DEB-220
2019-05-09 10:09:33 +02:00
Albert Backenhof
b3b17e8d0c Merge pull request #58 from qlik-oss/DEB-173/MasterObject
Fixed master object bug
2019-05-03 09:28:27 +02:00
31 changed files with 1052 additions and 1507 deletions

View File

@@ -3,7 +3,7 @@ version: 2
defaults: &defaults defaults: &defaults
working_directory: ~/qlik-smart-pivot working_directory: ~/qlik-smart-pivot
docker: docker:
- image: circleci/node:stretch - image: circleci/node:16.13.0
environment: environment:
GITHUB_ORG: "qlik-oss" GITHUB_ORG: "qlik-oss"
GITHUB_REPO: "PLSmartPivot" GITHUB_REPO: "PLSmartPivot"
@@ -12,20 +12,12 @@ defaults: &defaults
jobs: jobs:
test: test:
docker: docker:
- image: circleci/node:stretch-browsers - image: circleci/node:16.13.0-browsers
steps: steps:
- checkout - checkout
- run: - run:
name: Install dependencies name: Install dependencies
command: npm install command: npm install
- run:
name: BlackDuck scan
command: curl -s https://blackducksoftware.github.io/hub-detect/hub-detect.sh | bash -s -- \
--blackduck.url="https://qliktech.blackducksoftware.com" \
--blackduck.trust.cert=true \
--blackduck.username="svc-blackduck" \
--blackduck.password=${svc_blackduck} \
--detect.project.name="viz-bundle-qlik-smart-pivot"
bump-version: bump-version:
<<: *defaults <<: *defaults
@@ -43,6 +35,10 @@ jobs:
<<: *defaults <<: *defaults
steps: steps:
- checkout - checkout
- run:
name: Setup environment
command: |
sudo chmod +x ./.circleci/upgrade-node.sh
- attach_workspace: - attach_workspace:
at: ~/qlik-smart-pivot at: ~/qlik-smart-pivot
- run: - run:

17
.circleci/upgrade-node.sh Normal file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -eo pipefail
NVM_DIR="/opt/circleci/.nvm"
NODE_VERSION="v16"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
echo "Installing Node $NODE_VERSION"
nvm install $NODE_VERSION
nvm alias default $NODE_VERSION
# Each step uses the same `$BASH_ENV`, so need to modify it
echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
echo "[ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"" >> $BASH_ENV

View File

@@ -3,15 +3,15 @@ module.exports = {
ecmaVersion: 6, ecmaVersion: 6,
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
modules: true modules: true,
}, },
sourceType: "module" sourceType: "module",
}, },
parser: "babel-eslint", parser: "babel-eslint",
env: { env: {
browser: true, browser: true,
es6: true, es6: true,
node: true node: true,
}, },
globals: { globals: {
angular: false, angular: false,
@@ -21,49 +21,63 @@ module.exports = {
document: false, document: false,
expect: false, expect: false,
it: false, it: false,
require: false require: false,
}, },
rules: { rules: {
"indent": ["error", 2, { "SwitchCase": 1 }], indent: ["warn", 2, { SwitchCase: 1 }],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
"object-curly-spacing": ["error", "always"], "object-curly-spacing": ["error", "always"],
"max-lines": ["warn", 300], "max-lines": ["warn", 300],
"max-len": ["warn", { "code": 120, "ignoreComments": true, "ignoreTrailingComments": false }], "max-len": [
"warn",
{ code: 120, ignoreComments: true, ignoreTrailingComments: false },
],
"no-console": ["warn"], "no-console": ["warn"],
"no-mixed-operators": ["warn", { "no-mixed-operators": [
"groups": [ "warn",
["==", "!=", "===", "!==", ">", ">=", "<", "<="], {
["&&", "||"], groups: [
["in", "instanceof"] ["==", "!=", "===", "!==", ">", ">=", "<", "<="],
], ["&&", "||"],
"allowSamePrecedence": true ["in", "instanceof"],
}], ],
allowSamePrecedence: true,
},
],
"comma-dangle": ["off"],
"space-before-function-paren": ["off"],
"no-multi-spaces": ["error"], "no-multi-spaces": ["error"],
"no-cond-assign": ["warn"], "no-cond-assign": ["warn"],
"no-fallthrough": ["warn"], "no-fallthrough": ["warn"],
"no-undef": ["error"], "no-undef": ["error"],
"no-unused-vars": ["error"], "no-unused-vars": ["error"],
"no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], "no-use-before-define": [
"error",
{ functions: false, classes: false, variables: false },
],
"no-useless-escape": ["warn"], "no-useless-escape": ["warn"],
"no-useless-return": ["warn"], "no-useless-return": ["warn"],
"no-underscore-dangle": ["warn", { "allow": ["_id"] }], "no-underscore-dangle": ["warn", { allow: ["_id"] }],
"no-redeclare": ["error"], "no-redeclare": ["error"],
"no-restricted-syntax": ["warn"], "no-restricted-syntax": ["warn"],
"operator-linebreak": ["warn", "before"], "operator-linebreak": ["warn", "before"],
"prefer-promise-reject-errors": ["warn"], "prefer-promise-reject-errors": ["warn"],
"padded-blocks": ["warn", { "blocks": "never", "switches": "never", "classes": "never" }], "padded-blocks": [
"semi": ["error", "always"], "warn",
{ blocks: "never", switches: "never", classes: "never" },
],
semi: ["error", "always"],
"valid-typeof": ["warn"], "valid-typeof": ["warn"],
"no-eval": ["error"], "no-eval": ["error"],
"no-implied-eval": ["error"], "no-implied-eval": ["error"],
"no-debugger": ["warn"], "no-debugger": ["warn"],
"no-unreachable": ["warn"], "no-unreachable": ["warn"],
"quotes": ["warn", "single", { "avoidEscape": true }], quotes: ["warn", "single", { avoidEscape: true }],
"sort-imports": ["off"], "sort-imports": ["off"],
"max-lines-per-function": ["off"], // marks the entire functions, a bit too noisy "max-lines-per-function": ["off"], // marks the entire functions, a bit too noisy
"complexity": ["warn"], complexity: ["warn"],
"camelcase": ["warn"], camelcase: ["warn"],
"max-statements": ["off"], // marks the entire functions, a bit too noisy "max-statements": ["off"], // marks the entire functions, a bit too noisy
"sort-vars": ["off"], // not much value for the work "sort-vars": ["off"], // not much value for the work
"init-declarations": ["off"], "init-declarations": ["off"],
"capitalized-comments": ["off"], "capitalized-comments": ["off"],
@@ -99,13 +113,13 @@ module.exports = {
"array-bracket-newline": ["warn"], "array-bracket-newline": ["warn"],
"array-element-newline": ["warn"], "array-element-newline": ["warn"],
"object-shorthand": ["warn"], "object-shorthand": ["warn"],
"eqeqeq": ["warn"], eqeqeq: ["warn"],
"no-empty-function": ["off"], "no-empty-function": ["off"],
"function-paren-newline": ["warn"], "function-paren-newline": ["warn"],
"no-invalid-this": ["warn"], "no-invalid-this": ["warn"],
"newline-per-chained-call": ["warn"], "newline-per-chained-call": ["warn"],
"no-unused-expressions": ["warn"], "no-unused-expressions": ["warn"],
"strict": ["warn"], strict: ["warn"],
"no-ternary": ["off"], "no-ternary": ["off"],
"multiline-ternary": ["off"], "multiline-ternary": ["off"],
"no-param-reassign": ["error"], "no-param-reassign": ["error"],
@@ -115,9 +129,9 @@ module.exports = {
"default-case": ["warn"], "default-case": ["warn"],
"no-alert": ["warn"], "no-alert": ["warn"],
"max-params": ["warn"], "max-params": ["warn"],
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }], "brace-style": ["warn", "1tbs", { allowSingleLine: true }],
"prefer-const": ["warn"], "prefer-const": ["warn"],
"class-methods-use-this":["warn"], "class-methods-use-this": ["warn"],
// plugin:react // plugin:react
"react/jsx-indent": ["warn", 2], "react/jsx-indent": ["warn", 2],
"react/jsx-indent-props": ["warn", 2], "react/jsx-indent-props": ["warn", 2],
@@ -131,10 +145,7 @@ module.exports = {
"react/jsx-max-depth": ["off"], // rule throws exception in single-dimension-measure "react/jsx-max-depth": ["off"], // rule throws exception in single-dimension-measure
"react/jsx-filename-extension": ["warn"], "react/jsx-filename-extension": ["warn"],
"react/prefer-stateless-function": ["warn"], "react/prefer-stateless-function": ["warn"],
"react/no-set-state": ["warn"] "react/no-set-state": ["warn"],
}, },
extends: [ extends: ["eslint:all", "plugin:react/all"],
"eslint:all", };
"plugin:react/all"
]
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"json.format.enable": false
}

View File

@@ -1,52 +1,22 @@
# P&L Smart Pivot, a Qlik Sense Extension for Financial reporting # P&L Smart Pivot, a Qlik Sense Extension for Financial reporting
[![CircleCI](https://circleci.com/gh/qlik-oss/PLSmartPivot.svg?style=svg)](https://circleci.com/gh/qlik-oss/PLSmartPivot) This extension is part of the extension bundles for Qlik Sense. The repository is maintained and moderated by Qlik RD.
This extension is useful to create reports where the look&feel is rellevantand and pivot a second dimension is needed. Based on P&L Smart. Feel free to fork and suggest pull requests for improvements and bug fixes. Changes will be moderated and reviewed before inclusion in future bundle versions. Please note that emphasis is on backward compatibility, i.e. breaking changes will most likely not be approved.
It's specifically focused on financial reports, trying to solve some common needs of this area:
- smart export to excel
- easy creation of reports
- custom corporate reporting (bold, italic, background color, letter size, headers,...)
- selections inside the reports
- custom external templates
- analytical reports
# Manual
You'll find a manual [Qlik Sense P&LSmart Pivot Extension Manual.pdf](resources/Qlik Sense P&LSmart Pivot Extension Manual.pdf) and one app example [P&LSmartPivot_demo.qvf](resources/P&LSmartPivot_demo.qvf).
# Installation
1. Download the extension zip, `qlik-smart-pivot_<version>.zip`, from the latest release(https://github.com/qlik-oss/PLSmartPivot/releases/latest)
2. Install the extension:
a. **Qlik Sense Desktop**: unzip to a directory under [My Documents]/Qlik/Sense/Extensions.
b. **Qlik Sense Server**: import the zip file in the QMC.
Usage documentation for the extension is available at https://help.qlik.com.
# Developing the extension # Developing the extension
If you want to do code changes to the extension follow these simple steps to get going. If you want to do code changes to the extension follow these simple steps to get going.
1. Get Qlik Sense Desktop 1. Get Qlik Sense Desktop
1. Create a new app and add the extension to a sheet. 1. Create a new app and add P&L pivot to a sheet.
2. Clone the repository 2. Clone the repository
3. Run `npm install` 3. Run `npm install`
4. Set the environment variable `BUILD_PATH` to your extensions directory. It will be something like `C:/Users/<user>/Documents/Qlik/Sense/Extensions/<extension_name>`. 4. Run `npm run build` - to build a dev-version to the /dist folder.
5. You now have two options. Either run the watch task or the build task. They are explained below. Both of them default to development mode but can be run in production by setting `NODE_ENV=production` before running the npm task. 5. Move the content of the /dist folder to the extension directory. Usually in `C:/Users/<user>/Documents/Qlik/Sense/Extensions/qlik-smart-pivot`.
a. **Watch**: `npm run watch`. This will start a watcher which will rebuild the extension and output all needed files to the `buildFolder` for each code change you make. See your changes directly in your Qlik Sense app.
b. **Build**: `npm run build`. If you want to build the extension package. The output zip-file can be found in the `buildFolder`.
# Original authors # Original authors
[github.com/iviasensio](https://github.com/iviasensio) [github.com/iviasensio](https://github.com/iviasensio)
# License # License
Released under the [MIT License](LICENSE).
Released under the [MIT License](LICENSE).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -9,48 +9,52 @@ var pkg = require('./package.json');
var DIST = './dist'; var DIST = './dist';
var VERSION = process.env.VERSION || 'local-dev'; var VERSION = process.env.VERSION || 'local-dev';
gulp.task('qext', function () { gulp.task("qext", function () {
var qext = { var qext = {
name: 'P&L pivot', name: "P&L pivot",
type: 'visualization', type: "visualization",
description: pkg.description + '\nVersion: ' + VERSION, description: pkg.description + "\nVersion: " + VERSION,
version: VERSION, version: VERSION,
icon: 'pivot-table', icon: "pivot-table",
preview: 'qlik-smart-pivot.png', preview: "qlik-smart-pivot.png",
keywords: 'qlik-sense, visualization', keywords: "qlik-sense, visualization",
author: pkg.author, author: pkg.author,
homepage: pkg.homepage, homepage: pkg.homepage,
license: pkg.license, license: pkg.license,
repository: pkg.repository, repository: pkg.repository,
dependencies: { dependencies: {
'qlik-sense': '>=5.5.x' "qlik-sense": ">=5.5.x",
} },
}; __next: true,
if (pkg.contributors) { };
qext.contributors = pkg.contributors; if (pkg.contributors) {
} qext.contributors = pkg.contributors;
var src = require('stream').Readable({ }
objectMode: true var src = require("stream").Readable({
}); objectMode: true,
src._read = function () { });
this.push(new gutil.File({ src._read = function () {
cwd: '', this.push(
base: '', new gutil.File({
path: pkg.name + '.qext', cwd: "",
contents: Buffer.from(JSON.stringify(qext, null, 4)) base: "",
})); path: "qlik-smart-pivot.qext",
this.push(null); contents: Buffer.from(JSON.stringify(qext, null, 4)),
}; })
return src.pipe(gulp.dest(DIST)); );
this.push(null);
};
return src.pipe(gulp.dest(DIST));
}); });
gulp.task('clean', function(){ gulp.task("clean", function () {
return del([DIST], { force: true }); return del([DIST], { force: true });
}); });
gulp.task('zip-build', function(){ gulp.task("zip-build", function () {
return gulp.src(DIST + '/**/*') return gulp
.pipe(zip(`${pkg.name}_${VERSION}.zip`)) .src(DIST + "/**/*")
.pipe(zip(`qlik-smart-pivot_${VERSION}.zip`))
.pipe(gulp.dest(DIST)); .pipe(gulp.dest(DIST));
}); });

510
package-lock.json generated
View File

@@ -1417,23 +1417,6 @@
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
"dev": true "dev": true
}, },
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
"dev": true,
"optional": true
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"dev": true,
"optional": true,
"requires": {
"safer-buffer": "~2.1.0"
}
},
"asn1.js": { "asn1.js": {
"version": "4.10.1", "version": "4.10.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
@@ -1462,7 +1445,7 @@
}, },
"util": { "util": {
"version": "0.10.3", "version": "0.10.3",
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -1471,12 +1454,6 @@
} }
} }
}, },
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"assign-symbols": { "assign-symbols": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
@@ -1524,13 +1501,6 @@
"async-done": "^1.2.2" "async-done": "^1.2.2"
} }
}, },
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true,
"optional": true
},
"atob": { "atob": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -1563,20 +1533,6 @@
} }
} }
}, },
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"dev": true,
"optional": true
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true,
"optional": true
},
"babel-code-frame": { "babel-code-frame": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -1596,7 +1552,7 @@
}, },
"chalk": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -1737,16 +1693,6 @@
"integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
"dev": true "dev": true
}, },
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"dev": true,
"optional": true,
"requires": {
"tweetnacl": "^0.14.3"
}
},
"beeper": { "beeper": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz",
@@ -2041,13 +1987,6 @@
"integrity": "sha512-D6J9jloPm2MPkg0PXcODLMQAJKkeixKO9xhqTUMvtd44MtTYMyyDXPQ2Lk9IgBq5FH0frwiPa/N/w8ncQf7kIQ==", "integrity": "sha512-D6J9jloPm2MPkg0PXcODLMQAJKkeixKO9xhqTUMvtd44MtTYMyyDXPQ2Lk9IgBq5FH0frwiPa/N/w8ncQf7kIQ==",
"dev": true "dev": true
}, },
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true,
"optional": true
},
"ccount": { "ccount": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz", "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz",
@@ -2325,15 +2264,6 @@
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"dev": true "dev": true
}, },
"combined-stream": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
},
"commander": { "commander": {
"version": "2.19.0", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
@@ -2394,6 +2324,15 @@
"safe-buffer": "~5.1.1" "safe-buffer": "~5.1.1"
} }
}, },
"copy-anything": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"dev": true,
"requires": {
"is-what": "^3.14.1"
}
},
"copy-concurrently": { "copy-concurrently": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -2585,13 +2524,13 @@
"dependencies": { "dependencies": {
"jsesc": { "jsesc": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true "dev": true
}, },
"regexpu-core": { "regexpu-core": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
"integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -2602,13 +2541,13 @@
}, },
"regjsgen": { "regjsgen": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
"integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
"dev": true "dev": true
}, },
"regjsparser": { "regjsparser": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
"integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -2653,16 +2592,6 @@
"es5-ext": "^0.10.9" "es5-ext": "^0.10.9"
} }
}, },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"dev": true,
"optional": true,
"requires": {
"assert-plus": "^1.0.0"
}
},
"date-now": { "date-now": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
@@ -2823,7 +2752,7 @@
}, },
"globby": { "globby": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "http://registry.npmjs.org/globby/-/globby-3.0.1.tgz", "resolved": "https://registry.npmjs.org/globby/-/globby-3.0.1.tgz",
"integrity": "sha1-IJSvhCHhkVIVDViT62QWsxLZoi8=", "integrity": "sha1-IJSvhCHhkVIVDViT62QWsxLZoi8=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -2837,18 +2766,12 @@
}, },
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
} }
} }
}, },
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"des.js": { "des.js": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
@@ -2913,7 +2836,7 @@
"dependencies": { "dependencies": {
"domelementtype": { "domelementtype": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
"dev": true "dev": true
} }
@@ -2976,7 +2899,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "1.1.14", "version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -3016,17 +2939,6 @@
"object.defaults": "^1.1.0" "object.defaults": "^1.1.0"
} }
}, },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"dev": true,
"optional": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.3.88", "version": "1.3.88",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz",
@@ -3605,12 +3517,6 @@
} }
} }
}, },
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
"fancy-log": { "fancy-log": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
@@ -3794,25 +3700,6 @@
"for-in": "^1.0.1" "for-in": "^1.0.1"
} }
}, },
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
"dev": true,
"optional": true
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"dev": true,
"optional": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"fragment-cache": { "fragment-cache": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -3880,7 +3767,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -3901,12 +3789,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3921,17 +3811,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -4048,7 +3941,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -4060,6 +3954,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -4074,6 +3969,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -4081,12 +3977,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -4105,6 +4003,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -4185,7 +4084,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -4197,6 +4097,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -4282,7 +4183,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -4318,6 +4220,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -4337,6 +4240,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -4380,12 +4284,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@@ -4430,16 +4336,6 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
"dev": true "dev": true
}, },
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"dev": true,
"optional": true,
"requires": {
"assert-plus": "^1.0.0"
}
},
"glob": { "glob": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
@@ -4699,7 +4595,7 @@
}, },
"chalk": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -4780,7 +4676,7 @@
}, },
"chalk": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -4799,7 +4695,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "1.0.34", "version": "1.0.34",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -4823,7 +4719,7 @@
}, },
"through2": { "through2": {
"version": "0.6.5", "version": "0.6.5",
"resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
"integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -4842,24 +4738,6 @@
"glogg": "^1.0.0" "glogg": "^1.0.0"
} }
}, },
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"dev": true,
"optional": true
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"optional": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
"has": { "has": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -5010,18 +4888,6 @@
} }
} }
}, },
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"optional": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"https-browserify": { "https-browserify": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
@@ -5073,7 +4939,7 @@
"image-size": { "image-size": {
"version": "0.5.5", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
@@ -5533,13 +5399,6 @@
"has-symbols": "^1.0.0" "has-symbols": "^1.0.0"
} }
}, },
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true,
"optional": true
},
"is-unc-path": { "is-unc-path": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
@@ -5561,6 +5420,12 @@
"integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
"dev": true "dev": true
}, },
"is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
"dev": true
},
"is-whitespace-character": { "is-whitespace-character": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz",
@@ -5597,13 +5462,6 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true "dev": true
}, },
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true,
"optional": true
},
"js-base64": { "js-base64": {
"version": "2.4.9", "version": "2.4.9",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz",
@@ -5631,12 +5489,6 @@
"esprima": "^4.0.0" "esprima": "^4.0.0"
} }
}, },
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true
},
"jsesc": { "jsesc": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -5649,13 +5501,6 @@
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true "dev": true
}, },
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true,
"optional": true
},
"json-schema-traverse": { "json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -5677,13 +5522,6 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true "dev": true
}, },
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true,
"optional": true
},
"json5": { "json5": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
@@ -5696,19 +5534,6 @@
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
"dev": true "dev": true
}, },
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"dev": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"jsx-ast-utils": { "jsx-ast-utils": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
@@ -5774,26 +5599,37 @@
} }
}, },
"less": { "less": {
"version": "3.8.1", "version": "3.13.1",
"resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz",
"integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==",
"dev": true, "dev": true,
"requires": { "requires": {
"clone": "^2.1.2", "copy-anything": "^2.0.1",
"errno": "^0.1.1", "errno": "^0.1.1",
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"image-size": "~0.5.0", "image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1", "mime": "^1.4.1",
"mkdirp": "^0.5.0", "native-request": "^1.0.5",
"promise": "^7.1.1", "source-map": "~0.6.0",
"request": "^2.83.0", "tslib": "^1.10.0"
"source-map": "~0.6.0"
}, },
"dependencies": { "dependencies": {
"mime": { "make-dir": {
"version": "1.6.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"optional": true,
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
}
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
@@ -5803,6 +5639,12 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true, "dev": true,
"optional": true "optional": true
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
} }
} }
}, },
@@ -5845,7 +5687,7 @@
}, },
"load-json-file": { "load-json-file": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -5858,7 +5700,7 @@
"dependencies": { "dependencies": {
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
}, },
@@ -6380,20 +6222,12 @@
"brorand": "^1.0.1" "brorand": "^1.0.1"
} }
}, },
"mime-db": { "mime": {
"version": "1.37.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true
},
"mime-types": {
"version": "2.1.21",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
"dev": true, "dev": true,
"requires": { "optional": true
"mime-db": "~1.37.0"
}
}, },
"mimic-fn": { "mimic-fn": {
"version": "1.2.0", "version": "1.2.0",
@@ -6559,6 +6393,13 @@
"to-regex": "^3.0.1" "to-regex": "^3.0.1"
} }
}, },
"native-request": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz",
"integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==",
"dev": true,
"optional": true
},
"natural-compare": { "natural-compare": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -6707,13 +6548,6 @@
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true "dev": true
}, },
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true,
"optional": true
},
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -7283,7 +7117,7 @@
}, },
"chalk": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -7481,16 +7315,6 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true "dev": true
}, },
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"dev": true,
"optional": true,
"requires": {
"asap": "~2.0.3"
}
},
"promise-inflight": { "promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -7518,13 +7342,6 @@
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true "dev": true
}, },
"psl": {
"version": "1.1.29",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
"dev": true,
"optional": true
},
"public-encrypt": { "public-encrypt": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@@ -7566,13 +7383,6 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true "dev": true
}, },
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true,
"optional": true
},
"querystring": { "querystring": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@@ -7736,7 +7546,7 @@
}, },
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
}, },
@@ -7805,7 +7615,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -7934,7 +7744,7 @@
"dependencies": { "dependencies": {
"jsesc": { "jsesc": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true "dev": true
} }
@@ -8052,35 +7862,6 @@
"remove-trailing-separator": "^1.1.0" "remove-trailing-separator": "^1.1.0"
} }
}, },
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"optional": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.0",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"require-directory": { "require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -8578,24 +8359,6 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true "dev": true
}, },
"sshpk": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
"integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
"dev": true,
"optional": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
}
},
"ssri": { "ssri": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz",
@@ -8744,7 +8507,7 @@
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -8981,7 +8744,7 @@
}, },
"table": { "table": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz",
"integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -9194,26 +8957,6 @@
"through2": "^2.0.3" "through2": "^2.0.3"
} }
}, },
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"optional": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true,
"optional": true
}
}
},
"trim": { "trim": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
@@ -9256,22 +8999,6 @@
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true "dev": true
}, },
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
"type-check": { "type-check": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -9653,13 +9380,6 @@
"object.getownpropertydescriptors": "^2.0.3" "object.getownpropertydescriptors": "^2.0.3"
} }
}, },
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true,
"optional": true
},
"v8flags": { "v8flags": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz",
@@ -9685,18 +9405,6 @@
"integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=",
"dev": true "dev": true
}, },
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"optional": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"vfile": { "vfile": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz",

View File

@@ -1,5 +1,5 @@
{ {
"name": "qlik-smart-pivot", "name": "@qlik/qlik-smart-pivot",
"version": "0.0.1", "version": "0.0.1",
"description": "Profit & Loss reporting with color and font customizations.", "description": "Profit & Loss reporting with color and font customizations.",
"homepage": "", "homepage": "",
@@ -31,7 +31,7 @@
"gulp": "4.0.0", "gulp": "4.0.0",
"gulp-util": "^3.0.7", "gulp-util": "^3.0.7",
"gulp-zip": "3.0.2", "gulp-zip": "3.0.2",
"less": "3.8.1", "less": "3.13.1",
"less-loader": "4.1.0", "less-loader": "4.1.0",
"lodash.merge": "4.6.1", "lodash.merge": "4.6.1",
"style-loader": "0.23.1", "style-loader": "0.23.1",

View File

@@ -6,15 +6,8 @@ set -o errexit
echo "Verifying built file count" echo "Verifying built file count"
while read line; do
if [[ $line =~ ^\"name\": ]]; then
name=${line#*: \"}
name=${name%\"*}
fi
done < package.json
expected_file_count=$(($(find dist -type f | wc -l)-1)) expected_file_count=$(($(find dist -type f | wc -l)-1))
zip_file_count=$(zipinfo dist/${name}_${VERSION}.zip | grep ^- | wc -l) zip_file_count=$(zipinfo dist/qlik-smart-pivot_${VERSION}.zip | grep ^- | wc -l)
if [ "${expected_file_count}" -ne "${zip_file_count}" ]; then if [ "${expected_file_count}" -ne "${zip_file_count}" ]; then
# File count is incorrect # File count is incorrect

View File

@@ -1,55 +1,64 @@
/* eslint-disable react/sort-prop-types */
/* eslint-disable space-before-function-paren */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ApplyPreMask } from '../masking';
import { addSeparators } from '../utilities';
import Tooltip from '../tooltip/index.jsx'; import Tooltip from '../tooltip/index.jsx';
class DataCell extends React.PureComponent { class DataCell extends React.PureComponent {
constructor (props) { constructor(props) {
super(props); super(props);
this.handleSelect = this.handleSelect.bind(this); this.handleSelect = this.handleSelect.bind(this);
} }
handleSelect () { handleSelect() {
const { data: { meta: { dimensionCount } }, general: { allowFilteringByClick }, measurement, qlik } = this.props; const {
data: {
meta: {
dimensionCount
}
},
general: {
allowFilteringByClick
},
measurement,
component
} = this.props;
const hasSecondDimension = dimensionCount > 1; const hasSecondDimension = dimensionCount > 1;
if (!allowFilteringByClick) { if (!allowFilteringByClick) {
return; return;
} }
// fixes the console error on selection made from data cells
qlik.backendApi.selectValues(0, [measurement.parents.dimension1.elementNumber], true); component.selectValues(0, [measurement.parents.dimension1.elementNumber], false);
if (hasSecondDimension) { if (hasSecondDimension) {
qlik.backendApi.selectValues(1, [measurement.parents.dimension2.elementNumber], true); component.selectValues(1, [measurement.parents.dimension2.elementNumber], false);
} }
} }
render () { render() {
const { const {
data, cellWidth,
general,
measurement, measurement,
styleBuilder, styleBuilder,
styling styling
} = this.props; } = this.props;
let textAlignment = styling.options.textAlignment || 'Right'; const textAlignment = styling.options.textAlignment || 'Right';
let cellStyle = { const cellStyle = {
fontFamily: styling.options.fontFamily, fontFamily: styling.options.fontFamily,
...styleBuilder.getStyle(), ...styleBuilder.getStyle(),
paddingLeft: '5px', paddingLeft: '5px',
textAlign: textAlignment textAlign: textAlignment,
minWidth: cellWidth,
maxWidth: cellWidth
}; };
const isEmptyCell = measurement.displayValue === ''; const isEmptyCell = measurement.displayValue === '';
const isColumnPercentageBased = (/%/).test(measurement.format);
let formattedMeasurementValue; let formattedMeasurementValue;
if (isEmptyCell) { if (isEmptyCell || styleBuilder.hasComments()) {
formattedMeasurementValue = ''; formattedMeasurementValue = '';
cellStyle.cursor = 'default'; cellStyle.cursor = 'default';
} else if (styleBuilder.hasComments()) {
formattedMeasurementValue = '.';
} else { } else {
formattedMeasurementValue = formatMeasurementValue(measurement, styling); formattedMeasurementValue = formatMeasurementValue(measurement, styling);
} }
@@ -57,10 +66,10 @@ class DataCell extends React.PureComponent {
const { conditionalColoring } = styling; const { conditionalColoring } = styling;
if (conditionalColoring.enabled) { if (conditionalColoring.enabled) {
const isValidConditionalColoringValue = !styleBuilder.hasComments() && !isNaN(measurement.value); const isValidConditionalColoringValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
const isSpecifiedRow = const isSpecifiedRow
conditionalColoring.rows.indexOf(measurement.parents.dimension1.header) !== -1; = conditionalColoring.rows.indexOf(measurement.parents.dimension1.header) !== -1;
const isSpecifiedMeasure = const isSpecifiedMeasure
conditionalColoring.measures.indexOf(measurement.parents.measurement.index) !== -1; = conditionalColoring.measures.indexOf(measurement.parents.measurement.index) !== -1;
const shouldHaveConditionalColoring = (conditionalColoring.colorAllRows || isSpecifiedRow) const shouldHaveConditionalColoring = (conditionalColoring.colorAllRows || isSpecifiedRow)
&& (conditionalColoring.colorAllMeasures || isSpecifiedMeasure); && (conditionalColoring.colorAllMeasures || isSpecifiedMeasure);
if (isValidConditionalColoringValue && shouldHaveConditionalColoring) { if (isValidConditionalColoringValue && shouldHaveConditionalColoring) {
@@ -70,16 +79,9 @@ class DataCell extends React.PureComponent {
} }
} }
let cellClass = 'grid-cells';
const hasTwoDimensions = data.headers.dimension2 && data.headers.dimension2.length > 0;
const shouldUseSmallCells = isColumnPercentageBased && data.headers.measurements.length > 1 && hasTwoDimensions;
if (shouldUseSmallCells) {
cellClass = 'grid-cells-small';
}
return ( return (
<td <td
className={`${cellClass}${general.cellSuffix}`} className="grid-cells"
onClick={isEmptyCell ? null : this.handleSelect} onClick={isEmptyCell ? null : this.handleSelect}
style={cellStyle} style={cellStyle}
> >
@@ -95,22 +97,24 @@ class DataCell extends React.PureComponent {
} }
DataCell.propTypes = { DataCell.propTypes = {
cellWidth: PropTypes.string.isRequired,
data: PropTypes.shape({ data: PropTypes.shape({
headers: PropTypes.shape({ headers: PropTypes.shape({
measurements: PropTypes.array.isRequired measurements: PropTypes.array.isRequired
}).isRequired,
meta: PropTypes.shape({
dimensionCount: PropTypes.number.isRequired
}).isRequired }).isRequired
}).isRequired, }).isRequired,
general: PropTypes.shape({ general: PropTypes.shape({}).isRequired,
cellSuffix: PropTypes.string.isRequired
}).isRequired,
measurement: PropTypes.shape({ measurement: PropTypes.shape({
format: PropTypes.string, format: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
value: PropTypes.any value: PropTypes.any
}).isRequired, }).isRequired,
qlik: PropTypes.shape({ component: PropTypes.shape({
backendApi: PropTypes.shape({ backendApi: PropTypes.shape({
selectValues: function (props, propName) { selectValues (props, propName) {
if (props.isSnapshot || typeof props[propName] === 'function') { if (props.isSnapshot || typeof props[propName] === 'function') {
return null; return null;
} }
@@ -128,55 +132,15 @@ DataCell.propTypes = {
export default DataCell; export default DataCell;
function formatMeasurementValue (measurement, styling) { function formatMeasurementValue(measurement, styling) {
const isColumnPercentageBased = (/%/).test(measurement.format); if (isNaN(measurement.value)) {
let formattedMeasurementValue = ''; return styling.symbolForNulls;
if (isColumnPercentageBased) {
if (isNaN(measurement.value)) {
formattedMeasurementValue = styling.symbolForNulls;
} else {
formattedMeasurementValue = ApplyPreMask('0,00%', measurement.value);
}
} else {
let magnitudeDivider;
switch (measurement.magnitude.toLowerCase()) {
case 'k':
magnitudeDivider = 1000;
break;
case 'm':
magnitudeDivider = 1000000;
break;
default:
magnitudeDivider = 1;
}
const formattingStringWithoutMagnitude = measurement.format.replace(/k|K|m|M/gi, '');
if (isNaN(measurement.value)) {
formattedMeasurementValue = styling.symbolForNulls;
} else {
let preFormatValue = measurement.value;
if (isColumnPercentageBased) {
preFormatValue *= 100;
}
switch (formattingStringWithoutMagnitude) {
case '#.##0':
formattedMeasurementValue = addSeparators((preFormatValue / magnitudeDivider), '.', ',', 0);
break;
case '#,##0':
formattedMeasurementValue = addSeparators((preFormatValue / magnitudeDivider), ',', '.', 0);
break;
default:
formattedMeasurementValue = ApplyPreMask(
formattingStringWithoutMagnitude,
(preFormatValue / magnitudeDivider)
);
break;
}
}
} }
return formattedMeasurementValue;
return measurement.displayValue;
} }
function getConditionalColor (measurement, conditionalColoring) { function getConditionalColor(measurement, conditionalColoring) {
if (measurement.value < conditionalColoring.threshold.poor) { if (measurement.value < conditionalColoring.threshold.poor) {
return conditionalColoring.colors.poor; return conditionalColoring.colors.poor;
} }

View File

@@ -5,101 +5,175 @@ import DataCell from './data-cell.jsx';
import RowHeader from './row-header.jsx'; import RowHeader from './row-header.jsx';
import { injectSeparators } from '../utilities'; import { injectSeparators } from '../utilities';
const DataTable = ({ data, general, qlik, renderData, styling }) => { // eslint-disable-next-line react/prefer-stateless-function
const { class DataTable extends React.PureComponent {
headers: { render () {
dimension1, const {
measurements cellWidth,
}, columnSeparatorWidth,
matrix component,
} = data; data,
general,
renderData,
styling
} = this.props;
return ( const {
<div className="row-wrapper"> headers: {
<table> dimension1,
<tbody> dimension2,
{dimension1.map((dimensionEntry, dimensionIndex) => { measurements
const rowHeaderText = dimensionEntry.displayValue || ''; },
if (rowHeaderText === '-') { matrix
return null; } = data;
}
const styleBuilder = new StyleBuilder(styling);
if (styling.hasCustomFileStyle) {
styleBuilder.parseCustomFileStyle(rowHeaderText);
} else {
styleBuilder.applyStandardAttributes(dimensionIndex);
styleBuilder.applyCustomStyle({
fontSize: `${14 + styling.options.fontSizeAdjustment}px`
});
}
const rowStyle = {
fontFamily: styling.options.fontFamily,
width: '230px',
...styleBuilder.getStyle()
};
return ( const separatorStyle = {
<tr key={dimensionEntry.displayValue}> minWidth: columnSeparatorWidth,
{!renderData ? maxWidth: columnSeparatorWidth
<RowHeader };
entry={dimensionEntry}
qlik={qlik} const renderMeasurementData = (dimIndex, atEvery) => {
rowStyle={rowStyle} if (dimension2.length <= 0) {
styleBuilder={styleBuilder} return injectSeparators(
styling={styling} matrix[dimIndex],
/> : null columnSeparatorWidth,
atEvery
);
}
const measurementDataRow = [];
let index = 0,
match;
dimension2.forEach(dim2 => {
measurements.forEach((measure, mesInd) => {
for (index = 0; index < matrix[dimIndex].length; index++) {
match = false;
if (
matrix[dimIndex][index].parents &&
dimension1[dimIndex].displayValue ===
matrix[dimIndex][index].parents.dimension1.header
) {
if (
dim2.displayValue ===
matrix[dimIndex][index].parents.dimension2.header
) {
if (
measure.name ===
matrix[dimIndex][index].parents.measurement.header
) {
measurementDataRow.push(matrix[dimIndex][index]);
match = true;
break;
} }
{renderData && injectSeparators( }
matrix[dimensionIndex], }
styling.useSeparatorColumns, }
{ atEvery: measurements.length } if (!match) {
).map((measurementData, index) => { measurementDataRow.push({
if (measurementData.isSeparator) { displayValue: "",
const separatorStyle = { parents: {
color: 'white', dimension1: {
fontFamily: styling.options.fontFamily, elementNumber: dimension1[dimIndex].elementNumber,
fontSize: `${12 + styling.options.fontSizeAdjustment}px` header: dimension1[dimIndex].displayValue
}; },
dimension2: {
elementNumber: dim2.elementNumber,
header: dim2.displayValue
},
measurement: {
header: measure.name,
index: mesInd
}
}
});
}
});
});
return injectSeparators(
measurementDataRow,
columnSeparatorWidth,
atEvery
);
};
return (
<td
className="empty"
key={`${dimensionEntry.displayValue}-${index}-separator`}
style={separatorStyle}
>
*
</td>
);
}
const { dimension1: dimension1Info, dimension2, measurement } = measurementData.parents; return (
const id = `${dimension1Info.elementNumber}-${dimension2 && dimension2.elementNumber}-${measurement.header}`; <div className="row-wrapper">
return ( <table>
<DataCell <tbody>
data={data} {dimension1.map((dimensionEntry, dimensionIndex) => {
general={general} const rowHeaderText = dimensionEntry.displayValue || '';
key={`${dimensionEntry.displayValue}-${id}`} if (rowHeaderText === '-') {
measurement={measurementData} return null;
qlik={qlik} }
const styleBuilder = new StyleBuilder(styling);
if (styling.hasCustomFileStyle) {
styleBuilder.parseCustomFileStyle(rowHeaderText);
} else {
styleBuilder.applyStandardAttributes(dimensionIndex);
styleBuilder.applyCustomStyle({
fontSize: `${14 + styling.options.fontSizeAdjustment}px`
});
}
const rowStyle = {
fontFamily: styling.options.fontFamily,
width: '230px',
...styleBuilder.getStyle()
};
return (
<tr key={`${dimensionEntry.displayValue}-${dimensionIndex}-separator`}>
{!renderData ?
<RowHeader
component={component}
entry={dimensionEntry}
rowStyle={rowStyle}
styleBuilder={styleBuilder} styleBuilder={styleBuilder}
styling={styling} styling={styling}
/> /> : null
); }
})} {renderData && renderMeasurementData(dimensionIndex, { atEvery: measurements.length }).map((measurementData, index) => {
</tr> if (measurementData.isSeparator) {
); return (
})} <td
</tbody> className="empty"
</table> key={`${dimensionEntry.displayValue}-${index}-separator`}
</div> style={separatorStyle}
); />
}; );
}
// eslint-disable-next-line no-shadow
return (
<DataCell
cellWidth={cellWidth}
component={component}
data={data}
general={general}
key={`${Math.random()}-${new Date().getTime()}`}
measurement={measurementData}
styleBuilder={styleBuilder}
styling={styling}
/>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
DataTable.defaultProps = { DataTable.defaultProps = {
renderData: true renderData: true
}; };
DataTable.propTypes = { DataTable.propTypes = {
cellWidth: PropTypes.string.isRequired,
columnSeparatorWidth: PropTypes.string.isRequired,
component: PropTypes.shape({}).isRequired,
data: PropTypes.shape({ data: PropTypes.shape({
headers: PropTypes.shape({ headers: PropTypes.shape({
dimension1: PropTypes.array.isRequired dimension1: PropTypes.array.isRequired
@@ -107,7 +181,6 @@ DataTable.propTypes = {
matrix: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired matrix: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired
}).isRequired, }).isRequired,
general: PropTypes.shape({}).isRequired, general: PropTypes.shape({}).isRequired,
qlik: PropTypes.shape({}).isRequired,
renderData: PropTypes.bool, renderData: PropTypes.bool,
styling: PropTypes.shape({ styling: PropTypes.shape({
hasCustomFileStyle: PropTypes.bool.isRequired hasCustomFileStyle: PropTypes.bool.isRequired

View File

@@ -10,14 +10,15 @@ class RowHeader extends React.PureComponent {
this.handleSelect = this.handleSelect.bind(this); this.handleSelect = this.handleSelect.bind(this);
} }
// fixes the console error on row selected values
handleSelect () { handleSelect () {
const { entry, qlik } = this.props; const { component, entry } = this.props;
qlik.backendApi.selectValues(0, [entry.elementNumber], true); component.selectValues(0, [entry.elementNumber], false);
} }
render () { render () {
const { entry, rowStyle, styleBuilder, styling, qlik } = this.props; const { entry, rowStyle, styleBuilder, styling, component } = this.props;
const inEditState = qlik.inEditState(); const inEditState = component.inEditState();
return ( return (
<td <td
@@ -42,12 +43,9 @@ class RowHeader extends React.PureComponent {
} }
RowHeader.propTypes = { RowHeader.propTypes = {
entry: PropTypes.shape({ component: PropTypes.shape({
displayValue: PropTypes.string.isRequired
}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({ backendApi: PropTypes.shape({
selectValues: function (props, propName) { selectValues (props, propName) {
if (props.isSnapshot || typeof props[propName] === 'function') { if (props.isSnapshot || typeof props[propName] === 'function') {
return null; return null;
} }
@@ -55,6 +53,10 @@ RowHeader.propTypes = {
} }
}).isRequired }).isRequired
}).isRequired, }).isRequired,
entry: PropTypes.shape({
displayValue: PropTypes.string.isRequired,
elementNumber: PropTypes.number.isRequired
}).isRequired,
rowStyle: PropTypes.shape({}).isRequired, rowStyle: PropTypes.shape({}).isRequired,
styleBuilder: PropTypes.shape({}).isRequired, styleBuilder: PropTypes.shape({}).isRequired,
styling: PropTypes.shape({}).isRequired styling: PropTypes.shape({}).isRequired

View File

@@ -6,39 +6,66 @@ function createCube (definition, app) {
}); });
} }
async function buildDataCube (originCubeDefinition, hasTwoDimensions, app) { async function buildDataCube (originCubeDefinition, originCube, app, requestPage) {
const cubeDefinition = { const cubeDefinition = {
...originCubeDefinition, ...originCubeDefinition,
qInitialDataFetch: [ qInitialDataFetch: [
{ {
// eslint-disable-next-line no-undefined
qTop: requestPage === undefined ? 0 : requestPage[0].qTop,
qLeft: 0,
qHeight: 1000, qHeight: 1000,
qWidth: 10 qWidth: originCube.qSize.qcx
} }
], ],
qDimensions: [originCubeDefinition.qDimensions[0]], qDimensions: [originCubeDefinition.qDimensions[0]],
qMeasures: originCubeDefinition.qMeasures qMeasures: originCubeDefinition.qMeasures
}; };
if (hasTwoDimensions) { if (originCube.qDimensionInfo.length === 2) {
cubeDefinition.qDimensions.push(originCubeDefinition.qDimensions[1]); cubeDefinition.qDimensions.push(originCubeDefinition.qDimensions[1]);
} }
const cube = await createCube(cubeDefinition, app); const cube = await createCube(cubeDefinition, app);
return cube.qHyperCube.qDataPages[0].qMatrix; const cubeMatrix = cube.qHyperCube.qDataPages[0].qMatrix;
app.destroySessionObject(cube.qInfo.qId);
return cubeMatrix;
} }
export async function initializeDataCube (component, layout) { export async function initializeDataCube (component, layout) {
if (component.backendApi.isSnapshot) { if (component.backendApi.isSnapshot) {
return layout.snapshotData.dataCube; return layout.snapshotData.dataCube;
} }
const app = qlik.currApp(component); const app = qlik.currApp(component);
const properties = (await component.backendApi.getProperties()); const properties = (await component.backendApi.getProperties());
const rowCount = component.backendApi.getRowCount();
const cellCount = rowCount * layout.qHyperCube.qSize.qcx;
const maxLoops = layout.maxloops;
// If this is a master object, fetch the hyperCubeDef of the original object // If this is a master object, fetch the hyperCubeDef of the original object
const hyperCubeDef = properties.qExtendsId let hyperCubeDef = properties.qExtendsId
? (await app.getObjectProperties(properties.qExtendsId)).properties.qHyperCubeDef ? (await app.getObjectProperties(properties.qExtendsId)).properties.qHyperCubeDef
: properties.qHyperCubeDef; : properties.qHyperCubeDef;
hyperCubeDef = JSON.parse(JSON.stringify(hyperCubeDef));
return buildDataCube(hyperCubeDef, layout.qHyperCube.qDimensionInfo.length === 2, app); hyperCubeDef.qStateName = layout.qStateName;
const pagedCube = {};
let lastRow = 0;
if (cellCount < (maxLoops * 10000)) {
for (let index = 0; cellCount > lastRow; index += 1) {
const requestPage = [
{
qHeight: 1000,
qLeft: 0,
qTop: lastRow,
qWidth: 10 // should be # of columns
}
];
// eslint-disable-next-line no-await-in-loop
pagedCube[index] = await buildDataCube(hyperCubeDef, layout.qHyperCube, app, requestPage);
lastRow = lastRow + 1000;
}
return pagedCube;
}
return null;
} }
export function initializeDesignList (component, layout) { export function initializeDesignList (component, layout) {
@@ -59,6 +86,6 @@ export function initializeDesignList (component, layout) {
resolve(data); resolve(data);
}; };
stylingField.OnData.bind(listener); stylingField.OnData.bind(listener);
stylingField.getData(); stylingField.getData({ rows: 5000 });
}); });
} }

View File

@@ -39,7 +39,7 @@ const definition = {
component: 'text' component: 'text'
}, },
paragraph1: { paragraph1: {
label: `P&L pivot is a Qlik Sense extension which allows you to display Profit & Loss label: `P&L pivot is a Qlik Sense chart which allows you to display Profit & Loss
reporting with color and font customizations.`, reporting with color and font customizations.`,
component: 'text' component: 'text'
}, },

View File

@@ -23,30 +23,6 @@ const pagination = {
{ {
value: 4, value: 4,
label: '40k cells' label: '40k cells'
},
{
value: 5,
label: '50k cells'
},
{
value: 6,
label: '60k cells'
},
{
value: 7,
label: '70k cells'
},
{
value: 8,
label: '80k cells'
},
{
value: 9,
label: '90k cells'
},
{
value: 10,
label: '100k cells'
} }
], ],
defaultValue: 2 defaultValue: 2
@@ -55,7 +31,8 @@ const pagination = {
ref: 'errormessage', ref: 'errormessage',
label: 'Default error message', label: 'Default error message',
type: 'string', type: 'string',
defaultValue: 'Unable to display all the data. Apply more filters to limit the amount of displayed data.' defaultValue: `Unable to display all the data.
Change the pagination size supported or apply more filters to limit the amount of displayed data.`
} }
} }
}; };

View File

@@ -209,15 +209,33 @@ const tableFormat = {
], ],
defaultValue: 'right' defaultValue: 'right'
}, },
FitChartWidth: {
ref: 'fitchartwidth',
type: 'boolean',
component: 'switch',
label: 'Fill chart width',
options: [
{
value: true,
label: 'On'
},
{
value: false,
label: 'Off'
}
],
defaultValue: false
},
ColumnWidthSlider: { ColumnWidthSlider: {
type: 'number', type: 'number',
component: 'slider', component: 'slider',
label: 'Column width', label: 'Column width',
ref: 'columnwidthslider', ref: 'columnwidthslider',
min: 1, min: 20,
max: 3, max: 250,
step: 1, step: 10,
defaultValue: 2 defaultValue: 50,
show: data => !data.fitchartwidth
}, },
SymbolForNulls: { SymbolForNulls: {
ref: 'symbolfornulls', ref: 'symbolfornulls',

View File

@@ -7,13 +7,12 @@ function cleanupNodes (node) {
}); });
} }
function buildTableHTML (id, title, subtitle, footnote) { function buildTableHTML (containerElement, title, subtitle, footnote) {
const titleHTML = `<p style="font-size:15pt"><b>${title}</b></p>`; const titleHTML = `<p style="font-size:15pt"><b>${title}</b></p>`;
const subtitleHTML = `<p style="font-size:11pt">${subtitle}</p>`; const subtitleHTML = `<p style="font-size:11pt">${subtitle}</p>`;
const footnoteHTML = `<p style="font-size:11pt">${footnote}</p>`; const footnoteHTML = `<p style="font-size:11pt">${footnote}</p>`;
const container = document.querySelector(`[tid="${id}"]`); const kpiTableClone = containerElement[0].querySelector('.kpi-table').cloneNode(true);
const kpiTableClone = container.querySelector('.kpi-table').cloneNode(true); const dataTableClone = containerElement[0].querySelector('.data-table').cloneNode(true);
const dataTableClone = container.querySelector('.data-table').cloneNode(true);
cleanupNodes(kpiTableClone); cleanupNodes(kpiTableClone);
cleanupNodes(kpiTableClone); cleanupNodes(kpiTableClone);
@@ -83,7 +82,7 @@ function buildTableHTML (id, title, subtitle, footnote) {
function downloadXLS (html) { function downloadXLS (html) {
const filename = 'analysis.xls'; const filename = 'analysis.xls';
const blobObject = new Blob([html]); const blobObject = new Blob([html], { type: 'application/vnd.ms-excel' });
// IE/Edge // IE/Edge
if (window.navigator.msSaveOrOpenBlob) { if (window.navigator.msSaveOrOpenBlob) {
@@ -100,8 +99,8 @@ function downloadXLS (html) {
return true; return true;
} }
export function exportXLS (id, title, subtitle, footnote) { export function exportXLS (containerElement, title, subtitle, footnote) {
// original was removing icon when starting export, disable and some spinner instead, shouldn't take enough time to warrant either..? // original was removing icon when starting export, disable and some spinner instead, shouldn't take enough time to warrant either..?
const table = buildTableHTML(id, title, subtitle, footnote); const table = buildTableHTML(containerElement, title, subtitle, footnote);
downloadXLS(table); downloadXLS(table);
} }

View File

@@ -1,42 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { exportXLS } from './excel-export';
class ExportButton extends React.PureComponent {
constructor (props) {
super(props);
this.handleExport = this.handleExport.bind(this);
}
handleExport () {
const { id, excelExport, general } = this.props;
const { title, subtitle, footnote } = general;
if (excelExport) {
exportXLS(id, title, subtitle, footnote);
}
}
render () {
const { excelExport } = this.props;
return excelExport === true && (
<input
className="icon-xls"
onClick={this.handleExport}
src="/Extensions/qlik-smart-pivot/Excel.png"
type="image"
/>
);
}
}
ExportButton.defaultProps = {
excelExport: false
};
ExportButton.propTypes = {
id: PropTypes.string.isRequired,
excelExport: PropTypes.bool,
general: PropTypes.shape({}).isRequired
};
export default ExportButton;

View File

@@ -1,34 +1,39 @@
/* eslint-disable object-shorthand */
/* eslint-disable space-before-function-paren */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { HEADER_FONT_SIZE } from '../initialize-transformed'; import { HEADER_FONT_SIZE } from '../initialize-transformed';
import Tooltip from '../tooltip/index.jsx'; import Tooltip from '../tooltip/index.jsx';
class ColumnHeader extends React.PureComponent { class ColumnHeader extends React.PureComponent {
constructor (props) { constructor(props) {
super(props); super(props);
this.handleSelect = this.handleSelect.bind(this); this.handleSelect = this.handleSelect.bind(this);
} }
handleSelect () { // fixes console error for column selected values
const { entry, qlik } = this.props; handleSelect() {
qlik.backendApi.selectValues(1, [entry.elementNumber], true); const { component, entry } = this.props;
component.selectValues(1, [entry.elementNumber], false);
} }
render () { render() {
const { baseCSS, cellSuffix, colSpan, entry, styling, qlik } = this.props; const { baseCSS, cellWidth, colSpan, component, entry, styling } = this.props;
const inEditState = qlik.inEditState(); const inEditState = component.inEditState();
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM; const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
const style = { const style = {
...baseCSS, ...baseCSS,
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment}px`, fontSize: `${14 + styling.headerOptions.fontSizeAdjustment}px`,
height: isMediumFontSize ? '43px' : '33px', height: isMediumFontSize ? '43px' : '33px',
verticalAlign: 'middle' verticalAlign: 'middle',
minWidth: cellWidth,
maxWidth: cellWidth
}; };
return ( return (
<th <th
className={`grid-cells2${cellSuffix}`} className="grid-cells"
colSpan={colSpan} colSpan={colSpan}
onClick={this.handleSelect} onClick={this.handleSelect}
style={style} style={style}
@@ -46,19 +51,14 @@ class ColumnHeader extends React.PureComponent {
} }
ColumnHeader.defaultProps = { ColumnHeader.defaultProps = {
cellSuffix: '',
colSpan: 1 colSpan: 1
}; };
ColumnHeader.propTypes = { ColumnHeader.propTypes = {
baseCSS: PropTypes.shape({}).isRequired, baseCSS: PropTypes.shape({}).isRequired,
cellSuffix: PropTypes.string, cellWidth: PropTypes.string.isRequired,
colSpan: PropTypes.number, colSpan: PropTypes.number,
entry: PropTypes.shape({ component: PropTypes.shape({
elementNumber: PropTypes.number.isRequired,
name: PropTypes.string.isRequired
}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({ backendApi: PropTypes.shape({
selectValues: function (props, propName) { selectValues: function (props, propName) {
if (props.isSnapshot || typeof props[propName] === 'function') { if (props.isSnapshot || typeof props[propName] === 'function') {
@@ -68,6 +68,10 @@ ColumnHeader.propTypes = {
} }
}).isRequired }).isRequired
}).isRequired, }).isRequired,
entry: PropTypes.shape({
displayValue: PropTypes.string.isRequired,
elementNumber: PropTypes.number.isRequired
}).isRequired,
styling: PropTypes.shape({ styling: PropTypes.shape({
headerOptions: PropTypes.shape({ headerOptions: PropTypes.shape({
fontSizeAdjustment: PropTypes.number.isRequired fontSizeAdjustment: PropTypes.number.isRequired

View File

@@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ExportButton from '../export-button.jsx';
import { HEADER_FONT_SIZE } from '../initialize-transformed'; import { HEADER_FONT_SIZE } from '../initialize-transformed';
import Tooltip from '../tooltip/index.jsx';
const ExportColumnHeader = ({ id, baseCSS, general, title, allowExcelExport, hasSecondDimension, styling }) => { const Dim1Header = ({ component, baseCSS, title, hasSecondDimension, styling }) => {
const inEditState = component.inEditState();
const rowSpan = hasSecondDimension ? 2 : 1; const rowSpan = hasSecondDimension ? 2 : 1;
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM; const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
const style = { const style = {
@@ -21,21 +22,20 @@ const ExportColumnHeader = ({ id, baseCSS, general, title, allowExcelExport, has
rowSpan={rowSpan} rowSpan={rowSpan}
style={style} style={style}
> >
<ExportButton <Tooltip
id={id} isTooltipActive={!inEditState}
excelExport={allowExcelExport} styling={styling}
general={general} tooltipText={title}
/> >
{title} {title}
</Tooltip>
</th> </th>
); );
}; };
ExportColumnHeader.propTypes = { Dim1Header.propTypes = {
id: PropTypes.string.isRequired,
allowExcelExport: PropTypes.bool.isRequired,
baseCSS: PropTypes.shape({}).isRequired, baseCSS: PropTypes.shape({}).isRequired,
general: PropTypes.shape({}).isRequired, component: PropTypes.shape({}).isRequired,
hasSecondDimension: PropTypes.bool.isRequired, hasSecondDimension: PropTypes.bool.isRequired,
styling: PropTypes.shape({ styling: PropTypes.shape({
headerOptions: PropTypes.shape({ headerOptions: PropTypes.shape({
@@ -45,4 +45,4 @@ ExportColumnHeader.propTypes = {
title: PropTypes.string.isRequired title: PropTypes.string.isRequired
}; };
export default ExportColumnHeader; export default Dim1Header;

View File

@@ -1,124 +1,124 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ExportColumnHeader from './export-column-header.jsx'; import Dim1Header from './dim1-header.jsx';
import ColumnHeader from './column-header.jsx'; import ColumnHeader from './column-header.jsx';
import MeasurementColumnHeader from './measurement-column-header.jsx'; import MeasurementColumnHeader from './measurement-column-header.jsx';
import { injectSeparators } from '../utilities'; import { injectSeparators } from '../utilities';
const HeadersTable = ({ data, general, qlik, styling, isKpi }) => { class HeadersTable extends React.PureComponent {
const baseCSS = { render () {
backgroundColor: styling.headerOptions.colorSchema, const {
color: styling.headerOptions.textColor, cellWidth,
fontFamily: styling.options.fontFamily, columnSeparatorWidth,
textAlign: styling.headerOptions.alignment component,
}; data,
isKpi,
styling
} = this.props;
const { const baseCSS = {
dimension1, backgroundColor: styling.headerOptions.colorSchema,
dimension2, color: styling.headerOptions.textColor,
measurements fontFamily: styling.options.fontFamily,
} = data.headers; textAlign: styling.headerOptions.alignment
};
const hasSecondDimension = dimension2.length > 0; const {
dimension1,
dimension2,
measurements
} = data.headers;
return ( const hasSecondDimension = dimension2.length > 0;
<div className="header-wrapper">
<table className="header">
<tbody>
<tr>
{isKpi ?
<ExportColumnHeader
id={qlik.options.id}
allowExcelExport={general.allowExcelExport}
baseCSS={baseCSS}
general={general}
hasSecondDimension={hasSecondDimension}
styling={styling}
title={dimension1[0].name}
/> : null
}
{!isKpi && !hasSecondDimension && measurements.map(measurementEntry => (
<MeasurementColumnHeader
baseCSS={baseCSS}
general={general}
hasSecondDimension={hasSecondDimension}
key={`${measurementEntry.displayValue}-${measurementEntry.name}`}
measurement={measurementEntry}
styling={styling}
/>
))}
{!isKpi && hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map((entry, index) => {
if (entry.isSeparator) {
const separatorStyle = {
color: 'white',
fontFamily: styling.options.fontFamily,
fontSize: `${13 + styling.headerOptions.fontSizeAdjustment}px`
};
return ( const separatorStyle = {
<th minWidth: columnSeparatorWidth,
className="empty" maxWidth: columnSeparatorWidth
key={index} };
style={separatorStyle}
> return (
* <div className="header-wrapper">
</th> <table className="header">
); <tbody>
} <tr>
return ( {isKpi ?
<ColumnHeader <Dim1Header
baseCSS={baseCSS} baseCSS={baseCSS}
cellSuffix={general.cellSuffix} component={component}
colSpan={measurements.length} hasSecondDimension={hasSecondDimension}
entry={entry} styling={styling}
key={entry.displayValue} title={dimension1[0].name}
qlik={qlik} /> : null
}
{!isKpi && !hasSecondDimension && measurements.map(measurementEntry => (
<MeasurementColumnHeader
baseCSS={baseCSS}
cellWidth={cellWidth}
hasSecondDimension={hasSecondDimension}
key={`${measurementEntry.displayValue}-${measurementEntry.name}-${measurementEntry.index}`}
measurement={measurementEntry}
styling={styling} styling={styling}
/> />
); ))}
})} {!isKpi && hasSecondDimension && injectSeparators(dimension2, columnSeparatorWidth).map((entry, index) => {
</tr> if (entry.isSeparator) {
{!isKpi && hasSecondDimension && (
<tr>
{injectSeparators(dimension2, styling.useSeparatorColumns).map((dimensionEntry, index) => {
if (dimensionEntry.isSeparator) {
const separatorStyle = {
color: 'white',
fontFamily: styling.options.fontFamily,
fontSize: `${12 + styling.headerOptions.fontSizeAdjustment}px`
};
return ( return (
<th <th
className="empty" className="empty"
key={index} key={index}
style={separatorStyle} style={separatorStyle}
> />
*
</th>
); );
} }
return measurements.map(measurementEntry => ( return (
<MeasurementColumnHeader <ColumnHeader
baseCSS={baseCSS} baseCSS={baseCSS}
dimensionEntry={dimensionEntry} cellWidth={cellWidth}
general={general} colSpan={measurements.length}
hasSecondDimension={hasSecondDimension} component={component}
key={`${measurementEntry.displayValue}-${measurementEntry.name}-${dimensionEntry.name}`} entry={entry}
measurement={measurementEntry} key={`${entry.displayValue}-${index}-separator`}
styling={styling} styling={styling}
/> />
)); );
})} })}
</tr> </tr>
)} {!isKpi && hasSecondDimension && (
</tbody> <tr>
</table> {injectSeparators(dimension2, columnSeparatorWidth).map((dimensionEntry, index) => {
</div> if (dimensionEntry.isSeparator) {
); return (
}; <th
className="empty"
key={index}
style={separatorStyle}
/>
);
}
return measurements.map(measurementEntry => (
<MeasurementColumnHeader
baseCSS={baseCSS}
cellWidth={cellWidth}
dimensionEntry={dimensionEntry}
hasSecondDimension={hasSecondDimension}
key={`${measurementEntry.displayValue}-${measurementEntry.name}-${measurementEntry.index}-${dimensionEntry.name}`}
measurement={measurementEntry}
styling={styling}
/>
));
})}
</tr>
)}
</tbody>
</table>
</div>
);
}
}
HeadersTable.propTypes = { HeadersTable.propTypes = {
cellWidth: PropTypes.string.isRequired,
columnSeparatorWidth: PropTypes.string.isRequired,
data: PropTypes.shape({ data: PropTypes.shape({
headers: PropTypes.shape({ headers: PropTypes.shape({
dimension1: PropTypes.array, dimension1: PropTypes.array,
@@ -126,17 +126,7 @@ HeadersTable.propTypes = {
measurements: PropTypes.array measurements: PropTypes.array
}) })
}).isRequired, }).isRequired,
general: PropTypes.shape({}).isRequired, component: PropTypes.shape({}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({
selectValues: function (props, propName) {
if (props.isSnapshot || typeof props[propName] === 'function') {
return null;
}
return new Error('Missing implementation of qlik.backendApi.selectValues.');
}
}).isRequired
}).isRequired,
styling: PropTypes.shape({ styling: PropTypes.shape({
headerOptions: PropTypes.shape({}), headerOptions: PropTypes.shape({}),
options: PropTypes.shape({}) options: PropTypes.shape({})

View File

@@ -3,28 +3,30 @@ import PropTypes from 'prop-types';
import { HEADER_FONT_SIZE } from '../initialize-transformed'; import { HEADER_FONT_SIZE } from '../initialize-transformed';
import Tooltip from '../tooltip/index.jsx'; import Tooltip from '../tooltip/index.jsx';
const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measurement, styling }) => { const MeasurementColumnHeader = ({ baseCSS, cellWidth, hasSecondDimension, measurement, styling }) => {
const title = `${measurement.name} ${measurement.magnitudeLabelSuffix}`; const title = `${measurement.name}`;
const { fontSizeAdjustment } = styling.headerOptions; const { fontSizeAdjustment } = styling.headerOptions;
const isMediumFontSize = fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM; const isMediumFontSize = fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
const cellStyle = {
...baseCSS,
verticalAlign: 'middle',
minWidth: cellWidth,
maxWidth: cellWidth
};
if (hasSecondDimension) { if (hasSecondDimension) {
const isPercentageFormat = measurement.format.substring(measurement.format.length - 1) === '%'; const isPercentageFormat = measurement.format.substring(measurement.format.length - 1) === '%';
let baseFontSize = 14; let baseFontSize = 14;
let cellClass = 'grid-cells2';
if (isPercentageFormat) { if (isPercentageFormat) {
baseFontSize = 13; baseFontSize = 13;
cellClass = 'grid-cells2-small';
} }
const cellStyle = { cellStyle.fontSize = `${baseFontSize + fontSizeAdjustment}px`;
...baseCSS, cellStyle.height = isMediumFontSize ? '45px' : '35px';
fontSize: `${baseFontSize + fontSizeAdjustment}px`,
height: isMediumFontSize ? '45px' : '35px',
verticalAlign: 'middle'
};
return ( return (
<th <th
className={`${cellClass}${general.cellSuffix}`} className="grid-cells"
style={cellStyle} style={cellStyle}
> >
<Tooltip <Tooltip
@@ -37,16 +39,12 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
); );
} }
const style = { cellStyle.fontSize = `${15 + fontSizeAdjustment}px`;
...baseCSS, cellStyle.height = isMediumFontSize ? '90px' : '70px';
fontSize: `${15 + fontSizeAdjustment}px`,
height: isMediumFontSize ? '90px' : '70px',
verticalAlign: 'middle'
};
return ( return (
<th <th
className={`grid-cells2${general.cellSuffix}`} className="grid-cells"
style={style} style={cellStyle}
> >
<Tooltip <Tooltip
tooltipText={title} tooltipText={title}
@@ -64,9 +62,7 @@ MeasurementColumnHeader.defaultProps = {
MeasurementColumnHeader.propTypes = { MeasurementColumnHeader.propTypes = {
baseCSS: PropTypes.shape({}).isRequired, baseCSS: PropTypes.shape({}).isRequired,
general: PropTypes.shape({ cellWidth: PropTypes.string.isRequired,
cellSuffix: PropTypes.string.isRequired
}).isRequired,
hasSecondDimension: PropTypes.bool, hasSecondDimension: PropTypes.bool,
measurement: PropTypes.shape({ measurement: PropTypes.shape({
name: PropTypes.string.isRequired name: PropTypes.string.isRequired

View File

@@ -1,21 +1,18 @@
import definition from './definition'; import definition from "./definition";
import { initializeDataCube, initializeDesignList } from './dataset'; import { exportXLS } from "./excel-export";
import initializeStore from './store'; import { initializeDataCube, initializeDesignList } from "./dataset";
import React from 'react'; import initializeStore from "./store";
import ReactDOM from 'react-dom'; import React from "react";
import Root from './root.jsx'; import ReactDOM from "react-dom";
import './main.less'; import Root from "./root.jsx";
import "./main.less";
if (!window._babelPolyfill) { // eslint-disable-line no-underscore-dangle if (!window._babelPolyfill) {
require('@babel/polyfill'); // eslint-disable-line global-require // eslint-disable-line no-underscore-dangle
require("@babel/polyfill"); // eslint-disable-line global-require
} }
export default { export default ({ flags }) => ({
controller: [
'$scope',
'$timeout',
function controller () {}
],
design: { design: {
dimensions: { dimensions: {
max: 1, max: 1,
@@ -24,20 +21,23 @@ export default {
}, },
data: { data: {
dimensions: { dimensions: {
max: function (nMeasures) { max (nMeasures) {
return nMeasures < 9 ? 2 : 1; return nMeasures < 9 ? 2 : 1;
}, },
min: 1, min: 1,
uses: 'dimensions' uses: 'dimensions'
}, },
measures: { measures: {
max: function (nDims) { max (nDims) {
return nDims < 2 ? 9 : 8; return nDims < 2 ? 9 : 8;
}, },
min: 1, min: 1,
uses: 'measures' uses: 'measures'
} }
}, },
// Prevent conversion from and to this object
exportProperties: null,
importProperties: null,
definition, definition,
initialProperties: { initialProperties: {
version: 1.0, version: 1.0,
@@ -45,11 +45,14 @@ export default {
qDimensions: [], qDimensions: [],
qInitialDataFetch: [ qInitialDataFetch: [
{ {
qHeight: 1, qTop: 0,
qWidth: 10 qLeft: 0,
qWidth: 50,
qHeight: 50
} }
], ],
qMeasures: [] qMeasures: [],
qSuppressZero: false
} }
}, },
support: { support: {
@@ -57,23 +60,32 @@ export default {
exportData: true, exportData: true,
snapshot: true snapshot: true
}, },
paint: async function ($element, layout) { async paint ($element, layout, requestPage) {
const dataCube = await initializeDataCube(this, layout); const dataCube = await initializeDataCube(this, layout, requestPage);
const designList = await initializeDesignList(this, layout);
const state = await initializeStore({
$element,
component: this,
dataCube,
designList,
layout
});
const editmodeClass = this.inAnalysisState() ? '' : 'edit-mode'; const editmodeClass = this.inAnalysisState() ? '' : 'edit-mode';
let state, designList;
if (dataCube === null) {
state = {
$element,
component: this,
dataCube,
designList,
layout,
error: true
};
} else {
designList = await initializeDesignList(this, layout);
state = await initializeStore({
$element,
component: this,
dataCube,
designList,
layout,
error: false
});
}
const jsx = ( const jsx = (
<Root <Root editmodeClass={editmodeClass} component={this} state={state} />
editmodeClass={editmodeClass}
qlik={this}
state={state}
/>
); );
ReactDOM.render(jsx, $element[0]); ReactDOM.render(jsx, $element[0]);
@@ -81,10 +93,48 @@ export default {
snapshot: { snapshot: {
canTakeSnapshot: true canTakeSnapshot: true
}, },
setSnapshotData: async function (snapshotLayout) { async setSnapshotData (snapshotLayout) {
snapshotLayout.snapshotData.dataCube = await initializeDataCube(this, snapshotLayout); snapshotLayout.snapshotData.dataCube = await initializeDataCube(
snapshotLayout.snapshotData.designList = await initializeDesignList(this, snapshotLayout); this,
snapshotLayout
);
snapshotLayout.snapshotData.designList = await initializeDesignList(
this,
snapshotLayout
);
return snapshotLayout; return snapshotLayout;
}, },
async getContextMenu (obj, menu) {
if (!this.$scope.layout.allowexportxls) {
return menu;
}
const app = this.backendApi.model.app;
const isPersonalResult = await app.global.isPersonalMode();
// Export as XLS option is removed from desktop because the desktop wrapper blocks downloads.
// Enabled for windows and QCS
// isPersonalMode returns true for both desktop and QCS
// By checking both if has download dialog and if is QCS can enable Export as XLS option on QCS
if (
(this.backendApi.model.layout.qMeta.privileges.includes('exportdata') && !isPersonalResult) ||
(flags.isEnabled('DOWNLOAD_USE_REPORTING') && isPersonalResult)
) {
menu.addItem({
translation: 'Export as XLS',
tid: 'export-excel',
icon: 'export',
select: () => {
exportXLS(
this.$element,
this.$scope.layout.title,
this.$scope.layout.subtitle,
this.$scope.layout.footnote
);
}
});
}
return menu;
},
version: 1.0 version: 1.0
}; });

View File

@@ -1,260 +1,192 @@
import { distinctArray } from './utilities'; import { distinctArray } from "./utilities";
export const HEADER_FONT_SIZE = { export const HEADER_FONT_SIZE = {
SMALL: -1, SMALL: -1,
MEDIUM: 1 MEDIUM: 1,
}; };
function getAlignment (option) { function getAlignment(option) {
const alignmentOptions = { const alignmentOptions = {
1: 'left', 1: "left",
2: 'center', 2: "center",
3: 'right' 3: "right",
}; };
return alignmentOptions[option] || 'left'; return alignmentOptions[option] || "left";
} }
function getFontSizeAdjustment (option) { function getFontSizeAdjustment(option) {
const fontSizeAdjustmentOptions = { const fontSizeAdjustmentOptions = {
1: HEADER_FONT_SIZE.SMALL, 1: HEADER_FONT_SIZE.SMALL,
2: HEADER_FONT_SIZE.MEDIUM 2: HEADER_FONT_SIZE.MEDIUM,
}; };
return fontSizeAdjustmentOptions[option] || 0; return fontSizeAdjustmentOptions[option] || 0;
} }
function getCellSuffix (option) { function generateMeasurements(information) {
const cellSuffixOptions = { return information.map((measurement) => {
1: '-s',
3: '-l'
};
return cellSuffixOptions[option] || '';
}
function getMeasurementFormat (measurement) {
if (measurement.qNumFormat.qType === 'U' || measurement.qNumFormat.qFmt === '##############') {
return '#.##0';
} else if (measurement.qNumFormat.qType === 'R') {
return measurement.qNumFormat.qFmt.replace(/(|)/gi, '');
}
return measurement.qNumFormat.qFmt;
}
function getMagnitudeLabelSuffix (magnitudeOption) {
const magnitudeLabelSuffixOptions = {
'k': ' (k)',
'm': ' (m)'
};
return magnitudeLabelSuffixOptions[magnitudeOption] || '';
}
function generateMeasurements (information) {
return information.map(measurement => {
const format = getMeasurementFormat(measurement);
const formatMagnitude = format.substr(format.length - 1).toLowerCase();
const transformedMeasurement = { const transformedMeasurement = {
format, format: measurement.qNumFormat.qFmt || "#.##0",
magnitudeLabelSuffix: getMagnitudeLabelSuffix(formatMagnitude), name: measurement.qFallbackTitle,
name: measurement.qFallbackTitle
}; };
return transformedMeasurement; return transformedMeasurement;
}); });
} }
function generateDimensionEntry (information, data) { function generateDimensionEntry(information, data) {
return { return {
displayValue: data.qText || data.qNum, displayValue: data.qText || data.qNum,
elementNumber: data.qElemNumber, elementNumber: data.qElemNumber,
name: information.qFallbackTitle, name: information.qFallbackTitle,
value: data.qNum value: data.qNum,
}; };
} }
function generateMatrixCell ({ cell, dimension1Information, dimension2Information, measurementInformation }) { function generateMatrixCell({
cell,
dimension1Information,
dimension2Information,
measurementInformation,
}) {
const matrixCell = { const matrixCell = {
displayValue: cell.qText, displayValue: cell.qText,
format: measurementInformation.format, format: measurementInformation.format,
magnitude: measurementInformation.magnitudeLabelSuffix.substring(
measurementInformation.magnitudeLabelSuffix.length - 2,
measurementInformation.magnitudeLabelSuffix.length - 1
),
magnitudeLabelSuffix: measurementInformation.magnitudeLabelSuffix,
name: measurementInformation.name, name: measurementInformation.name,
parents: { parents: {
dimension1: { dimension1: {
elementNumber: dimension1Information.qElemNumber, elementNumber: dimension1Information.qElemNumber,
header: dimension1Information.qText header: dimension1Information.qText,
}, },
measurement: { measurement: {
header: measurementInformation.name, header: measurementInformation.name,
index: measurementInformation.index index: measurementInformation.index,
} },
}, },
value: cell.qNum value: cell.qNum,
}; };
if (dimension2Information) { if (dimension2Information) {
matrixCell.parents.dimension2 = { matrixCell.parents.dimension2 = {
elementNumber: dimension2Information.qElemNumber elementNumber: dimension2Information.qElemNumber,
header: dimension2Information.qText,
}; };
} }
return matrixCell; return matrixCell;
} }
let lastRow = 0; function generateDataSet(
function generateDataSet ( component,
component, dimensionsInformation, measurementsInformation, dataCube) { dimensionsInformation,
measurementsInformation,
dataCube
) {
const measurements = generateMeasurements(measurementsInformation); const measurements = generateMeasurements(measurementsInformation);
let dimension1 = []; let dimension1 = [];
let dimension2 = []; let dimension2 = [];
let matrix = []; let matrix = [];
const hasSecondDimension = dimensionsInformation.length > 1; const hasSecondDimension = dimensionsInformation.length > 1;
dataCube.forEach(row => { // eslint-disable-next-line no-undefined
lastRow += 1; for (let index = 0; dataCube[index] !== undefined; index++) {
const dimension1Entry = generateDimensionEntry(dimensionsInformation[0], row[0]); // eslint-disable-next-line no-loop-func
dimension1.push(dimension1Entry); dataCube[index].forEach((row) => {
let dimension2Entry; const dimension1Entry = generateDimensionEntry(
let firstDataCell = 1; dimensionsInformation[0],
if (hasSecondDimension) { row[0]
dimension2Entry = generateDimensionEntry(dimensionsInformation[1], row[1]); );
dimension2.push(dimension2Entry); dimension1.push(dimension1Entry);
firstDataCell = 2; let dimension2Entry;
} let firstDataCell = 1;
let matrixRow = row if (hasSecondDimension) {
.slice(firstDataCell, row.length) dimension2Entry = generateDimensionEntry(
.map((cell, cellIndex) => { dimensionsInformation[1],
const measurementInformation = measurements[cellIndex]; row[1]
measurementInformation.index = cellIndex; );
const dimension1Information = row[0]; // eslint-disable-line prefer-destructuring dimension2.push(dimension2Entry);
const dimension2Information = hasSecondDimension ? row[1] : null; firstDataCell = 2;
const generatedCell = generateMatrixCell({ }
cell, let matrixRow = row
dimension1Information, .slice(firstDataCell, row.length)
dimension2Information, .map((cell, cellIndex) => {
measurementInformation const measurementInformation = measurements[cellIndex];
measurementInformation.index = cellIndex;
const dimension1Information = row[0]; // eslint-disable-line prefer-destructuring
const dimension2Information = hasSecondDimension ? row[1] : null;
const generatedCell = generateMatrixCell({
cell,
dimension1Information,
dimension2Information,
measurementInformation,
});
return generatedCell;
}); });
return generatedCell; let appendToRowIndex = matrix.length;
}); if (hasSecondDimension) {
// See if there already is a row for the current dim1
let appendToRowIndex = matrix.length; for (let i = 0; i < matrix.length; i++) {
if (hasSecondDimension) { if (
// See if there already is a row for the current dim1 matrix[i][0].parents.dimension1.header ===
for (let i = 0; i < matrix.length; i++) { matrixRow[0].parents.dimension1.header
if (matrix[i][0].parents.dimension1.header === matrixRow[0].parents.dimension1.header) { ) {
appendToRowIndex = i; appendToRowIndex = i;
matrixRow = matrix[i].concat(matrixRow); matrixRow = matrix[i].concat(matrixRow);
}
} }
} }
} matrix[appendToRowIndex] = matrixRow;
matrix[appendToRowIndex] = matrixRow; });
}); }
// filter header dimensions to only have distinct values // filter header dimensions to only have distinct values
dimension1 = distinctArray(dimension1); dimension1 = distinctArray(dimension1);
dimension2 = distinctArray(dimension2); dimension2 = distinctArray(dimension2);
// Make sure all rows are saturated, otherwise data risks being displayed in the wrong column
matrix = matrix.map((row, rowIndex) => {
if ((hasSecondDimension && row.length == (dimension2.length * measurements.length))
|| (!hasSecondDimension && row.length == measurements.length)) {
// Row is saturated
return row;
}
// Row is not saturated, so must add empty cells to fill the gaps
let newRow = [];
if (hasSecondDimension) {
// Got a second dimension, so need to add measurements for all values of the second dimension
let rowDataIndex = 0;
dimension2.forEach(dim => {
rowDataIndex = appendMissingCells(
row, newRow, rowDataIndex, measurements, rowIndex, dim.elementNumber);
});
} else {
appendMissingCells(row, newRow, 0, measurements, rowIndex);
}
return newRow;
});
return { return {
dimension1: dimension1, dimension1: dimension1,
dimension2: dimension2, dimension2: dimension2,
matrix, matrix,
measurements measurements,
}; };
} }
/* function initializeTransformed({
* Appends the cells of the source row, as well as those missing, to the destination row, starting $element,
* from the given source index. Returns the source index of the next source cell after this has component,
* completed. If there is a second dimension the dim2ElementNumber should be set to the current dataCube,
* index of the dimension2 value being processed. designList,
*/ layout,
function appendMissingCells ( }) {
sourceRow, destRow, sourceIndex, measurements, dim1ElementNumber, dim2ElementNumber = -1) {
let index = sourceIndex;
measurements.forEach((measurement, measureIndex) => {
if (index < sourceRow.length
&& (dim2ElementNumber === -1
|| sourceRow[index].parents.dimension2.elementNumber === dim2ElementNumber)
&& sourceRow[index].parents.measurement.header === measurement.name) {
// Source contains the expected cell
destRow.push(sourceRow[index]);
index++;
} else {
// Source doesn't contain the expected cell, so add empty
destRow.push({
displayValue: '',
parents: {
dimension1: { elementNumber: dim1ElementNumber },
dimension2: { elementNumber: dim2ElementNumber },
measurement: {
header: measurement.name,
index: measureIndex
}
}
});
}
});
return index;
}
function initializeTransformed ({ $element, component, dataCube, designList, layout }) {
const dimensionsInformation = component.backendApi.getDimensionInfos(); const dimensionsInformation = component.backendApi.getDimensionInfos();
const measurementsInformation = component.backendApi.getMeasureInfos(); const measurementsInformation = component.backendApi.getMeasureInfos();
const dimensionCount = layout.qHyperCube.qDimensionInfo.length; const dimensionCount = layout.qHyperCube.qDimensionInfo.length;
const rowCount = component.backendApi.getRowCount(); const { dimension1, dimension2, measurements, matrix } = generateDataSet(
const maxLoops = layout.maxloops; component,
const { dimensionsInformation,
dimension1, measurementsInformation,
dimension2, dataCube
measurements, );
matrix
} = generateDataSet(component, dimensionsInformation, measurementsInformation, dataCube);
const customSchemaBasic = []; const customSchemaBasic = [];
const customSchemaFull = []; const customSchemaFull = [];
let customHeadersCount = 0; let customHeadersCount = 0;
if (designList && designList.length > 0) { if (designList && designList.length > 0) {
const headers = designList[0].split(';'); const headers = designList[0].split(";");
customHeadersCount = headers.length; customHeadersCount = headers.length;
for (let lineNumber = 0; lineNumber < designList.length; lineNumber += 1) { for (let lineNumber = 0; lineNumber < designList.length; lineNumber += 1) {
customSchemaFull[lineNumber] = new Array(headers.length); customSchemaFull[lineNumber] = new Array(headers.length);
const data = designList[lineNumber].split(';'); const data = designList[lineNumber].split(";");
if (data.length === headers.length) { if (data.length === headers.length) {
for (let headerIndex = 0; headerIndex < headers.length; headerIndex += 1) { for (
let headerIndex = 0;
headerIndex < headers.length;
headerIndex += 1
) {
[customSchemaBasic[lineNumber]] = data; [customSchemaBasic[lineNumber]] = data;
customSchemaFull[lineNumber][headerIndex] = data[headerIndex]; customSchemaFull[lineNumber][headerIndex] = data[headerIndex];
} }
@@ -262,45 +194,62 @@ function initializeTransformed ({ $element, component, dataCube, designList, lay
} }
} }
let cellWidth;
if (layout.fitchartwidth) {
// The widths are calculated based on the current element width. Note: this could use % to set
// the widths as percentages of the available width. However, this often results in random
// columns getting 1px wider than the others because of rounding necessary to fill the width.
// This 1px causes missalignment between the data- and header tables.
cellWidth = "";
} else {
// If using the previous solution just set 60px
cellWidth = `${
layout.columnwidthslider > 10 ? layout.columnwidthslider : 60
}px`;
}
// top level properties could be reducers and then components connect to grab what they want, // top level properties could be reducers and then components connect to grab what they want,
// possibly with reselect for some presentational transforms (moving some of the presentational logic like formatting and such) // possibly with reselect for some presentational transforms (moving some of the presentational logic like formatting and such)
const transformedProperties = { const transformedProperties = {
element: $element[0],
data: { data: {
headers: { headers: {
dimension1, // column headers dimension1, // column headers
dimension2, // parent row headers if exists dimension2, // parent row headers if exists
measurements // row headers, looped for each dimension2 if exists measurements, // row headers, looped for each dimension2 if exists
}, },
matrix, // 2d array of all rows/cells to render in body of datatable matrix, // 2d array of all rows/cells to render in body of datatable
meta: { meta: {
dimensionCount: dimensionsInformation.length dimensionCount: dimensionsInformation.length,
} },
}, },
general: { general: {
allowExcelExport: layout.allowexportxls, allowExcelExport: layout.allowexportxls,
allowFilteringByClick: layout.filteroncellclick, allowFilteringByClick: layout.filteroncellclick,
cellSuffix: getCellSuffix(layout.columnwidthslider), // TOOD: move to matrix cells or is it headers.measurements? cellWidth: cellWidth,
errorMessage: layout.errormessage, errorMessage: layout.errormessage,
footnote: layout.footnote, footnote: layout.footnote,
maxLoops,
subtitle: layout.subtitle, subtitle: layout.subtitle,
title: layout.title title: layout.title,
useColumnSeparator: layout.separatorcols && dimensionCount > 1,
}, },
selection: { selection: {
dimensionSelectionCounts: dimensionsInformation.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected) dimensionSelectionCounts: dimensionsInformation.map(
(dimensionInfo) => dimensionInfo.qStateCounts.qSelected
),
}, },
styling: { styling: {
customCSV: { customCSV: {
basic: customSchemaBasic, basic: customSchemaBasic,
count: customHeadersCount, count: customHeadersCount,
full: customSchemaFull full: customSchemaFull,
}, },
hasCustomFileStyle: Boolean(designList), hasCustomFileStyle: Boolean(designList),
headerOptions: { headerOptions: {
alignment: getAlignment(layout.HeaderAlign), alignment: getAlignment(layout.HeaderAlign),
colorSchema: layout.HeaderColorSchema.color, colorSchema: layout.HeaderColorSchema.color,
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersizeheader), fontSizeAdjustment: getFontSizeAdjustment(layout.lettersizeheader),
textColor: layout.HeaderTextColorSchema.color textColor: layout.HeaderTextColorSchema.color,
}, },
options: { options: {
backgroundColor: layout.rowEvenBGColor, backgroundColor: layout.rowEvenBGColor,
@@ -308,55 +257,44 @@ function initializeTransformed ({ $element, component, dataCube, designList, lay
color: layout.BodyTextColorSchema, color: layout.BodyTextColorSchema,
fontFamily: layout.FontFamily, fontFamily: layout.FontFamily,
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersize), fontSizeAdjustment: getFontSizeAdjustment(layout.lettersize),
textAlignment: layout.cellTextAlignment textAlignment: layout.cellTextAlignment,
}, },
conditionalColoring: { conditionalColoring: {
enabled: layout.conditionalcoloring.enabled, enabled: layout.conditionalcoloring.enabled,
colorAllRows: layout.conditionalcoloring.colorall, colorAllRows: layout.conditionalcoloring.colorall,
rows: layout.conditionalcoloring.rows.map(row => row.rowname), rows: layout.conditionalcoloring.rows.map((row) => row.rowname),
colorAllMeasures: typeof layout.conditionalcoloring.colorallmeasures === 'undefined' colorAllMeasures:
|| layout.conditionalcoloring.colorallmeasures, typeof layout.conditionalcoloring.colorallmeasures === "undefined" ||
layout.conditionalcoloring.colorallmeasures,
measures: !layout.conditionalcoloring.measures measures: !layout.conditionalcoloring.measures
? [] : layout.conditionalcoloring.measures.split(',').map(index => Number(index)), ? []
: layout.conditionalcoloring.measures
.split(",")
.map((index) => Number(index)),
threshold: { threshold: {
poor: layout.conditionalcoloring.threshold_poor, poor: layout.conditionalcoloring.threshold_poor,
fair: layout.conditionalcoloring.threshold_fair fair: layout.conditionalcoloring.threshold_fair,
}, },
colors: { colors: {
poor: { poor: {
color: layout.conditionalcoloring.color_poor, color: layout.conditionalcoloring.color_poor,
textColor: layout.conditionalcoloring.textcolor_poor textColor: layout.conditionalcoloring.textcolor_poor,
}, },
fair: { fair: {
color: layout.conditionalcoloring.color_fair, color: layout.conditionalcoloring.color_fair,
textColor: layout.conditionalcoloring.textcolor_fair textColor: layout.conditionalcoloring.textcolor_fair,
}, },
good: { good: {
color: layout.conditionalcoloring.color_good, color: layout.conditionalcoloring.color_good,
textColor: layout.conditionalcoloring.textcolor_good textColor: layout.conditionalcoloring.textcolor_good,
} },
} },
}, },
symbolForNulls: layout.symbolfornulls, symbolForNulls: layout.symbolfornulls,
usePadding: layout.indentbool, usePadding: layout.indentbool,
useSeparatorColumns: dimensionCount === 1 ? false : layout.separatorcols },
}
}; };
if (rowCount > lastRow && rowCount <= (maxLoops * 1000)) {
const requestPage = [
{
qHeight: Math.min(1000, rowCount - lastRow),
qLeft: 0,
qTop: matrix.length,
qWidth: 10 // should be # of columns
}
];
component.backendApi.getData(requestPage).then(() => {
component.paint($element, layout);
});
}
return transformedProperties; return transformedProperties;
} }

View File

@@ -33,7 +33,7 @@ class LinkedScrollWrapper extends React.PureComponent {
unlinkComponent (component) { unlinkComponent (component) {
const componentIndex = this.scrollElements.map(element => element.component).indexOf(component); const componentIndex = this.scrollElements.map(element => element.component).indexOf(component);
if (componentIndex !== -1) { if (componentIndex !== -1) {
this.scrollElements.removeAt(componentIndex); this.scrollElements.splice(componentIndex, 1);
// eslint-disable-next-line react/no-find-dom-node // eslint-disable-next-line react/no-find-dom-node
const node = ReactDOM.findDOMNode(component); const node = ReactDOM.findDOMNode(component);
node.onscroll = null; node.onscroll = null;

View File

@@ -12,9 +12,7 @@
pointer-events: none; pointer-events: none;
} }
._cell(@Width: 50px) { .grid-cells {
min-width: @Width !important;
max-width: @Width !important;
cursor: pointer; cursor: pointer;
line-height: 1em !important; line-height: 1em !important;
} }
@@ -59,10 +57,8 @@
} }
.empty { .empty {
width: 3%;
background: #fff; background: #fff;
min-width: 4px !important; padding: 0 !important;
max-width: 4px !important;
} }
th.main-kpi { th.main-kpi {
@@ -74,67 +70,6 @@
text-align: right; text-align: right;
} }
// *****************
// Medium column size
// *****************
.grid-cells {
position: relative;
._cell(70px);
}
.grid-cells2 {
._cell(70px);
}
.grid-cells-small {
._cell(52px);
}
.grid-cells2-small {
._cell(52px);
}
// *****************
// Small column size
// *****************
.grid-cells-s {
._cell(67px);
}
.grid-cells2-s {
._cell(67px);
}
.grid-cells-small-s {
._cell(52px);
}
.grid-cells2-small-s {
._cell(52px);
}
// *****************
// Large column size
// *****************
.grid-cells-l {
._cell(82px);
}
.grid-cells2-l {
._cell(82px);
}
.grid-cells-small-l {
._cell(66px);
}
.grid-cells2-small-l {
._cell(66px);
}
// END OF GRID CELLS
// First Column // First Column
.fdim-cells { .fdim-cells {
min-width: 230px !Important; min-width: 230px !Important;
@@ -149,14 +84,6 @@
color: #fff; color: #fff;
} }
.grid-cells-header {
padding: 0;
}
.grid-cells-title {
min-width: 522px;
}
.grid { .grid {
height: 50px; height: 50px;
width: 350px; width: 350px;
@@ -178,6 +105,20 @@
width: 100%; width: 100%;
} }
.error {
position: absolute; /*Define position */
width: 100%; /* Full width (cover the whole page) */
height: 100%; /* Full height (cover the whole page) */
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000; /* Specify a stack order in case you're using a different order for other elements */
display: flex;
justify-content: center;
align-items: center;
}
.kpi-table .fdim-cells, .kpi-table .fdim-cells,
.data-table td { .data-table td {
line-height: 1em !important; line-height: 1em !important;

View File

@@ -1,132 +0,0 @@
import { addSeparators } from './utilities';
export function ApplyPreMask (mask, value) { // aqui
if (mask.indexOf(';') >= 0) {
if (value >= 0) {
switch (mask.substring(0, mask.indexOf(';'))) {
case '#.##0':
return (addSeparators(value, '.', ',', 0));
case '#,##0':
return (addSeparators(value, ',', '.', 0));
case '+#.##0':
return (addSeparators(value, '.', ',', 0));
case '+#,##0':
return (addSeparators(value, ',', '.', 0));
default:
return (applyMask(mask.substring(0, mask.indexOf(';')), value));
}
} else {
const vMyValue = value * -1;
let vMyMask = mask.substring(mask.indexOf(';') + 1, mask.length);
vMyMask = vMyMask.replace('(', '');
vMyMask = vMyMask.replace(')', '');
switch (vMyMask) {
case '#.##0':
return (`(${addSeparators(vMyValue, '.', ',', 0)})`);
case '#,##0':
return (`(${addSeparators(vMyValue, ',', '.', 0)})`);
case '-#.##0':
return (`(${addSeparators(vMyValue, '.', ',', 0)})`);
case '-#,##0':
return (`(${addSeparators(vMyValue, ',', '.', 0)})`);
default:
return (`(${applyMask(vMyMask, vMyValue)})`);
}
}
} else {
return (applyMask(mask, value));
}
}
function applyMask (originalMask, originalValue) {
if (!originalMask || isNaN(Number(originalValue))) {
return originalValue;
}
let isNegative;
let result;
let integer;
// find prefix/suffix
let len = originalMask.length;
const start = originalMask.search(/[0-9\-\+#]/);
const prefix = start > 0 ? originalMask.substring(0, start) : '';
// reverse string: not an ideal method if there are surrogate pairs
let str = originalMask.split('')
.reverse()
.join('');
const end = str.search(/[0-9\-\+#]/);
let offset = len - end;
const substr = originalMask.substring(offset, offset + 1);
let index = offset + ((substr === '.' || (substr === ',')) ? 1 : 0);
const suffix = end > 0 ? originalMask.substring(index, len) : '';
// mask with prefix & suffix removed
let mask = originalMask.substring(start, index);
// convert any string to number according to formation sign.
let value = mask.charAt(0) === '-' ? -originalValue : Number(originalValue);
isNegative = value < 0 ? value = -value : 0; // process only abs(), and turn on flag.
// search for separator for grp & decimal, anything not digit, not +/- sign, not #.
result = mask.match(/[^\d\-\+#]/g);
const decimal = (result && result[result.length - 1]) || '.'; // treat the right most symbol as decimal
const group = (result && result[1] && result[0]) || ','; // treat the left most symbol as group separator
// split the decimal for the format string if any.
mask = mask.split(decimal);
// Fix the decimal first, toFixed will auto fill trailing zero.
value = value.toFixed(mask[1] && mask[1].length);
value = String(Number(value)); // convert number to string to trim off *all* trailing decimal zero(es)
// fill back any trailing zero according to format
const posTrailZero = mask[1] && mask[1].lastIndexOf('0'); // look for last zero in format
const part = value.split('.');
// integer will get !part[1]
if (!part[1] || (part[1] && part[1].length <= posTrailZero)) {
value = (Number(value)).toFixed(posTrailZero + 1);
}
const szSep = mask[0].split(group); // look for separator
mask[0] = szSep.join(''); // join back without separator for counting the pos of any leading 0.
const posLeadZero = mask[0] && mask[0].indexOf('0');
if (posLeadZero > -1) {
while (part[0].length < (mask[0].length - posLeadZero)) {
part[0] = `0${part[0]}`;
}
} else if (Number(part[0]) === 0) {
part[0] = '';
}
value = value.split('.');
value[0] = part[0];
// process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
const posSeparator = (szSep[1] && szSep[szSep.length - 1].length);
if (posSeparator) {
integer = value[0];
str = '';
offset = integer.length % posSeparator;
len = integer.length;
for (index = 0; index < len; index++) {
str += integer.charAt(index); // ie6 only support charAt for sz.
// -posSeparator so that won't trail separator on full length
// jshint -W018
if (!((index - offset + 1) % posSeparator) && index < len - posSeparator) {
str += group;
}
}
value[0] = str;
}
value[1] = (mask[1] && value[1]) ? decimal + value[1] : '';
// remove negative sign if result is zero
result = value.join('');
if (result === '0' || result === '') {
// remove negative sign if result is zero
isNegative = false;
}
// put back any negation, combine integer and fraction, and add back prefix & suffix
return prefix + ((isNegative ? '-' : '') + result) + suffix;
}

View File

@@ -1,64 +1,121 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import HeadersTable from './headers-table/index.jsx'; import HeadersTable from "./headers-table/index.jsx";
import DataTable from './data-table/index.jsx'; import DataTable from "./data-table/index.jsx";
import { LinkedScrollWrapper, LinkedScrollSection } from './linked-scroll'; import { LinkedScrollWrapper, LinkedScrollSection } from "./linked-scroll";
const Root = ({ state, qlik, editmodeClass }) => ( class Root extends React.PureComponent {
<div className="root"> render () {
<LinkedScrollWrapper> const { editmodeClass, component, state } = this.props;
<div className={`kpi-table ${editmodeClass}`}> const { data, general, styling, error, element } = state;
<HeadersTable
data={state.data} // Determine cell- and column separator width
general={state.general} let cellWidth = '0px';
isKpi let columnSeparatorWidth = '';
qlik={qlik} if (!error && element) {
styling={state.styling} const tableWidth = element.getBoundingClientRect().width;
/>
<LinkedScrollSection linkVertical> if (general.cellWidth) {
<DataTable cellWidth = general.cellWidth;
data={state.data} if (general.useColumnSeparator) {
general={state.general} columnSeparatorWidth = '8px';
qlik={qlik} }
renderData={false} } else {
styling={state.styling} // 230 is the left "header", rest is magic margins
/> const headerMarginRight = 8 + 230 + 20;
</LinkedScrollSection> const borderWidth = 1;
const rowCellCount = data.matrix[0].length;
let separatorCount = 0;
let separatorWidth = 0;
if (general.useColumnSeparator) {
separatorCount = data.headers.dimension2.length - 1;
separatorWidth = Math.min(
Math.floor((tableWidth * 0.2) / separatorCount),
8
);
columnSeparatorWidth = `${separatorWidth}px`;
}
const separatorWidthSum =
(separatorWidth + borderWidth) * separatorCount;
cellWidth = `${Math.floor(
(tableWidth - separatorWidthSum - headerMarginRight - borderWidth) /
rowCellCount) - borderWidth}px`;
}
}
return (
<div className="root">
{error ? (
<div className={`error ${editmodeClass}`}>
{state.layout.errormessage}
</div>
) : (
<LinkedScrollWrapper>
<div className={`kpi-table ${editmodeClass}`}>
<HeadersTable
cellWidth={cellWidth}
columnSeparatorWidth={columnSeparatorWidth}
component={component}
data={data}
general={general}
isKpi
styling={styling}
/>
<LinkedScrollSection linkVertical>
<DataTable
cellWidth={cellWidth}
columnSeparatorWidth={columnSeparatorWidth}
component={component}
data={data}
general={general}
renderData={false}
styling={styling}
/>
</LinkedScrollSection>
</div>
<div
className={`data-table ${editmodeClass}`}
style={{ width: general.cellWidth ? 'auto' : '100%' }}
>
<LinkedScrollSection linkHorizontal>
<HeadersTable
cellWidth={cellWidth}
columnSeparatorWidth={columnSeparatorWidth}
component={component}
data={data}
general={general}
isKpi={false}
styling={styling}
/>
</LinkedScrollSection>
<LinkedScrollSection linkHorizontal linkVertical>
<DataTable
cellWidth={cellWidth}
columnSeparatorWidth={columnSeparatorWidth}
component={component}
data={data}
general={general}
styling={styling}
/>
</LinkedScrollSection>
</div>
</LinkedScrollWrapper>
)}
</div> </div>
<div className={`data-table ${editmodeClass}`}> );
<LinkedScrollSection linkHorizontal> }
<HeadersTable }
data={state.data}
general={state.general}
isKpi={false}
qlik={qlik}
styling={state.styling}
/>
</LinkedScrollSection>
<LinkedScrollSection
linkHorizontal
linkVertical
>
<DataTable
data={state.data}
general={state.general}
qlik={qlik}
styling={state.styling}
/>
</LinkedScrollSection>
</div>
</LinkedScrollWrapper>
</div>
);
Root.propTypes = { Root.propTypes = {
qlik: PropTypes.shape({}).isRequired, component: PropTypes.shape({}).isRequired,
editmodeClass: PropTypes.string.isRequired,
state: PropTypes.shape({ state: PropTypes.shape({
data: PropTypes.object.isRequired, data: PropTypes.object,
general: PropTypes.object.isRequired, general: PropTypes.object,
styling: PropTypes.object.isRequired styling: PropTypes.object
}).isRequired, }).isRequired
editmodeClass: PropTypes.string.isRequired
}; };
export default Root; export default Root;

View File

@@ -9,21 +9,6 @@ export function distinctArray (array) {
.map(entry => JSON.parse(entry)); .map(entry => JSON.parse(entry));
} }
export function addSeparators (number, thousandSeparator, decimalSeparator, numberOfDecimals) {
const numberString = number.toFixed(numberOfDecimals);
const numberStringParts = numberString.split('.');
let [
wholeNumber,
decimal
] = numberStringParts;
decimal = numberStringParts.length > 1 ? decimalSeparator + decimal : '';
const regexCheckForThousand = /(\d+)(\d{3})/;
while (regexCheckForThousand.test(wholeNumber)) {
wholeNumber = wholeNumber.replace(regexCheckForThousand, `$1${thousandSeparator}$2`);
}
return wholeNumber + decimal;
}
export function Deferred () { export function Deferred () {
this.promise = new Promise((resolve, reject) => { this.promise = new Promise((resolve, reject) => {
this.resolve = resolve; this.resolve = resolve;
@@ -31,7 +16,7 @@ export function Deferred () {
}); });
} }
export function injectSeparators (array, shouldHaveSeparator, suppliedOptions) { export function injectSeparators (array, columnSeparatorWidth, suppliedOptions) {
const defaultOptions = { const defaultOptions = {
atEvery: 1, atEvery: 1,
separator: { isSeparator: true } separator: { isSeparator: true }
@@ -41,7 +26,7 @@ export function injectSeparators (array, shouldHaveSeparator, suppliedOptions) {
...suppliedOptions ...suppliedOptions
}; };
if (!shouldHaveSeparator) { if (!columnSeparatorWidth) {
return array; return array;
} }
return array.reduce((result, entry, index) => { return array.reduce((result, entry, index) => {

View File

@@ -3,76 +3,72 @@ const packageJSON = require('./package.json');
const path = require('path'); const path = require('path');
const DIST = path.resolve("./dist"); const DIST = path.resolve("./dist");
// eslint-disable-next-line no-process-env
const MODE = process.env.NODE_ENV || 'development'; const MODE = process.env.NODE_ENV || 'development';
const SOURCE_MAP = 'source-map';
const DEVTOOL = MODE === 'development' ? SOURCE_MAP : false;
console.log('Webpack mode:', MODE); // eslint-disable-line no-console console.log('Webpack mode:', MODE); // eslint-disable-line no-console
const config = { const config = {
devtool: 'source-map', devtool: DEVTOOL,
entry: ['./src/index.js'], entry: ["./src/index.js"],
externals: { externals: {
jquery: { jquery: {
amd: 'jquery', amd: "jquery",
commonjs: 'jquery', commonjs: "jquery",
commonjs2: 'jquery', commonjs2: "jquery",
root: '_' root: "_",
}, },
qlik: { qlik: {
amd: 'qlik', amd: "qlik",
commonjs: 'qlik', commonjs: "qlik",
commonjs2: 'qlik', commonjs2: "qlik",
root: '_' root: "_",
} },
}, },
mode: MODE, mode: MODE,
module: { module: {
rules: [ rules: [
{ {
enforce: 'pre', enforce: "pre",
exclude: /(node_modules|Library)/, exclude: /(node_modules|Library)/,
loader: 'eslint-loader', loader: "eslint-loader",
options: { options: {
failOnError: true failOnError: true,
}, },
test: /\.(js|jsx)$/ test: /\.(js|jsx)$/,
}, },
{ {
exclude: /node_modules/, exclude: /node_modules/,
test: /\.(js|jsx)$/, test: /\.(js|jsx)$/,
use: { use: {
loader: 'babel-loader', loader: "babel-loader",
options: { options: {
plugins: [ plugins: [
'@babel/plugin-transform-async-to-generator', "@babel/plugin-transform-async-to-generator",
'@babel/plugin-proposal-class-properties' "@babel/plugin-proposal-class-properties",
], ],
presets: [ presets: ["@babel/preset-env", "@babel/preset-react"],
'@babel/preset-env', },
'@babel/preset-react' },
]
}
}
}, },
{ {
test: /.less$/, test: /.less$/,
use: [ use: ["style-loader", "css-loader", "less-loader"],
'style-loader', },
'css-loader', ],
'less-loader'
]
}
]
}, },
output: { output: {
filename: `${packageJSON.name}.js`, filename: "qlik-smart-pivot.js",
libraryTarget: 'amd', libraryTarget: "amd",
path: DIST path: DIST,
}, },
plugins: [ plugins: [
new StyleLintPlugin({ new StyleLintPlugin({
files: '**/*.less' files: "**/*.less",
}) }),
] ],
}; };
module.exports = config; module.exports = config;