Compare commits

...

98 Commits

Author SHA1 Message Date
Albert Backenhof
bec68e7cd4 Merge pull request #39 from qlik-oss/DEB-159/Props
Changed order of properties and added About
2019-04-10 12:05:15 +02:00
Albert Backenhof
02bba4ad5a Merge pull request #38 from qlik-oss/DEB-157/NineMeasures
1 Dim and 9 Measures OR 2 Dim and 8 Measures
2019-04-10 11:30:44 +02:00
Albert Backenhof
57c4b12b24 Merge pull request #36 from qlik-oss/DEB-154/EmptyData
Improved setup of data matrix
2019-04-10 09:13:34 +02:00
Albert Backenhof
ca33540fd6 Merge pull request #35 from qlik-oss/DEB-152/HeaderWidth
The header width should match the table format
2019-04-10 09:13:03 +02:00
Albert Backenhof
cac3fabb2f Changed order of properties and added About
Issue: DEB-159
2019-04-10 08:13:24 +02:00
Albert Backenhof
c57b0edea8 1 Dim and 9 Measures OR 2 Dim and 8 Measures
-Dynamic max values for Dimensions and Measures.

Issue: DEB-157
2019-04-10 07:50:07 +02:00
Albert Backenhof
3862bd294c Arrange matching dim1 data on the same row
-The data is not guaranteed to be processed row
 by row. Therefore, make sure to check the entire
 matrix for the correct row (not just previous row)
 when appending new row data.

Issue: DEB-155
2019-04-09 08:29:37 +02:00
Albert Backenhof
0c18523891 Empty data should result in emtpy cells
-Previously, empty data resulted in the cells
 completely missing. This caused other cells
 to not end up in the correct column.

Issue: DEB-154
2019-04-09 07:50:33 +02:00
Albert Backenhof
629821bd6b The header width should match the table format
Issue: DEB-152
2019-04-08 10:39:02 +02:00
Albert Backenhof
08c5cf8104 Merge pull request #34 from qlik-oss/DEB-130/VersionInDesc
Aligned build to how Dashboard bundle extensions
2019-04-05 14:40:13 +02:00
Tobias Åström
d3c39bea75 Merge pull request #32 from qlik-oss/tsm-color-picker-check
Update metric-semaphores.js
2019-04-05 13:46:58 +02:00
Albert Backenhof
141be3f962 Aligned build to Dashboard bundle extensions build
-Part of the work to streamline how the extensions
 are handled, irregardless of what bundle.

Issue: DEB-130, DEB-133
2019-03-27 09:53:09 +01:00
Albert Backenhof
433a725f33 Merge pull request #33 from qlik-oss/snapshotFix
New preview image and fixed snapshot
2019-03-20 10:28:52 +01:00
Albert Backenhof
769d5cfa3f New preview image and fixed snapshot
-SnapshotApi doesn't have a getProperties
 so fetching the source properties from
 qlik-app instead.

-The snapshot still cannot be rendered in the
 "printing-snapshot" test page because of
 limitations on that page.
2019-03-19 10:38:29 +01:00
Albert Backenhof
9d925b6205 Merge pull request #29 from qlik-oss/feature/QPE-427
[QPE-427] Replace csv styling with dimension styling
2019-03-18 06:57:13 +01:00
Tobias Åström
34527c3d6d Update metric-semaphores.js
We recently had a bug on the color picker when used in extensions (QLIK-94131) so I went through all extensions to check their status, this was the only one with a potential problem.
The property notation should be as I changed it now (but after fixing the bug it will work anyway). However, there might be code elsewhere in the extension that mitigates the bug, so make sure this gets tested.
2019-03-12 09:04:43 +01:00
Kristoffer Lind
35d489c2e2 add updated demo app 2019-02-28 15:58:14 +01:00
Kristoffer Lind
9ef9981305 remove demo app 2019-02-28 15:56:44 +01:00
Kristoffer Lind
398192e057 fix indent and hide when design dimension is applied 2019-02-28 15:36:56 +01:00
Kristoffer Lind
4421217cb0 set default for metric semaphores to apply to nothing 2019-02-28 15:36:56 +01:00
Kristoffer Lind
c92be00ca7 update app with corrected colors for sheet 4 2019-02-28 15:36:56 +01:00
Kristoffer Lind
c66dfdc06c only show row colors when it does not have design dimension 2019-02-28 15:36:55 +01:00
Kristoffer Lind
a882b1d6aa update demo app so its no longer saved with faulty colors 2019-02-28 15:36:55 +01:00
Kristoffer Lind
554b029569 remove unused options include external csv file and path 2019-02-28 15:36:55 +01:00
Kristoffer Lind
4a15628325 remove csv files 2019-02-28 15:36:55 +01:00
Kristoffer Lind
fdb2aaaef4 use originHyperCubeDefinition as basis for dynamically created hyperCubes 2019-02-28 15:36:55 +01:00
Kristoffer Lind
a400e9c233 use createDimension for design cube aswell 2019-02-28 15:36:55 +01:00
Kristoffer Lind
091c564a75 apply styling from dimension 2019-02-28 15:36:54 +01:00
Kristoffer Lind
99eba8afcb split qHyperCube into two cubes, one for data and one for design 2019-02-28 15:36:54 +01:00
giovanni hanselius
cb78a2f2f9 Merge pull request #31 from qlik-oss/QPE-637-header-format-fixes
[QPE 637] Header format fixes
2019-02-28 12:48:09 +01:00
Balazs Gobel
f255efbf5d Handle medium header font size better and prevent cutting text off 2019-02-28 12:40:29 +01:00
giovanni hanselius
2f2d08fedd Merge pull request #30 from qlik-oss/QPE-636-table-format-fixes
[QPE 636] Table format fixes
2019-02-28 11:02:46 +01:00
Balazs Gobel
ac8b70bc84 Use correct fallback for all fonts 2019-02-28 10:49:24 +01:00
giovanni hanselius
0f2a4f9805 Merge pull request #27 from qlik-oss/QPE-631-IE-errors
[QPE 631] Prevent console errors in IE on cell hover
2019-02-28 09:33:55 +01:00
Balazs Gobel
85228412cc show correct color in the color picker 2019-02-27 21:43:09 +01:00
Balazs Gobel
03dfc0ce93 prevent infinite loop in angular color picker
- dualOutput must be the last line
2019-02-27 21:39:59 +01:00
Balazs Gobel
48427df559 enable changing font size in headers 2019-02-27 21:39:31 +01:00
Balazs Gobel
7fda7aa2d9 better defaults when missing font family 2019-02-27 20:54:50 +01:00
Balazs Gobel
4ba12b8b2d show user-selected font-family for the cells 2019-02-27 20:51:18 +01:00
Balazs Gobel
07af7b509e Make table format work again
- working color picker
 - working font size
 - working cell text alignment
 - this is all by preventing the infinite loop in angular
2019-02-27 20:45:06 +01:00
Balazs Gobel
b7ff83e1da prevent console errors in IE on cell hover 2019-02-27 20:37:02 +01:00
giovanni hanselius
621359d6f9 Merge pull request #28 from qlik-oss/QPE-634-column-width-fix
[QPE 634] Fix column width
2019-02-27 15:06:04 +01:00
Balazs Gobel
a71f80f8fa minor cleanup 2019-02-27 14:26:27 +01:00
Balazs Gobel
a6cbfcda70 Fix column width issue
- Look at the value format to determine if column is percentage based
2019-02-27 14:25:50 +01:00
giovanni hanselius
de2e9c16ac Merge pull request #25 from qlik-oss/QPE-549-tooltip-design
[QPE 549] Minor adjustments for tooltip
2019-02-27 12:30:42 +01:00
giovanni hanselius
ad0c0dacba Merge pull request #24 from qlik-oss/QPE-622-visible-scrollbar
[QPE 622] show scrollbar for easier scrolling
2019-02-27 11:14:01 +01:00
giovanni hanselius
97564cf8b1 Merge pull request #26 from qlik-oss/QPE-600-same-padding-in-single-object-mode
[QPE 600] Use same layout in single onject mode as in normal mode
2019-02-27 11:03:33 +01:00
Balazs Gobel
7fa44c06b0 Use same layout in single onject mode as in normal mode
- same padding for single object mode
2019-02-26 16:02:40 +01:00
Balazs Gobel
dae192b7af Minor adjustments for tooltip
- Added tooltip for row header
- Better vertical alignment
- Move static styling to css
2019-02-26 15:44:17 +01:00
Balazs Gobel
5abfd5b7e5 show scrollbar for easier scrolling 2019-02-26 15:10:00 +01:00
giovanni hanselius
65f5d70654 Merge pull request #23 from qlik-oss/feature/QPE-615
[QPE-615] fix metric semaphore colors
2019-02-26 14:34:17 +01:00
Kristoffer Lind
da7ba5c3a8 fix default indexes for metric colors 2019-02-26 14:23:42 +01:00
Kristoffer Lind
6e5df323d8 update sample state 2019-02-26 11:37:31 +01:00
Kristoffer Lind
aad92678db fix metric semaphore colors 2019-02-26 11:31:24 +01:00
giovanni hanselius
15226d8598 Merge pull request #17 from qlik-oss/QPE-426
[QPE-426] color pickers
2019-02-25 15:25:29 +01:00
Balazs Gobel
a5fc586859 Merge branch 'master' into QPE-426 2019-02-25 15:14:13 +01:00
giovanni hanselius
980c0387bf Merge pull request #14 from qlik-oss/feature/QPE-549
[QPE-549] tooltip
2019-02-25 14:45:13 +01:00
Balazs Gobel
710d4a8842 Fix tests 2019-02-25 13:41:11 +01:00
Balazs Gobel
ebb5a7cf16 Additional merge conflict fixes 2019-02-25 12:59:02 +01:00
Balazs Gobel
7107f485be resolve additional merge conflict: removed obsolete code 2019-02-25 12:55:48 +01:00
Balazs Gobel
633fd39b80 Merge branch 'master' into feature/QPE-549
# Conflicts:
#	src/main.less
#	src/paint.jsx
2019-02-25 12:50:47 +01:00
Christopher Lebond
9eeaecb994 Merge pull request #22 from qlik-oss/fix-merge-issues
remove some duplicated css from merge conflicts
2019-02-22 16:11:48 +01:00
Kristoffer Lind
7305175049 fix semaphore alignment 2019-02-22 16:08:51 +01:00
Kristoffer Lind
f99281ff47 hide scrollbars in firefox 2019-02-22 16:04:24 +01:00
Kristoffer Lind
557cd1aeb6 remove some duplicated css 2019-02-22 16:04:24 +01:00
Kristoffer Lind
a25b2c40c0 removed a few more pieces of the colors library 2019-02-22 14:30:18 +01:00
ahmed-Bazzara
46d6520273 update app added
colors set to have a fallback value
2019-02-22 14:29:23 +01:00
ahmed-Bazzara
19286f6c56 color libraries deleted and Qlik's color-pickers introduced 2019-02-22 14:29:23 +01:00
ahmed-Bazzara
bbadc711dc merge conflicts resolved 2019-02-22 11:56:38 +01:00
ahmed-Bazzara
18e2b2024e fixing alignment between cells and row-headers 2019-02-22 11:26:17 +01:00
ahmed-Bazzara
bdf231f88d code enhancements 2019-02-21 18:17:45 +01:00
ahmed-Bazzara
88ad73bd41 tooltip position now follow the mouse 2019-02-21 18:17:45 +01:00
ahmed-Bazzara
530f0919f1 tooltip positioning tweaked 2019-02-21 18:17:45 +01:00
ahmed-Bazzara
79b89a3b25 all logic for tooltip has been moved to it's component
data-cell & column-header components reseted to pure ones
2019-02-21 18:17:44 +01:00
ahmed-Bazzara
4520d6a48a handling tooltip logic within it 2019-02-21 18:17:44 +01:00
ahmed-Bazzara
98678d4a13 jQuery commented code deleted 2019-02-21 18:17:44 +01:00
ahmed-Bazzara
da57204c41 console log removed 2019-02-21 18:17:44 +01:00
ahmed-Bazzara
906a11c6b4 tooltip component cleaned 2019-02-21 18:17:44 +01:00
ahmed-Bazzara
c66ad78e48 tooltip added to column header
comopnent changed to normal component to hold state in it
2019-02-21 18:17:44 +01:00
ahmed-Bazzara
6994bf51a3 showTooltip boolean changed name
skipping values that are header rows
2019-02-21 18:17:44 +01:00
ahmed-Bazzara
521d508604 commented jqeury from paint.js for tooltip 2019-02-21 18:17:43 +01:00
ahmed-Bazzara
3946f6c894 mind width set to tooltip
text inside it is center aligned
2019-02-21 18:16:38 +01:00
ahmed-Bazzara
aeccbf5d17 tooltip disabled in edit state 2019-02-21 18:16:38 +01:00
ahmed-Bazzara
95e330a50d tooltip added 2019-02-21 18:16:38 +01:00
ahmed-Bazzara
27b84c5623 code enhancements 2019-02-21 12:29:16 +01:00
ahmed-Bazzara
a804c31658 tooltip position now follow the mouse 2019-02-19 16:58:39 +01:00
ahmed-Bazzara
9efe580d18 tooltip positioning tweaked 2019-02-19 15:41:30 +01:00
ahmed-Bazzara
48970cb4f4 all logic for tooltip has been moved to it's component
data-cell & column-header components reseted to pure ones
2019-02-15 11:33:43 +01:00
ahmed-Bazzara
fe4b5a72ec handling tooltip logic within it 2019-02-15 09:56:32 +01:00
ahmed-Bazzara
8b41022ddc jQuery commented code deleted 2019-02-15 09:32:55 +01:00
ahmed-Bazzara
9c66c09899 console log removed 2019-02-15 09:26:16 +01:00
ahmed-Bazzara
09d9055643 tooltip component cleaned 2019-02-15 08:56:26 +01:00
ahmed-Bazzara
b1204e0929 tooltip added to column header
comopnent changed to normal component to hold state in it
2019-02-15 08:56:02 +01:00
ahmed-Bazzara
d130ca266d showTooltip boolean changed name
skipping values that are header rows
2019-02-15 08:55:13 +01:00
ahmed-Bazzara
f730dc2827 commented jqeury from paint.js for tooltip 2019-02-15 08:54:11 +01:00
ahmed-Bazzara
b5f74395f7 mind width set to tooltip
text inside it is center aligned
2019-02-15 08:53:35 +01:00
ahmed-Bazzara
dfac9ad5e9 tooltip disabled in edit state 2019-02-14 14:04:15 +01:00
ahmed-Bazzara
377d642fe2 tooltip added 2019-02-14 13:02:59 +01:00
46 changed files with 735 additions and 3549 deletions

View File

@@ -26,9 +26,6 @@ jobs:
--blackduck.username="svc-blackduck" \
--blackduck.password=${svc_blackduck} \
--detect.project.name="viz-bundle-qlik-smart-pivot"
- run:
name: Run tests
command: npm run test-once
bump-version:
<<: *defaults
@@ -56,16 +53,18 @@ jobs:
command: |
export VERSION=$(scripts/get-bumped-version.sh)
echo "Version: ${VERSION}"
npm run build
npm run build:zip
sudo chmod +x scripts/verify-files.sh
scripts/verify-files.sh
environment:
NODE_ENV: production
- persist_to_workspace:
root: ~/qlik-smart-pivot
paths:
- build
- dist
- store_artifacts:
path: build
destination: build
path: dist
destination: dist
deploy:
<<: *defaults
steps:

View File

@@ -117,7 +117,7 @@ module.exports = {
"max-params": ["warn"],
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }],
"prefer-const": ["warn"],
"class-methods-use-this":["warn"],
// plugin:react
"react/jsx-indent": ["warn", 2],
"react/jsx-indent-props": ["warn", 2],
@@ -130,7 +130,8 @@ module.exports = {
"react/jsx-no-literals": ["off"],
"react/jsx-max-depth": ["off"], // rule throws exception in single-dimension-measure
"react/jsx-filename-extension": ["warn"],
"react/prefer-stateless-function": ["warn"]
"react/prefer-stateless-function": ["warn"],
"react/no-set-state": ["warn"]
},
extends: [
"eslint:all",

2
.gitignore vendored
View File

@@ -19,7 +19,7 @@ $RECYCLE.BIN/
# Temporary build files
node_modules/
build/
dist/
BUMPED_VERSION
# =========================

View File

@@ -16,11 +16,6 @@ It's specifically focused on financial reports, trying to solve some common need
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).
# If the import does not work at first time
- remove [Accounts.csv](resources/Accounts.csv), [Accounts2.csv](resources/Accounts2.csv) and [Excel.png](resources/Excel.png), zip it again and import.
- Then reintroduce [Accounts.csv](resources/Accounts.csv), [Accounts2.csv](resources/Accounts2.csv) and [Excel.png](resources/Excel.png), zip it again and import.
# Installation
1. Download the extension zip, `qlik-smart-pivot_<version>.zip`, from the latest release(https://github.com/qlik-oss/PLSmartPivot/releases/latest)

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -1,16 +0,0 @@
{
"name": "P&L pivot",
"description": "Profit & Loss reporting with color and font customizations.",
"type": "visualization",
"version": "X.Y.Z",
"icon": "table",
"preview": "",
"author": "Ivan Felipe Asensio <ivan.felipe@qlik.com>",
"homepage": "",
"keywords": "qlik-sense, visualization",
"license": "MIT",
"repository": "https://github.com/qlik-oss/PLSmartPivot",
"dependencies": {
"qlik-sense": ">=5.5.x"
}
}

View File

@@ -1,3 +0,0 @@
qlik-smart-pivot.js;
qlik-smart-pivot.css;
qlik-smart-pivot.qext

View File

@@ -1,23 +1,61 @@
var gulp = require('gulp');
var gutil = require('gulp-util');
var zip = require('gulp-zip');
var del = require('del');
var path = require('path');
var settings = require('./settings');
var webpackConfig = require('./webpack.config');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var jeditor = require("gulp-json-editor");
var pkg = require('./package.json');
var srcFiles = path.resolve('./src/**/*.*');
var DIST = './dist';
var VERSION = process.env.VERSION || 'local-dev';
gulp.task('remove-build-folder', function(){
return del([settings.buildDestination], { force: true });
gulp.task('qext', function () {
var qext = {
name: 'P&L pivot',
type: 'visualization',
description: pkg.description + '\nVersion: ' + VERSION,
version: VERSION,
icon: 'table',
preview: 'qlik-smart-pivot.png',
keywords: 'qlik-sense, visualization',
author: pkg.author,
homepage: pkg.homepage,
license: pkg.license,
repository: pkg.repository,
dependencies: {
'qlik-sense': '>=5.5.x'
}
};
if (pkg.contributors) {
qext.contributors = pkg.contributors;
}
var src = require('stream').Readable({
objectMode: true
});
src._read = function () {
this.push(new gutil.File({
cwd: '',
base: '',
path: pkg.name + '.qext',
contents: Buffer.from(JSON.stringify(qext, null, 4))
}));
this.push(null);
};
return src.pipe(gulp.dest(DIST));
});
gulp.task('clean', function(){
return del([DIST], { force: true });
});
gulp.task('zip-build', function(){
return gulp.src(settings.buildDestination + '/**/*')
.pipe(zip(`${settings.name}_${settings.version}.zip`))
.pipe(gulp.dest(settings.buildDestination));
return gulp.src(DIST + '/**/*')
.pipe(zip(`${pkg.name}_${VERSION}.zip`))
.pipe(gulp.dest(DIST));
});
gulp.task('add-assets', function(){
return gulp.src('./assets/**/*').pipe(gulp.dest(DIST));
});
gulp.task('webpack-build', done => {
@@ -36,40 +74,14 @@ gulp.task('webpack-build', done => {
});
});
gulp.task('update-qext-version', function () {
return gulp.src(`${settings.buildDestination}/${settings.name}.qext`)
.pipe(jeditor({
'version': settings.version
}))
.pipe(gulp.dest(settings.buildDestination));
});
gulp.task('build',
gulp.series('remove-build-folder', 'webpack-build', 'update-qext-version', 'zip-build')
gulp.series('clean', 'webpack-build', 'qext', 'add-assets')
);
gulp.task('zip',
gulp.series('build', 'zip-build')
);
gulp.task('default',
gulp.series('build')
);
gulp.task('watch', () => new Promise((resolve, reject) => {
webpackConfig.entry.unshift('webpack-dev-server/client?http://localhost:' + settings.port);
const compiler = webpack(webpackConfig);
const originalOutputFileSystem = compiler.outputFileSystem;
const devServer = new WebpackDevServer(compiler, {
headers: {
"Access-Control-Allow-Origin": "*"
},
}).listen(settings.port, 'localhost', error => {
compiler.outputFileSystem = originalOutputFileSystem;
if (error) {
console.error(error); // eslint-disable-line no-console
return reject(error);
}
// eslint-disable-next-line no-console
console.log('Listening at localhost:' + settings.port);
resolve(null, devServer);
});
}));

View File

@@ -1,69 +0,0 @@
const path = require('path');
const settings = require('./settings');
module.exports = (config) => {
config.set({
browsers: ['SlimChromeHeadless'],
customLaunchers: {
SlimChromeHeadless: {
base: 'ChromeHeadless',
flags: [
'--headless',
'--disable-gpu',
'--disable-translate',
'--disable-extensions'
]
}
},
files: [
{
pattern: 'src/**/*.spec.js',
watched: true
}
],
frameworks: ['jasmine'],
preprocessors: {
'src/**/*.spec.{js, jsx}': [
'webpack',
'sourcemap'
]
},
webpack: {
devtool: 'source-map',
externals: {
jquery: {
amd: 'jquery',
commonjs: 'jquery',
commonjs2: 'jquery',
root: '_'
}
},
mode: settings.mode,
module: {
rules: [
{
exclude: [/node_modules/],
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-transform-async-to-generator',
'@babel/plugin-proposal-class-properties'],
presets: ['@babel/preset-react']
},
test: /\.(js|jsx)$/
},
{
loader: 'ignore-loader',
test: /\.less$/
}
]
},
resolve: {
alias: {
'test-utilities': path.resolve('test/test-utilities')
},
modules: ['node_modules']
}
}
});
};

2633
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,15 @@
{
"name": "qlik-smart-pivot",
"version": "0.0.1",
"description": "Formatted table for P&L reports.",
"keywords": "smart pivot qliksense extension",
"description": "Profit & Loss reporting with color and font customizations.",
"homepage": "",
"repository": "https://github.com/iviasensio/PLSmartPivot",
"author": "Ivan Felipe Asensio <ivan.felipe@qlik.com>",
"license": "MIT",
"scripts": {
"build": "gulp build",
"build:zip": "gulp zip",
"eslint": "eslint src",
"eslint:fix": "eslint --fix src",
"test": "karma start karma.conf.js",
"test-once": "karma start karma.conf.js --single-run",
"watch": "gulp watch",
"stylelint": "stylelint src/main.less"
},
"devDependencies": {
@@ -24,7 +21,6 @@
"@babel/preset-react": "7.0.0",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"copy-webpack-plugin": "4.5.3",
"css-loader": "1.0.0",
"del": "2.0.2",
"enzyme": "3.8.0",
@@ -33,15 +29,8 @@
"eslint-loader": "2.1.1",
"eslint-plugin-react": "7.11.1",
"gulp": "4.0.0",
"gulp-json-editor": "2.4.3",
"gulp-util": "^3.0.7",
"gulp-zip": "3.0.2",
"jasmine-core": "3.2.1",
"jasmine-enzyme": "7.0.1",
"karma": "3.0.0",
"karma-chrome-launcher": "2.2.0",
"karma-jasmine": "1.1.2",
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "3.0.5",
"less": "3.8.1",
"less-loader": "4.1.0",
"lodash.merge": "4.6.1",
@@ -49,9 +38,7 @@
"stylelint": "8.4.0",
"stylelint-webpack-plugin": "0.10.5",
"text-loader": "0.0.1",
"webpack": "4.20.2",
"webpack-cli": "3.1.2",
"webpack-dev-server": "3.1.10"
"webpack": "4.20.2"
},
"dependencies": {
"prop-types": "15.6.2",

View File

@@ -1,44 +0,0 @@
Accounts;Bold;Background;FontStyle;LetterColor;Align;Size;Comment
Revenues;;rgb(183, 219, 255);<italic>;;<center>;;<comment>
Gross sales revenues;;;;;;;
Less return & allowances;;;;;;;
Net sales revenues;<bold>;<soft>;;;;;
Cost of goods sold;<bold>;;;;;;
Direct materials;;;;;;;
Direct labor;;;;;;;
Manufacturing overhead;;#b7dbff;<italic>;;<center>;;<comment>
Indirect labor;;;;;;;
Depreciation, manufacturing equip;;;;;;;
Other mfr overhead;;;;;;;
Net mfr overhead;<bold>;<violete>;;;;;
Net costs of goods sold;<bold>;<violete>;;;;;
Gross profit;<bold>;<dark>;;;<center>;<large>;
Operating expenses;<bold>;;<italic>;;;;
Selling expenses;;;;;;;
Sales salaries;;;;;;;
Warranty expenses;;;;;;;
Depreciation, store equipment;;;;;;;
Other selling expenses3;;;;;;;
Total selling expenses;<bold>;<violete>;;;;;
General & administrative expenses;;#b7dbff;<italic>;;<center>;;<comment>
Administration salaries;;;;;;;
Rent expenses;;;;;;;
Depreciation, computers;;;;;;;
Other general & admin expenses;;;;;;;
total general & admin expenses;<bold>;<soft>;;;;;
total operating expenses;<bold>;<violete>;;;;;
Operating income before taxes;<bold>;<dark>;;<white>;;<large>;
Financial revenue & expenses;;#b7dbff;<italic>;;<center>;;<comment>
Revenue from investments;;;;;;;
Less interest expenses;;;;;;;
Net financial gain (expense);;;;;;;
Income before tax & extraordinary items;<bold>;<dark>;;<white>;;<large>;
Less income tax on operations;;;;;;;
Income before extraordinary items;;;;;;;
Extraordinary items;;#b7dbff;<italic>;;<center>;;<comment>
Sale of land;;;;;;;
Less initial cost;;;;;;;
Net gain on sale of land;<bold>;<soft>;;;;;
Less income tax on gain;;;;;;;
Extraordinary items after tax;<bold>;<soft>;;;;;
Net Income (Profit);<bold>;<dark>;;<white>;<center>;<large>;
1 Accounts Bold Background FontStyle LetterColor Align Size Comment
2 Revenues rgb(183, 219, 255) <italic> <center> <comment>
3 Gross sales revenues
4 Less return & allowances
5 Net sales revenues <bold> <soft>
6 Cost of goods sold <bold>
7 Direct materials
8 Direct labor
9 Manufacturing overhead #b7dbff <italic> <center> <comment>
10 Indirect labor
11 Depreciation, manufacturing equip
12 Other mfr overhead
13 Net mfr overhead <bold> <violete>
14 Net costs of goods sold <bold> <violete>
15 Gross profit <bold> <dark> <center> <large>
16 Operating expenses <bold> <italic>
17 Selling expenses
18 Sales salaries
19 Warranty expenses
20 Depreciation, store equipment
21 Other selling expenses3
22 Total selling expenses <bold> <violete>
23 General & administrative expenses #b7dbff <italic> <center> <comment>
24 Administration salaries
25 Rent expenses
26 Depreciation, computers
27 Other general & admin expenses
28 total general & admin expenses <bold> <soft>
29 total operating expenses <bold> <violete>
30 Operating income before taxes <bold> <dark> <white> <large>
31 Financial revenue & expenses #b7dbff <italic> <center> <comment>
32 Revenue from investments
33 Less interest expenses
34 Net financial gain (expense)
35 Income before tax & extraordinary items <bold> <dark> <white> <large>
36 Less income tax on operations
37 Income before extraordinary items
38 Extraordinary items #b7dbff <italic> <center> <comment>
39 Sale of land
40 Less initial cost
41 Net gain on sale of land <bold> <soft>
42 Less income tax on gain
43 Extraordinary items after tax <bold> <soft>
44 Net Income (Profit) <bold> <dark> <white> <center> <large>

View File

@@ -1,21 +0,0 @@
Accounts;Bold;Background;FontStyle;LetterColor;Align;Size;Comment
Revenues;<bold>;;<italic>;;<center>;;<comment>
Net sales revenues;<bold>;rgb(128, 191, 255);;;;;
Cost of goods sold;<bold>;rgb(128, 191, 255);;;;;
Manufacturing overhead;<bold>;;<italic>;;<center>;;<comment>
Net mfr overhead;<bold>;rgb(128, 191, 255);;;;;
Net costs of goods sold;<bold>;rgb(128, 191, 255);;;;;
Gross profit;<bold>;rgb(0, 102, 204);<white>;;<center>;<large>;
Operating expenses;<bold>;;<italic>;;;;
Total selling expenses;<bold>;rgb(128, 191, 255);;;;;
General & administrative expenses;<bold>;;<italic>;;<center>;;<comment>
Other general & admin expenses;<bold>;rgb(128, 191, 255);<white>;;<center>;<large>;
total general & admin expenses;<bold>;<soft>;;;;;
total operating expenses;<bold>;rgb(128, 191, 255);;;;;
Operating income before taxes;<bold>;rgb(0, 102, 204);;<white>;;<large>;
Financial revenue & expenses;<bold>;;<italic>;;<center>;;<comment>
Income before tax & extraordinary items;<bold>;rgb(0, 102, 204);;<white>;;<large>;
Extraordinary items;<bold>;;<italic>;;<center>;;<comment>
Net gain on sale of land;<bold>;rgb(0, 102, 204);<white>;;<center>;<large>;
Extraordinary items after tax;<bold>;rgb(0, 102, 204);<white>;;<center>;<large>;
Net Income (Profit);<bold>;<night>;;<white>;<center>;<large>;
1 Accounts Bold Background FontStyle LetterColor Align Size Comment
2 Revenues <bold> <italic> <center> <comment>
3 Net sales revenues <bold> rgb(128, 191, 255)
4 Cost of goods sold <bold> rgb(128, 191, 255)
5 Manufacturing overhead <bold> <italic> <center> <comment>
6 Net mfr overhead <bold> rgb(128, 191, 255)
7 Net costs of goods sold <bold> rgb(128, 191, 255)
8 Gross profit <bold> rgb(0, 102, 204) <white> <center> <large>
9 Operating expenses <bold> <italic>
10 Total selling expenses <bold> rgb(128, 191, 255)
11 General & administrative expenses <bold> <italic> <center> <comment>
12 Other general & admin expenses <bold> rgb(128, 191, 255) <white> <center> <large>
13 total general & admin expenses <bold> <soft>
14 total operating expenses <bold> rgb(128, 191, 255)
15 Operating income before taxes <bold> rgb(0, 102, 204) <white> <large>
16 Financial revenue & expenses <bold> <italic> <center> <comment>
17 Income before tax & extraordinary items <bold> rgb(0, 102, 204) <white> <large>
18 Extraordinary items <bold> <italic> <center> <comment>
19 Net gain on sale of land <bold> rgb(0, 102, 204) <white> <center> <large>
20 Extraordinary items after tax <bold> rgb(0, 102, 204) <white> <center> <large>
21 Net Income (Profit) <bold> <night> <white> <center> <large>

Binary file not shown.

View File

@@ -1,21 +0,0 @@
Accounts;Bold;Background;FontStyle;LetterColor;Align;Size;Comment
Revenues;<bold>;;<italic>;;<center>;;<comment>
Net sales revenues;<bold>;RGB(225,226,226);;;;;
Cost of goods sold;<bold>;RGB(225,226,226);;;;;
Manufacturing overhead;<bold>;;<italic>;;<center>;;<comment>
Net mfr overhead;<bold>;RGB(225,226,226);;;;;
Net costs of goods sold;<bold>;RGB(225,226,226);;;;;
Gross profit;<bold>;RGB(193,216,47);;;<center>;<large>;
Operating expenses;<bold>;;<italic>;;;;
Total selling expenses;<bold>;RGB(225,226,226);;;;;
General & administrative expenses;<bold>;;<italic>;;<center>;;<comment>
Other general & admin expenses;<bold>;rgb(128, 191, 255);<white>;;<center>;<large>;
total general & admin expenses;<bold>;<soft>;;;;;
total operating expenses;<bold>;rgb(128, 191, 255);<white>;;;;
Operating income before taxes;<bold>;RGB(193,216,47);;;;<large>;
Financial revenue & expenses;<bold>;;<italic>;;<center>;;<comment>
Income before tax & extraordinary items;<bold>;RGB(193,216,47);;;;<large>;
Extraordinary items;<bold>;;<italic>;;<center>;;<comment>
Net gain on sale of land;<bold>;RGB(193,216,47);;;<center>;<large>;
Extraordinary items after tax;<bold>;RGB(193,216,47);;;<center>;<large>;
Net Income (Profit);<bold>;<night>;;<white>;<center>;<large>;
1 Accounts Bold Background FontStyle LetterColor Align Size Comment
2 Revenues <bold> <italic> <center> <comment>
3 Net sales revenues <bold> RGB(225,226,226)
4 Cost of goods sold <bold> RGB(225,226,226)
5 Manufacturing overhead <bold> <italic> <center> <comment>
6 Net mfr overhead <bold> RGB(225,226,226)
7 Net costs of goods sold <bold> RGB(225,226,226)
8 Gross profit <bold> RGB(193,216,47) <center> <large>
9 Operating expenses <bold> <italic>
10 Total selling expenses <bold> RGB(225,226,226)
11 General & administrative expenses <bold> <italic> <center> <comment>
12 Other general & admin expenses <bold> rgb(128, 191, 255) <white> <center> <large>
13 total general & admin expenses <bold> <soft>
14 total operating expenses <bold> rgb(128, 191, 255) <white>
15 Operating income before taxes <bold> RGB(193,216,47) <large>
16 Financial revenue & expenses <bold> <italic> <center> <comment>
17 Income before tax & extraordinary items <bold> RGB(193,216,47) <large>
18 Extraordinary items <bold> <italic> <center> <comment>
19 Net gain on sale of land <bold> RGB(193,216,47) <center> <large>
20 Extraordinary items after tax <bold> RGB(193,216,47) <center> <large>
21 Net Income (Profit) <bold> <night> <white> <center> <large>

View File

@@ -2,8 +2,8 @@
set -o errexit
echo "Creating release for version: $VERSION"
echo "Artifact name: ./build/${3}_${VERSION}.zip"
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} "./build/${3}_${4}.zip"
echo "Artifact name: ./dist/${3}_${VERSION}.zip"
$HOME/bin/ghr -t ${ghoauth} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} "./dist/${3}_${4}.zip"
# Usage

25
scripts/verify-files.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
# The build script has a known race-condition that sometimes causes it to not include all files
# in the built zip. This script verifies the that the zip contains the correct number of files.
set -o errexit
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))
zip_file_count=$(zipinfo dist/${name}_${VERSION}.zip | grep ^- | wc -l)
if [ "${expected_file_count}" -ne "${zip_file_count}" ]; then
# File count is incorrect
echo "Expected file count ${expected_file_count}, but was ${zip_file_count}"
exit 1
fi
echo "File count OK"
exit 0

View File

@@ -1,13 +0,0 @@
const path = require('path');
const packageJSON = require('./package.json');
const defaultBuildDestination = path.resolve("./build");
module.exports = {
buildDestination: process.env.BUILD_PATH || defaultBuildDestination,
mode: process.env.NODE_ENV || 'development',
name: packageJSON.name,
version: process.env.VERSION || 'local-dev',
url: process.env.BUILD_URL || defaultBuildDestination,
port: process.env.PORT || 8085
};

View File

@@ -2,10 +2,134 @@ import React from 'react';
import PropTypes from 'prop-types';
import { ApplyPreMask } from '../masking';
import { addSeparators } from '../utilities';
import Tooltip from '../tooltip/index.jsx';
class DataCell extends React.PureComponent {
constructor (props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
}
handleSelect () {
const { data: { meta: { dimensionCount } }, general: { allowFilteringByClick }, measurement, qlik } = this.props;
const hasSecondDimension = dimensionCount > 1;
if (!allowFilteringByClick) {
return;
}
qlik.backendApi.selectValues(0, [measurement.parents.dimension1.elementNumber], true);
if (hasSecondDimension) {
qlik.backendApi.selectValues(1, [measurement.parents.dimension2.elementNumber], true);
}
}
render () {
const {
data,
general,
measurement,
styleBuilder,
styling
} = this.props;
let textAlignment = styling.options.textAlignment || 'Right';
let cellStyle = {
fontFamily: styling.options.fontFamily,
...styleBuilder.getStyle(),
paddingLeft: '5px',
textAlign: textAlignment
};
const isEmptyCell = measurement.displayValue === '';
const isColumnPercentageBased = (/%/).test(measurement.format);
let formattedMeasurementValue;
if (isEmptyCell) {
formattedMeasurementValue = '';
cellStyle.cursor = 'default';
} else if (styleBuilder.hasComments()) {
formattedMeasurementValue = '.';
} else {
formattedMeasurementValue = formatMeasurementValue(measurement, styling);
}
const { semaphoreColors, semaphoreColors: { fieldsToApplyTo } } = styling;
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
const dimension1Row = measurement.parents.dimension1.elementNumber;
const isSpecifiedMetricField = fieldsToApplyTo.metricsSpecificFields.indexOf(dimension1Row) !== -1;
const shouldHaveSemaphoreColors = (fieldsToApplyTo.applyToMetric || isSpecifiedMetricField);
if (isValidSemaphoreValue && shouldHaveSemaphoreColors) {
const { backgroundColor, color } = getSemaphoreColors(measurement, semaphoreColors);
cellStyle = {
...styleBuilder.getStyle(),
backgroundColor,
color,
fontFamily: styling.options.fontFamily,
paddingLeft: '5px',
textAlign: textAlignment
};
}
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 (
<td
className={`${cellClass}${general.cellSuffix}`}
onClick={isEmptyCell ? null : this.handleSelect}
style={cellStyle}
>
<Tooltip
tooltipText={formattedMeasurementValue}
>
{formattedMeasurementValue}
</Tooltip>
</td>
);
}
}
DataCell.propTypes = {
data: PropTypes.shape({
headers: PropTypes.shape({
measurements: PropTypes.array.isRequired
}).isRequired
}).isRequired,
general: PropTypes.shape({
cellSuffix: PropTypes.string.isRequired
}).isRequired,
measurement: PropTypes.shape({
format: PropTypes.string,
name: PropTypes.string,
value: PropTypes.any
}).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,
styleBuilder: PropTypes.shape({
hasComments: PropTypes.func.isRequired
}).isRequired,
styling: PropTypes.shape({
symbolForNulls: PropTypes.any.isRequired
}).isRequired
};
export default DataCell;
function formatMeasurementValue (measurement, styling) {
// TODO: measurement.name is a horrible propertyname, it's actually the column header
const isColumnPercentageBased = measurement.parents.measurement.header.substring(0, 1) === '%';
const isColumnPercentageBased = (/%/).test(measurement.format);
let formattedMeasurementValue = '';
if (isColumnPercentageBased) {
if (isNaN(measurement.value)) {
@@ -53,108 +177,11 @@ function formatMeasurementValue (measurement, styling) {
}
function getSemaphoreColors (measurement, semaphoreColors) {
if (measurement < semaphoreColors.status.critical) {
if (measurement.value < semaphoreColors.status.critical) {
return semaphoreColors.statusColors.critical;
}
if (measurement < semaphoreColors.status.medium) {
if (measurement.value < semaphoreColors.status.medium) {
return semaphoreColors.statusColors.medium;
}
return semaphoreColors.statusColors.normal;
}
class DataCell extends React.PureComponent {
constructor (props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
}
handleSelect () {
const { data: { meta: { dimensionCount } }, general: { allowFilteringByClick }, measurement, qlik } = this.props;
const hasSecondDimension = dimensionCount > 1;
if (!allowFilteringByClick) {
return;
}
qlik.backendApi.selectValues(0, [measurement.parents.dimension1.elementNumber], true);
if (hasSecondDimension) {
qlik.backendApi.selectValues(1, [measurement.parents.dimension2.elementNumber], true);
}
}
render () {
const { data, general, measurement, styleBuilder, styling } = this.props;
const isColumnPercentageBased = measurement.name.substring(0, 1) === '%';
let formattedMeasurementValue = formatMeasurementValue(measurement, styling);
if (styleBuilder.hasComments()) {
formattedMeasurementValue = '.';
}
let textAlignment = 'Right';
const textAlignmentProp = styling.options.textAlignment;
if (textAlignmentProp) {
textAlignment = textAlignmentProp;
}
const { semaphoreColors, semaphoreColors: { fieldsToApplyTo } } = styling;
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
const shouldHaveSemaphoreColors = fieldsToApplyTo.applyToMetric && (fieldsToApplyTo.applyToAll || fieldsToApplyTo.specificFields.indexOf(measurement.name) !== -1);
let cellStyle;
if (isValidSemaphoreValue && shouldHaveSemaphoreColors) {
const { backgroundColor, color } = getSemaphoreColors(measurement, semaphoreColors);
cellStyle = {
backgroundColor,
color,
fontFamily: styling.options.fontFamily,
...styleBuilder.getStyle(),
paddingLeft: '4px',
textAlign: textAlignment
};
}
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 (
<td
className={`${cellClass}${general.cellSuffix}`}
onClick={this.handleSelect}
style={cellStyle}
>
{formattedMeasurementValue}
</td>
);
}
}
DataCell.propTypes = {
data: PropTypes.shape({
headers: PropTypes.shape({
measurements: PropTypes.array.isRequired
}).isRequired
}).isRequired,
general: PropTypes.shape({
cellSuffix: PropTypes.string.isRequired
}).isRequired,
measurement: PropTypes.shape({
format: PropTypes.string,
name: PropTypes.string,
value: PropTypes.any
}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({
selectValues: PropTypes.func.isRequired
}).isRequired
}).isRequired,
styleBuilder: PropTypes.shape({
hasComments: PropTypes.func.isRequired
}).isRequired,
styling: PropTypes.shape({
symbolForNulls: PropTypes.any.isRequired
}).isRequired
};
export default DataCell;

View File

@@ -69,6 +69,7 @@ const DataTable = ({ data, general, qlik, renderData, styling }) => {
</td>
);
}
const { dimension1: dimension1Info, dimension2, measurement } = measurementData.parents;
const id = `${dimension1Info.elementNumber}-${dimension2 && dimension2.elementNumber}-${measurement.header}`;
return (

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import HeaderPadding from './header-padding.jsx';
import Tooltip from '../tooltip/index.jsx';
class RowHeader extends React.PureComponent {
constructor (props) {
@@ -15,18 +16,25 @@ class RowHeader extends React.PureComponent {
}
render () {
const { entry, rowStyle, styleBuilder, styling } = this.props;
const { entry, rowStyle, styleBuilder, styling, qlik } = this.props;
const inEditState = qlik.inEditState();
return (
<td
className="fdim-cells"
onClick={this.handleSelect}
style={rowStyle}
>
<HeaderPadding
styleBuilder={styleBuilder}
styling={styling}
/>
{entry.displayValue}
<Tooltip
isTooltipActive={!inEditState}
tooltipText={entry.displayValue}
>
<HeaderPadding
styleBuilder={styleBuilder}
styling={styling}
/>
{entry.displayValue}
</Tooltip>
</td>
);
}
@@ -38,7 +46,12 @@ RowHeader.propTypes = {
}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({
selectValues: PropTypes.func.isRequired
selectValues: function (props, propName) {
if (props.isSnapshot || typeof props[propName] === 'function') {
return null;
}
return new Error('Missing implementation of qlik.backendApi.selectValues.');
}
}).isRequired
}).isRequired,
rowStyle: PropTypes.shape({}).isRequired,

91
src/dataset.js Normal file
View File

@@ -0,0 +1,91 @@
import qlik from 'qlik';
function createCube (definition, app) {
return new Promise(resolve => {
app.createCube(definition, resolve);
});
}
async function buildDataCube (originCubeDefinition, dimensionIndexes, app) {
const cubeDefinition = {
...originCubeDefinition,
qInitialDataFetch: [
{
qHeight: 1000,
qWidth: 10
}
],
qDimensions: [originCubeDefinition.qDimensions[dimensionIndexes.dimension1]],
qMeasures: originCubeDefinition.qMeasures
};
if (dimensionIndexes.dimension2) {
cubeDefinition.qDimensions.push(originCubeDefinition.qDimensions[dimensionIndexes.dimension2]);
}
const cube = await createCube(cubeDefinition, app);
return cube.qHyperCube.qDataPages[0].qMatrix;
}
async function buildDesignCube (originCubeDefinition, dimensionIndexes, app) {
if (!dimensionIndexes.design) {
return null;
}
const cube = await createCube({
qInitialDataFetch: [
{
qHeight: 1000,
qWidth: 1
}
],
qDimensions: [originCubeDefinition.qDimensions[dimensionIndexes.design]]
}, app);
return cube.qHyperCube.qDataPages[0].qMatrix;
}
const STYLE_SEPARATOR_COUNT = 7;
function findDesignDimension (qMatrix) {
return qMatrix[0].map(entry => (entry.qText.match(/;/g) || []).length).indexOf(STYLE_SEPARATOR_COUNT);
}
function getDimensionIndexes (dimensionsInformation, designDimensionIndex) {
const hasDesign = designDimensionIndex !== -1;
const nonDesignDimensionCount = hasDesign ? dimensionsInformation.length - 1 : dimensionsInformation.length;
const dimension1 = designDimensionIndex === 0 ? 1 : 0;
let dimension2 = false;
if (nonDesignDimensionCount === 2) {
dimension2 = hasDesign && designDimensionIndex < 2 ? 2 : 1;
}
const design = hasDesign && designDimensionIndex;
const firstMeasurementIndex = dimensionsInformation.length;
return {
design,
dimension1,
dimension2,
firstMeasurementIndex
};
}
export async function initializeCubes ({ component, layout }) {
const app = qlik.currApp(component);
const designDimensionIndex = findDesignDimension(layout.qHyperCube.qDataPages[0].qMatrix);
const dimensionsInformation = layout.qHyperCube.qDimensionInfo;
const dimensionIndexes = getDimensionIndexes(dimensionsInformation, designDimensionIndex);
let properties;
if (component.backendApi.isSnapshot) {
// Fetch properties of source
properties = (await app.getObjectProperties(layout.sourceObjectId)).properties;
} else {
properties = await component.backendApi.getProperties();
}
const originCubeDefinition = properties.qHyperCubeDef;
const designCube = await buildDesignCube(originCubeDefinition, dimensionIndexes, app);
const dataCube = await buildDataCube(originCubeDefinition, dimensionIndexes, app);
return {
design: designCube,
data: dataCube
};
}

View File

@@ -1,68 +0,0 @@
const colorLibrary = {
type: 'items',
label: 'Primary Colors Library',
items: {
ColLibClean: {
ref: 'collibclean',
translation: 'Clean',
type: 'string',
defaultValue: '#ffffff'
},
ColLibSoft: {
ref: 'collibsoft',
translation: 'Soft',
type: 'string',
defaultValue: '#efefef'
},
ColLibDark: {
ref: 'collibdark',
translation: 'Dark',
type: 'string',
defaultValue: '#c4c4c4'
},
ColLibNight: {
ref: 'collibnight',
translation: 'Night',
type: 'string',
defaultValue: '#808080'
},
ColLibRed: {
ref: 'collibred',
translation: 'Red',
type: 'string',
defaultValue: '#d58b94'
},
ColLibOrange: {
ref: 'colliborange',
translation: 'Orange',
type: 'string',
defaultValue: '#fd6600'
},
ColLibViolete: {
ref: 'collibviolete',
translation: 'Violete',
type: 'string',
defaultValue: '#ccc0ff'
},
ColLibBlue: {
ref: 'collibblue',
translation: 'Blue',
type: 'string',
defaultValue: '#4575b4'
},
ColLibGreen: {
ref: 'collibgreen',
translation: 'Green',
type: 'string',
defaultValue: '#7bb51c'
},
ColLibCustom: {
ref: 'collibcustom',
label: 'Custom',
type: 'string',
defaultValue: '#ffcccc'
}
}
};
export default colorLibrary;

View File

@@ -24,102 +24,26 @@ const header = {
defaultValue: 2
},
headercolors: {
ref: 'HeaderColorSchema',
type: 'string',
component: 'dropdown',
component: 'color-picker',
defaultValue: {
index: 6,
color: '#4477aa'
},
label: 'Background Header Color',
options: [
{
value: 'Clean',
label: 'Clean'
},
{
value: 'Soft',
label: 'Soft'
},
{
value: 'Dark',
label: 'Dark'
},
{
value: 'Night',
label: 'Night'
},
{
value: 'Blue',
label: 'Blue'
},
{
value: 'Orange',
label: 'Orange'
},
{
value: 'Red',
label: 'Red'
},
{
value: 'Green',
label: 'Green'
},
{
value: 'Violete',
label: 'Violete'
},
{
value: 'Custom',
label: 'Custom'
}
],
defaultValue: 'Night'
ref: 'HeaderColorSchema',
type: 'object',
dualOutput: true
},
HeaderTextColor: {
ref: 'HeaderTextColorSchema',
type: 'string',
component: 'dropdown',
label: 'Text Header Color',
options: [
{
value: 'Black',
label: 'Black'
},
{
value: 'DimGray',
label: 'DimGray'
},
{
value: 'ForestGreen',
label: 'ForestGreen'
},
{
value: 'Gainsboro',
label: 'Gainsboro'
},
{
value: 'Indigo',
label: 'Indigo'
},
{
value: 'Navy',
label: 'Navy'
},
{
value: 'Purple',
label: 'Purple'
},
{
value: 'WhiteSmoke',
label: 'WhiteSmoke'
},
{
value: 'White',
label: 'White'
},
{
value: 'YellowGreen',
label: 'YellowGreen'
}
],
defaultValue: 'WhiteSmoke'
component: 'color-picker',
defaultValue: {
index: 1,
color: '#ffffff'
},
type: 'object',
dualOutput: true
},
HeaderFontSize: {
ref: 'lettersizeheader',
@@ -136,7 +60,7 @@ const header = {
label: 'Medium'
}
],
defaultValue: 2
defaultValue: 1
}
}
};

View File

@@ -1,10 +1,8 @@
import pagination from './pagination';
import header from './header';
import formatted from './formatted';
import tableFormat from './table-format';
import conceptSemaphores from './concept-semaphores';
import metricSemaphores from './metric-semaphores';
import colorLibrary from './color-library';
import pijamaColorLibrary from './pijama-color-library';
const definition = {
component: 'accordion',
@@ -20,20 +18,38 @@ const definition = {
},
uses: 'data'
},
sorting: {
uses: 'sorting'
},
settings: {
items: {
ColorLibrary: colorLibrary,
ConceptSemaphores: conceptSemaphores,
Formatted: formatted,
Formatted: tableFormat,
Header: header,
MetricSemaphores: metricSemaphores,
Pagination: pagination,
PijamaColorLibrary: pijamaColorLibrary
Pagination: pagination
},
uses: 'settings'
},
sorting: {
uses: 'sorting'
about: {
component: 'items',
label: 'About',
items: {
header: {
label: 'P&L pivot',
style: 'header',
component: 'text'
},
paragraph1: {
label: `P&L pivot is a Qlik Sense extension which allows you to display Profit & Loss
reporting with color and font customizations.`,
component: 'text'
},
paragraph2: {
label: 'P&L pivot is based upon an extension created by Ivan Felipe Asensio.',
component: 'text'
}
}
}
},
type: 'items'

View File

@@ -23,7 +23,7 @@ const metricSemaphores = {
ref: 'metricssemaphore',
translation: 'Metrics affected (1,2,4,...)',
type: 'string',
defaultValue: '0',
defaultValue: '-',
show (data) {
return !data.allmetrics;
}
@@ -39,8 +39,9 @@ const metricSemaphores = {
label: 'Critic Color Fill',
type: 'object',
component: 'color-picker',
dualOutput: true,
defaultValue: {
index: 7,
index: 8,
color: '#f93f17'
}
},
@@ -49,8 +50,9 @@ const metricSemaphores = {
label: 'Critic Color Text',
type: 'object',
component: 'color-picker',
dualOutput: true,
defaultValue: {
index: 10,
index: 11,
color: '#ffffff'
}
},
@@ -65,8 +67,9 @@ const metricSemaphores = {
label: 'Medium Color Fill',
type: 'object',
component: 'color-picker',
dualOutput: true,
defaultValue: {
index: 8,
index: 9,
color: '#ffcf02'
}
},
@@ -75,8 +78,9 @@ const metricSemaphores = {
label: 'Medium Color Text',
type: 'object',
component: 'color-picker',
dualOutput: true,
defaultValue: {
index: 11,
index: 12,
color: '#000000'
}
},
@@ -85,8 +89,9 @@ const metricSemaphores = {
label: 'Success Color Fill',
type: 'object',
component: 'color-picker',
dualOutput: true,
defaultValue: {
index: 9,
index: 10,
color: '#276e27'
}
},
@@ -95,8 +100,9 @@ const metricSemaphores = {
label: 'Success Color Text',
type: 'object',
component: 'color-picker',
dualOutput: true,
defaultValue: {
index: 10,
index: 11,
color: '#ffffff'
}
}

View File

@@ -1,68 +0,0 @@
const pijamaColorLibrary = {
type: 'items',
label: 'Pijama Colors Library',
items: {
ColLibCleanP: {
ref: 'collibcleanp',
translation: 'Clean',
type: 'string',
defaultValue: '#ffffff'
},
ColLibSoftP: {
ref: 'collibsoftp',
translation: 'Soft',
type: 'string',
defaultValue: '#ffffff'
},
ColLibDarkP: {
ref: 'collibdarkp',
translation: 'Dark',
type: 'string',
defaultValue: '#efefef'
},
ColLibNightP: {
ref: 'collibnightp',
translation: 'Night',
type: 'string',
defaultValue: '#c4c4c4'
},
ColLibRedP: {
ref: 'collibredp',
translation: 'Red',
type: 'string',
defaultValue: '#ffcccc'
},
ColLibOrangeP: {
ref: 'colliborangep',
translation: 'Orange',
type: 'string',
defaultValue: '#ffcc66'
},
ColLibVioleteP: {
ref: 'collibvioletep',
translation: 'Violete',
type: 'string',
defaultValue: '#e6e6ff'
},
ColLibBlueP: {
ref: 'collibbluep',
translation: 'Blue',
type: 'string',
defaultValue: '#b3d9ff'
},
ColLibGreenP: {
ref: 'collibgreenp',
translation: 'Green',
type: 'string',
defaultValue: '#98fb98'
},
ColLibCustomP: {
ref: 'collibcustomp',
label: 'Custom',
type: 'string',
defaultValue: '#ffffff'
}
}
};
export default pijamaColorLibrary;

View File

@@ -1,4 +1,9 @@
const formatted = {
// fixes case for when there are 3 dimensions, missies the case with 1 design dimension and 1 data dimension
function hasDesignDimension (data) {
return data.qHyperCubeDef.qDimensions.length > 2;
}
const tableFormat = {
type: 'items',
label: 'Table Format',
items: {
@@ -6,7 +11,8 @@ const formatted = {
ref: 'indentbool',
type: 'boolean',
label: 'Indent',
defaultValue: true
defaultValue: false,
show: data => !hasDesignDimension(data)
},
SeparatorColumns: {
ref: 'separatorcols',
@@ -14,72 +20,29 @@ const formatted = {
label: 'Separator Columns',
defaultValue: false
},
CustomFileBool: {
ref: 'customfilebool',
type: 'boolean',
label: 'Include External File',
defaultValue: false
rowEvenBGColor: {
component: 'color-picker',
defaultValue: {
color: '#fff',
index: 1
},
label: 'Even row background color',
ref: 'rowEvenBGColor',
type: 'object',
dualOutput: true,
show: data => !hasDesignDimension(data)
},
CustomFile: {
ref: 'customfile',
label: 'Name of CSV file (; separated)',
type: 'string',
defaultValue: '',
show (data) {
return data.customfilebool;
}
},
colors: {
ref: 'ColorSchema',
type: 'string',
component: 'dropdown',
label: 'BackGround Style',
options: [
{
value: 'Clean',
label: 'Clean'
},
{
value: 'Soft',
label: 'Soft'
},
{
value: 'Dark',
label: 'Dark'
},
{
value: 'Night',
label: 'Night'
},
{
value: 'Blue',
label: 'Blue'
},
{
value: 'Orange',
label: 'Orange'
},
{
value: 'Red',
label: 'Red'
},
{
value: 'Green',
label: 'Green'
},
{
value: 'Violete',
label: 'Violete'
},
{
value: 'Custom',
label: 'Custom'
}
],
defaultValue: 'Clean',
show (data) {
return !data.customfilebool;
}
rowOddBGColor: {
component: 'color-picker',
defaultValue: {
color: '#b6d7ea',
index: 4
},
label: 'Odd row background color',
ref: 'rowOddBGColor',
type: 'object',
dualOutput: true,
show: data => !hasDesignDimension(data)
},
BodyTextColor: {
ref: 'BodyTextColorSchema',
@@ -129,9 +92,7 @@ const formatted = {
}
],
defaultValue: 'Black',
show (data) {
return !data.customfilebool;
}
show: data => !hasDesignDimension(data)
},
FontFamily: {
ref: 'FontFamily',
@@ -140,35 +101,35 @@ const formatted = {
label: 'FontFamily',
options: [
{
value: 'QlikView Sans',
value: 'QlikView Sans, -apple-system, sans-serif',
label: 'QlikView Sans'
},
{
value: 'Arial',
value: 'Arial, -apple-system, sans-serif',
label: 'Arial'
},
{
value: 'Calibri',
value: 'Calibri, -apple-system, sans-serif',
label: 'Calibri'
},
{
value: 'Comic Sans MS',
value: 'Comic Sans MS, -apple-system, sans-serif',
label: 'Comic Sans MS'
},
{
value: 'MS Sans Serif',
value: 'MS Sans Serif, -apple-system, sans-serif',
label: 'MS Sans Serif'
},
{
value: 'Tahoma',
value: 'Tahoma, -apple-system, sans-serif',
label: 'Tahoma'
},
{
value: 'Verdana',
value: 'Verdana, -apple-system, sans-serif',
label: 'Verdana'
}
],
defaultValue: 'QlikView Sans'
defaultValue: 'QlikView Sans, -apple-system, sans-serif'
},
DataFontSize: {
ref: 'lettersize',
@@ -260,4 +221,4 @@ const formatted = {
}
};
export default formatted;
export default tableFormat;

View File

@@ -1,10 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import Tooltip from '../tooltip/index.jsx';
class ColumnHeader extends React.PureComponent {
constructor (props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
}
@@ -14,10 +14,12 @@ class ColumnHeader extends React.PureComponent {
}
render () {
const { baseCSS, cellSuffix, colSpan, entry, styling } = this.props;
const { baseCSS, cellSuffix, colSpan, entry, styling, qlik } = this.props;
const inEditState = qlik.inEditState();
const style = {
...baseCSS,
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment} px`,
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment}px`,
height: '45px',
verticalAlign: 'middle'
};
@@ -29,7 +31,12 @@ class ColumnHeader extends React.PureComponent {
onClick={this.handleSelect}
style={style}
>
{entry.displayValue}
<Tooltip
isTooltipActive={!inEditState}
tooltipText={entry.displayValue}
>
{entry.displayValue}
</Tooltip>
</th>
);
}
@@ -50,7 +57,12 @@ ColumnHeader.propTypes = {
}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({
selectValues: PropTypes.func.isRequired
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({

View File

@@ -1,14 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import ExportButton from '../export-button.jsx';
import { HEADER_FONT_SIZE } from '../initialize-transformed';
const ExportColumnHeader = ({ baseCSS, general, title, allowExcelExport, hasSecondDimension, styling }) => {
const rowSpan = hasSecondDimension ? 2 : 1;
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
const style = {
...baseCSS,
cursor: 'default',
fontSize: `${16 + styling.headerOptions.fontSizeAdjustment} px`,
height: '80px',
fontSize: `${16 + styling.headerOptions.fontSizeAdjustment}px`,
height: isMediumFontSize ? '100px' : '80px',
verticalAlign: 'middle',
width: '230px'
};

View File

@@ -126,7 +126,12 @@ HeadersTable.propTypes = {
general: PropTypes.shape({}).isRequired,
qlik: PropTypes.shape({
backendApi: PropTypes.shape({
selectValues: PropTypes.func.isRequired
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({

View File

@@ -13,7 +13,8 @@ describe('<HeadersTable />', () => {
qlik: {
backendApi: {
selectValues: () => {}
}
},
inEditState: () => {}
},
styling
};

View File

@@ -1,9 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { HEADER_FONT_SIZE } from '../initialize-transformed';
import Tooltip from '../tooltip/index.jsx';
const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measurement, styling }) => {
const title = `${measurement.name} ${measurement.magnitudeLabelSuffix}`;
const { fontSizeAdjustment } = styling.headerOptions;
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
if (hasSecondDimension) {
const isPercentageFormat = measurement.format.substring(measurement.format.length - 1) === '%';
let baseFontSize = 14;
@@ -15,8 +19,8 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
const cellStyle = {
...baseCSS,
cursor: 'default',
fontSize: `${baseFontSize + fontSizeAdjustment} px`,
height: '25px',
fontSize: `${baseFontSize + fontSizeAdjustment}px`,
height: isMediumFontSize ? '50px' : '25px',
verticalAlign: 'middle'
};
return (
@@ -25,23 +29,28 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
style={cellStyle}
>
<span className="wrapclass25">
{title}
<Tooltip
tooltipText={title}
>
{title}
</Tooltip>
</span>
</th>
);
}
const isLong = (title.length > 11 && fontSizeAdjustment === 0) || (title.length > 12 && fontSizeAdjustment === -2);
const suffixWrap = isLong ? '70' : 'empty';
const style = {
...baseCSS,
cursor: 'default',
fontSize: `${15 + fontSizeAdjustment} px`,
height: '70px',
fontSize: `${15 + fontSizeAdjustment}px`,
height: isMediumFontSize ? '100px' : '80px',
verticalAlign: 'middle'
};
return (
<th
className={`grid-cells2 ${general.cellSuffix}`}
className={`grid-cells2${general.cellSuffix}`}
style={style}
>
<span

View File

@@ -10,27 +10,38 @@ export default {
controller: [
'$scope',
'$timeout',
function () { }
function controller () {}
],
design: {
dimensions: {
max: 1,
min: 0
}
},
data: {
dimensions: {
max: 2,
max: function (nMeasures) {
return nMeasures < 9 ? 2 : 1;
},
min: 1,
uses: 'dimensions'
},
measures: {
max: 9,
max: function (nDims) {
return nDims < 2 ? 9 : 8;
},
min: 1,
uses: 'measures'
}
},
definition,
initialProperties: {
version: 1.0,
qHyperCubeDef: {
qDimensions: [],
qInitialDataFetch: [
{
qHeight: 1000,
qHeight: 1,
qWidth: 10
}
],

View File

@@ -1,31 +1,9 @@
import jQuery from 'jquery';
import { distinctArray } from './utilities';
// TODO: rename colors
function initializeColors ({ layout }) {
return {
vColLibBlue: layout.collibblue,
vColLibBlueP: layout.collibbluep,
vColLibClean: layout.collibclean,
vColLibCleanP: layout.collibcleanp,
vColLibCustom: layout.collibcustom,
vColLibCustomP: layout.collibcustomp,
vColLibDark: layout.collibdark,
vColLibDarkP: layout.collibdarkp,
vColLibGreen: layout.collibgreen,
vColLibGreenP: layout.collibgreenp,
vColLibNight: layout.collibnight,
vColLibNightP: layout.collibnightp,
vColLibOrange: layout.colliborange,
vColLibOrangeP: layout.colliborangep,
vColLibRed: layout.collibred,
vColLibRedP: layout.collibredp,
vColLibSoft: layout.collibsoft,
vColLibSoftP: layout.collibsoftp,
vColLibViolete: layout.collibviolete,
vColLibVioleteP: layout.collibvioletep
};
}
export const HEADER_FONT_SIZE = {
SMALL: -1,
MEDIUM: 1
};
function getAlignment (option) {
const alignmentOptions = {
@@ -39,8 +17,8 @@ function getAlignment (option) {
function getFontSizeAdjustment (option) {
const fontSizeAdjustmentOptions = {
1: -1,
2: 1,
1: HEADER_FONT_SIZE.SMALL,
2: HEADER_FONT_SIZE.MEDIUM,
3: 2
};
@@ -129,15 +107,15 @@ function generateMatrixCell ({ cell, dimension1Information, dimension2Informatio
}
let lastRow = 0;
function generateDataSet (component, dimensionsInformation, measurementsInformation) {
const dimension1 = [];
const dimension2 = [];
function generateDataSet (component, dimensionsInformation, measurementsInformation, cubes) {
let dimension1 = [];
let dimension2 = [];
const measurements = generateMeasurements(measurementsInformation);
let matrix = [];
let previousDim1Entry;
const hasSecondDimension = dimensionsInformation.length > 1;
component.backendApi.eachDataRow((rowIndex, row) => {
const hasDesignDimension = cubes.design;
const hasSecondDimension = hasDesignDimension ? dimensionsInformation.length > 2 : dimensionsInformation.length > 1;
cubes.data.forEach(row => {
lastRow += 1;
const dimension1Entry = generateDimensionEntry(dimensionsInformation[0], row[0]);
dimension1.push(dimension1Entry);
@@ -148,7 +126,7 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat
dimension2.push(dimension2Entry);
firstDataCell = 2;
}
const matrixRow = row
let matrixRow = row
.slice(firstDataCell, row.length)
.map((cell, cellIndex) => {
const measurementInformation = measurements[cellIndex];
@@ -164,37 +142,65 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat
return generatedCell;
});
let appendToRowIndex = matrix.length;
if (hasSecondDimension) {
const currentDim1Entry = row[0].qText;
const isSameDimension1AsPrevious = currentDim1Entry === previousDim1Entry;
if (isSameDimension1AsPrevious) {
const updatedRow = matrix[matrix.length - 1].concat(matrixRow);
matrix = [
...matrix.slice(0, matrix.length - 1),
updatedRow
];
} else {
matrix[matrix.length] = matrixRow;
// See if there already is a row for the current dim1
for (let i = 0; i < matrix.length; i++) {
if (matrix[i][0].parents.dimension1.header === matrixRow[0].parents.dimension1.header) {
appendToRowIndex = i;
matrixRow = matrix[i].concat(matrixRow);
}
}
previousDim1Entry = currentDim1Entry;
} else {
matrix[matrix.length] = matrixRow;
}
matrix[appendToRowIndex] = matrixRow;
});
// filter header dimensions to only have distinct values
dimension1 = distinctArray(dimension1);
dimension2 = distinctArray(dimension2);
// Make sure all rows are saturated, otherwise data risks being displayed in the wrong column
matrix = matrix.map((row, rowIndex) => {
if (row.length == dimension2.length) {
// Row is saturated
return row;
}
// Row is not saturated, so must add empty cells to fill the gaps
let newRow = [];
let cellIndex = 0;
dimension2.forEach(dim => {
measurements.forEach(measurement => {
if (cellIndex < row.length
&& row[cellIndex].parents.dimension2.elementNumber === dim.elementNumber
&& row[cellIndex].parents.measurement.header === measurement.name) {
newRow.push(row[cellIndex]);
cellIndex++;
} else {
newRow.push({
displayValue: '',
parents: {
dimension1: { elementNumber: rowIndex },
dimension2: { elementNumber: dim.elementNumber },
measurement: { header: measurement.name }
}
});
}
});
});
return newRow;
});
return {
dimension1: distinctArray(dimension1),
dimension2: distinctArray(dimension2),
dimension1: dimension1,
dimension2: dimension2,
matrix,
measurements
};
}
async function initializeTransformed ({ $element, layout, component }) {
const colors = initializeColors({ layout });
function initializeTransformed ({ $element, component, cubes, layout }) {
const dimensionsInformation = component.backendApi.getDimensionInfos();
const measurementsInformation = component.backendApi.getMeasureInfos();
const dimensionCount = layout.qHyperCube.qDimensionInfo.length;
@@ -205,37 +211,29 @@ async function initializeTransformed ({ $element, layout, component }) {
dimension2,
measurements,
matrix
} = generateDataSet(component, dimensionsInformation, measurementsInformation);
} = generateDataSet(component, dimensionsInformation, measurementsInformation, cubes);
const customSchemaBasic = [];
const customSchemaFull = [];
let customHeadersCount = 0;
function readCustomSchema () {
const url = `/Extensions/qlik-smart-pivot/${layout.customfile}`;
if (cubes.design) {
const allTextLines = cubes.design.map(entry => entry[0].qText);
const headers = allTextLines[0].split(';');
customHeadersCount = headers.length;
for (let lineNumber = 0; lineNumber < allTextLines.length; lineNumber += 1) {
customSchemaFull[lineNumber] = new Array(headers.length);
const data = allTextLines[lineNumber].split(';');
return jQuery.get(url).then(response => {
const allTextLines = response.split(/\r\n|\n/);
const headers = allTextLines[0].split(';');
customHeadersCount = headers.length;
for (let lineNumber = 0; lineNumber < allTextLines.length; lineNumber += 1) {
customSchemaFull[lineNumber] = new Array(headers.length);
const data = allTextLines[lineNumber].split(';');
if (data.length === headers.length) {
for (let headerIndex = 0; headerIndex < headers.length; headerIndex += 1) {
customSchemaBasic[lineNumber] = data[0];
customSchemaFull[lineNumber][headerIndex] = data[headerIndex];
}
if (data.length === headers.length) {
for (let headerIndex = 0; headerIndex < headers.length; headerIndex += 1) {
[customSchemaBasic[lineNumber]] = data;
customSchemaFull[lineNumber][headerIndex] = data[headerIndex];
}
}
});
}
}
const hasCustomSchema = (layout.customfilebool && layout.customfile.length > 4);
const schemaPromise = hasCustomSchema ? readCustomSchema() : Promise.resolve();
await schemaPromise;
// 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)
const transformedProperties = {
@@ -264,22 +262,21 @@ async function initializeTransformed ({ $element, layout, component }) {
dimensionSelectionCounts: dimensionsInformation.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected)
},
styling: {
colors,
customCSV: {
basic: customSchemaBasic,
count: customHeadersCount,
full: customSchemaFull
},
hasCustomFileStyle: layout.customfilebool,
hasCustomFileStyle: Boolean(cubes.design),
headerOptions: {
alignment: getAlignment(layout.HeaderAlign),
colorSchema: colors[`vColLib${layout.HeaderColorSchema}`],
colorSchema: layout.HeaderColorSchema.color,
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersizeheader),
textColor: layout.HeaderTextColorSchema
textColor: layout.HeaderTextColorSchema.color
},
options: {
backgroundColor: colors[`vColLib${layout.ColorSchema}`],
backgroundColorOdd: colors[`vColLib${layout.ColorSchema}P`],
backgroundColor: layout.rowEvenBGColor,
backgroundColorOdd: layout.rowOddBGColor,
color: layout.BodyTextColorSchema,
fontFamily: layout.FontFamily,
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersize),
@@ -299,11 +296,12 @@ async function initializeTransformed ({ $element, layout, component }) {
layout.conceptsemaphore7,
layout.conceptsemaphore9,
layout.conceptsemaphore10
]
],
metricsSpecificFields: layout.metricssemaphore.split(',').map(entry => Number(entry))
},
status: {
critical: layout.metricstatus1,
medium: layout.metricstatus2
critical: layout.metricsstatus1,
medium: layout.metricsstatus2
},
statusColors: {
critical: {
@@ -340,7 +338,6 @@ async function initializeTransformed ({ $element, layout, component }) {
});
}
return transformedProperties;
}

View File

@@ -44,13 +44,20 @@
td,
th {
border: 1px solid #fff;
padding: 5px;
border-collapse: collapse;
padding: 5px !important; // prevent overwriting from single object
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
cursor: default;
> div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: inherit;
}
}
.empty {
@@ -70,13 +77,14 @@
text-align: right;
}
/* This is for wrap text in headers */
// This is for wrap text in headers
.wrapclass25 {
width: 100%;
height: 25px;
height: inherit;
white-space: pre-line;
overflow: hidden;
display: block;
text-overflow: ellipsis;
}
.wrapclass45 {
@@ -101,13 +109,12 @@
width: 100%;
}
/* ***************** */
/* Medium column size */
/* ***************** */
// *****************
// Medium column size
// *****************
.grid-cells {
position: relative;
._cell(70px);
}
@@ -123,11 +130,9 @@
._cell(52px);
}
/* ***************** */
/* Small column size */
/* ***************** */
// *****************
// Small column size
// *****************
.grid-cells-s {
._cell(67px);
}
@@ -144,12 +149,9 @@
._cell(52px);
}
/* ***************** */
/* large column size */
/* ***************** */
// *****************
// Large column size
// *****************
.grid-cells-l {
._cell(82px);
}
@@ -166,9 +168,9 @@
._cell(66px);
}
/* END OF GRID CELLS */
// END OF GRID CELLS
/* First Column */
// First Column
.fdim-cells {
min-width: 230px !Important;
max-width: 230px !Important;
@@ -195,22 +197,11 @@
min-width: 522px;
}
.grid-cells::before {
content: "\00a0";
}
.grid {
height: 50px;
width: 350px;
}
/* popups for headers */
.header-wrapper {
position: absolute;
top: 0;
z-index: 1;
}
.tooltip {
position: fixed !important;
color: rgb(70, 70, 70);
@@ -219,12 +210,6 @@
border: groove;
}
.row-wrapper {
height: calc(~"100% - 97px");
padding: 0;
margin-top: 0;
}
.kpi-table .fdim-cells,
.data-table td {
line-height: 1em !important;
@@ -255,6 +240,10 @@
}
}
.row-wrapper .fdim-cells {
padding-left: 12px;
}
.data-table {
height: 100%;
width: calc(100% - 243px);
@@ -277,9 +266,9 @@
// hide scrollbars
.kpi-table .header-wrapper,
.kpi-table .row-wrapper,
.data-table .header-wrapper,
.data-table .row-wrapper {
.data-table .header-wrapper {
// stylelint-disable-next-line property-no-unknown
scrollbar-width: none;
-ms-overflow-style: none; // IE 10+
-moz-overflow: -moz-scrollbars-none; // Firefox
@@ -287,4 +276,38 @@
display: none; // Safari and Chrome
}
}
.tooltip-wrapper {
min-width: 25px;
position: fixed;
padding: 5px;
padding-top: 8px;
background-color: #404040;
z-index: 100;
pointer-events: none;
border-radius: 5px;
height: 30px;
width: auto;
opacity: 0.9;
text-align: center;
transform: translate(-50%, -110%);
&::after {
content: "";
position: absolute;
bottom: -10px;
left: 50%;
border-width: 10px 10px 0;
border-style: solid;
border-color: #404040 transparent;
margin-left: -10px;
pointer-events: none;
}
> p {
color: #fff;
}
}
}
/* eslint-enable */

View File

@@ -1,13 +1,18 @@
import $ from 'jquery'; // eslint-disable-line id-length
import initializeStore from './store';
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './root.jsx';
import { initializeCubes } from './dataset';
export default async function paint ($element, layout, component) {
const cubes = await initializeCubes({
component,
layout
});
const state = await initializeStore({
$element,
component,
cubes,
layout
});
const editmodeClass = component.inAnalysisState() ? '' : 'edit-mode';
@@ -20,27 +25,4 @@ export default async function paint ($element, layout, component) {
);
ReactDOM.render(jsx, $element[0]);
// TODO: fixing tooltips has a seperate issue, make sure to remove this as part of that issue
$(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).hover(function () {
$(`[tid="${layout.qInfo.qId}"] .tooltip`).delay(500)
.show(0);
$(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).children(`[tid="${layout.qInfo.qId}"] .tooltip`)
.remove();
const element = $(this);
const offset = element.offset();
const toolTip = $('<div class="tooltip"></div>');
toolTip.css({
left: offset.left,
top: offset.top
});
toolTip.text(element.text());
$(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).append(toolTip);
}, () => {
$(`[tid="${layout.qInfo.qId}"] .tooltip`).delay(0)
.hide(0);
});
}

View File

@@ -10,7 +10,8 @@ describe('<Root />', () => {
qlik: {
backendApi: {
selectValues: () => {}
}
},
inEditState: () => {}
},
state
};

View File

@@ -1,9 +1,10 @@
import initializeTransformed from './initialize-transformed';
async function initialize ({ $element, layout, component }) {
async function initialize ({ $element, layout, component, cubes }) {
const transformedProperties = await initializeTransformed({
$element,
component,
cubes,
layout
});

View File

@@ -1,6 +1,5 @@
function StyleBuilder (styling) {
const {
colors,
customCSV,
options
} = styling;
@@ -12,9 +11,13 @@ function StyleBuilder (styling) {
let hasCustomFileStyle = false;
function applyStandardAttributes (rowNumber) {
const isEven = rowNumber % 2 === 0;
style.backgroundColor = isEven ? options.backgroundColor : options.backgroundColorOdd;
style.color = options.color;
const hasBackgroundColor = options.backgroundColor && options.backgroundColor.color;
const hasOddBackgroundColor = options.backgroundColorOdd && options.backgroundColorOdd.color;
if (hasBackgroundColor && hasOddBackgroundColor) {
const isEven = rowNumber % 2 === 0;
style.backgroundColor = isEven ? options.backgroundColor.color : options.backgroundColorOdd.color;
style.color = options.color;
}
style.fontSize = `${13 + options.fontSizeAdjustment}px`;
}
@@ -30,16 +33,7 @@ function StyleBuilder (styling) {
'<bold>': () => { style.fontWeight = 'bold'; },
'<italic>': () => { style.fontStyle = 'italic'; },
'<oblique>': () => { style.fontStyle = 'oblique'; },
// background and comment color
'<dark>': () => applyColor(colors.vColLibDark),
'<night>': () => applyColor(colors.vColLibNight),
'<soft>': () => applyColor(colors.vColLibSoft),
'<red>': () => applyColor(colors.vColLibRed),
'<orange>': () => applyColor(colors.vColLibOrange),
'<violete>': () => applyColor(colors.vColLibViolete),
'<blue>': () => applyColor(colors.vColLibBlue),
'<green>': () => applyColor(colors.vColLibGreen),
// font color TODO: this is a color just like the others, but it applies to text instead.. any way to make it less weird?
// font color
'<white>': () => { style.color = 'white'; },
// font size
'<large>': () => { style.fontSize = `${15 + options.fontSizeAdjustment}px`; },

62
src/tooltip/index.jsx Normal file
View File

@@ -0,0 +1,62 @@
import React from 'react';
import PropTypes from 'prop-types';
const handleCalculateTooltipPosition = (event) => {
const tooltip = document.querySelector('.tooltip-wrapper');
if (!tooltip) {
return;
}
tooltip.style.left = `${event.clientX}px`;
tooltip.style.top = `${event.clientY}px`;
};
class Tooltip extends React.PureComponent {
constructor (props) {
super(props);
this.state = {
showTooltip: false
};
this.handleRenderTooltip = this.handleRenderTooltip.bind(this);
}
handleRenderTooltip () {
const { showTooltip } = this.state;
this.setState({ showTooltip: !showTooltip });
}
render () {
const { children, tooltipText } = this.props;
const { showTooltip } = this.state;
return (
<div
onMouseMove={handleCalculateTooltipPosition}
onMouseOut={this.handleRenderTooltip}
onMouseOver={this.handleRenderTooltip}
>
{children}
{showTooltip
? (
<div
className="tooltip-wrapper"
>
<p>
{tooltipText}
</p>
</div>
) : null}
</div>
);
}
}
Tooltip.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired,
tooltipText: PropTypes.string.isRequired
};
export default Tooltip;

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +0,0 @@
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import jasmineEnzyme from 'jasmine-enzyme';
Enzyme.configure({ adapter: new Adapter() });
beforeEach(() => {
jasmineEnzyme();
});
export * from 'enzyme';

View File

@@ -1,9 +0,0 @@
import React from 'react';
import { mount } from 'test-utilities/enzyme-setup';
export function mountedComponent (Model, Component, props = {}) {
const component = mount((
<Component {...props} />
)).find(Component);
return new Model(component);
}

View File

@@ -1,8 +1,11 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const settings = require('./settings');
const packageJSON = require('./package.json');
const path = require('path');
console.log('Webpack mode:', settings.mode); // eslint-disable-line no-console
const DIST = path.resolve("./dist");
const MODE = process.env.NODE_ENV || 'development';
console.log('Webpack mode:', MODE); // eslint-disable-line no-console
const config = {
devtool: 'source-map',
@@ -13,13 +16,15 @@ const config = {
commonjs: 'jquery',
commonjs2: 'jquery',
root: '_'
},
qlik: {
amd: 'qlik',
commonjs: 'qlik',
commonjs2: 'qlik',
root: '_'
}
},
mode: settings.mode,
// TODO: breaks core-js for some reason
// resolve: {
// extensions: ['js', 'jsx']
// },
mode: MODE,
module: {
rules: [
{
@@ -59,22 +64,11 @@ const config = {
]
},
output: {
filename: `${settings.name}.js`,
filename: `${packageJSON.name}.js`,
libraryTarget: 'amd',
path: settings.buildDestination
path: DIST
},
plugins: [
new CopyWebpackPlugin([
`assets/${settings.name}.qext`,
`assets/${settings.name}.png`,
'assets/wbfolder.wbl',
'resources/Excel.png',
// TODO: remove entries below this line
'resources/Accounts.csv',
'resources/Accounts2.csv',
'resources/QlikLook.csv'
], {}),
new StyleLintPlugin({
files: '**/*.less'
})