From 9c39eb73bbe4814c7ab0f62a87c2daa599afe215 Mon Sep 17 00:00:00 2001 From: Tung Jin Chew Date: Mon, 20 Feb 2017 20:55:31 +0000 Subject: [PATCH 01/28] Squash all the PowerPoint export changes into a single commit for ease of merging, removing all the merged dashboard and dategraph changes. Squashed commit of the following: commit edaa4c91ae49ced69a3625c6ca4a2a3980063e6b Author: Tung Jin Chew Date: Fri Feb 17 18:48:24 2017 +0000 Fix missing import in settings page. commit 9a3305b47dafed09259bcff9290b5bd11db74efd Author: Tung Jin Chew Date: Fri Feb 17 11:31:42 2017 +0000 Use new API signature commit ead0659ff0e65ea5099da09ff0101afa4e17cdc4 Author: Tung Jin Chew Date: Fri Feb 17 09:57:51 2017 +0000 The nullable parameters are now last (though we'd like them in the UI, so we keep the @RequestParam as required) commit 3095c188e47dae78156fd366a727230d341de307 Author: Tung Jin Chew Date: Thu Feb 16 11:13:57 2017 +0000 Correct margin-to-anchorpoint calculations commit 271f7c89c1f6676db2bf1a818e657d0aa1cabe28 Author: Tung Jin Chew Date: Thu Feb 16 10:50:40 2017 +0000 Use the official HPE accent colour commit beb9a163fdef148d9730b09d169ca63e222329f5 Author: Tung Jin Chew Date: Wed Feb 15 19:52:28 2017 +0000 Add an indicator to give some idea of what the margin percentages look like commit 958fa8a8f576bdcb000944502c8678430414f389 Author: Tung Jin Chew Date: Wed Feb 15 15:09:18 2017 +0000 Add i18n message for the invalid-margins error commit 83a0606237cc71803473a403338e1a80a0c64065 Author: Tung Jin Chew Date: Wed Feb 15 15:05:42 2017 +0000 Add button and controller method to download the default sample template. Fix rendering in Firefox. commit 02ebf45543b6cdb27befadc872286b2d0959a47e Author: Tung Jin Chew Date: Wed Feb 15 14:26:29 2017 +0000 Add UI, logic and validation checking for top/bottom/left/right margins on the PowerPoint templates; which is useful if the user wants to reserve space for their own logos etc. on the default master slide. commit 968581a314a0b2805a452584af8c510f60053759 Author: Tung Jin Chew Date: Tue Feb 14 19:27:58 2017 +0000 Show the reason why it's invalid if the template is reported as invalid; hide the message if it's valid. commit f699b2c92e75905db6fecbe6921d0dc20e9048e8 Author: Tung Jin Chew Date: Tue Feb 14 19:11:16 2017 +0000 Add a validate button to the powerpoint widget commit bdb32ef037a8e956d48b90c5b9b32d99792f8039 Author: Tung Jin Chew Date: Tue Feb 14 18:53:40 2017 +0000 Show error validation status on the powerpoint template input when saving commit 3893e483fec8f13bb36b228c781a3d3f12198c74 Author: Tung Jin Chew Date: Tue Feb 14 17:24:28 2017 +0000 Add save-time validation messages for the error when the PowerPoint template is invalid commit 090931454b04d42cd378c4bb820cb4b63ffab5bd Author: Tung Jin Chew Date: Tue Feb 14 16:59:40 2017 +0000 Don't allow saving when the powerpoint is invalid commit 5d405a90cdffe9f6c4cc8444ce6ef8f73a427f74 Author: Tung Jin Chew Date: Tue Feb 14 16:16:08 2017 +0000 Allow user to specify a custom PowerPoint template file. Add UI for this in the settings page. commit 4da3febf485eff57220047a4451a80c789e6f7fa Author: Tung Jin Chew Date: Tue Feb 14 12:01:29 2017 +0000 Fix tests commit 893343883a03eeaeae61d4617d0cb0e49b6f8129 Author: Tung Jin Chew Date: Tue Feb 14 11:56:12 2017 +0000 Cleanup: remove old TODO and reduce changes for simpler merge; delete file which has been superceded. commit bdef27d07ab5118fca9a7c810326e758b9d5dd49 Author: Tung Jin Chew Date: Tue Feb 14 11:30:33 2017 +0000 Use the default autowired object mapper instead of creating a new instance on each request commit 7e5b9ea76985d62debcd303252023ed363924b16 Author: Tung Jin Chew Date: Tue Feb 14 11:30:08 2017 +0000 Default template has been moved to the external package commit dcc0a1967dd51db82011906e13d9e184818ca160 Author: Tung Jin Chew Date: Mon Feb 13 20:09:10 2017 +0000 Distinguish service from implementation commit 1ca5331090bd6563eaea00e0f7958fe58eab31af Author: Tung Jin Chew Date: Mon Feb 13 20:00:19 2017 +0000 It seems it's necessary to include the reports package in directly; otherwise spring-boot doesn't track its transitive dependencies commit 51a2a2fc8831890f487dfdf918905598b46d10bf Author: Tung Jin Chew Date: Mon Feb 13 19:49:26 2017 +0000 Separately package the powerpoint report-generation backend into its own Maven artifact. commit f59b9fb66dee44f514f0c3ef01de84a4ef22b70c Author: Tung Jin Chew Date: Mon Feb 13 18:22:08 2017 +0000 Create a templatesource class so we won't have to include Spring core in our powerpoint package once it's split off commit 03aadf08e7768fa96289fb3e3a268114e3f099c4 Author: Tung Jin Chew Date: Mon Feb 13 18:05:43 2017 +0000 Cleanup: use the load exception instead of exposing ioexceptions/invalidformatexceptions commit 847b552455f2cfd4ee29466e79c28076299fdfd0 Author: Tung Jin Chew Date: Mon Feb 13 17:43:33 2017 +0000 Fix typo; clear system colour instead of preset colour on the line chart template if specified. Didn't affect operation since the actual template doesn't use system colours; but could affect people using other templates in future. commit b8fc101e0b65a18f60a2cc452229ae911b74ea7f Author: Tung Jin Chew Date: Mon Feb 13 15:51:08 2017 +0000 Mark anything that can be static as static, cleanup commit 252682fd8a5a6bf524706abf21252dc14fdcca2e Author: Tung Jin Chew Date: Mon Feb 13 15:34:26 2017 +0000 Use InputStreamSource instead of Resource commit b730cdb75fff46b941976a70fba741f2cce54ef1 Author: Tung Jin Chew Date: Mon Feb 13 15:14:14 2017 +0000 Split the powerpoint code into service implementation and UI controller wrappers; attempting to preserve history commit ecf9b383cacd2d5f75e6f6b0e82fbafdbc5790e6 Author: Tung Jin Chew Date: Mon Feb 13 13:41:16 2017 +0000 Move the data transfer objects and slideshow template to their own package; in preparation for package extraction commit f6deffb2020b66f1465512821277a0ea1441040a Author: Tung Jin Chew Date: Mon Feb 13 11:19:03 2017 +0000 Fix the problem where only two of the three map views get rendered on the 3-map dashboard in Chrome. New fix works fine in Firefox and IE11as well. commit 2ccdf3620d40ccbb7a61c4dcb7422a6659b4b941 Author: Tung Jin Chew Date: Mon Feb 13 10:56:32 2017 +0000 Note about the replace commit 547731a5ea3d0afc074d0d556ff68b2ecd6eaa45 Author: Tung Jin Chew Date: Fri Feb 10 20:15:07 2017 +0000 Work through the node tree to try and get bold / italic / font size properties and linebreak correctly; all of which are lost if we use jQuery's .text() method. Reproduce the bold / italic / font size formatting on the exported PowerPoint. commit d05d6585529fb37c422b59245b616280b40adef5 Author: Tung Jin Chew Date: Fri Feb 10 18:10:07 2017 +0000 The map operation is forced to be async, since thumbnailing the slides is an async operation. This means the Chrome popup blocker will block the new-window download to a _blank window. The downside is that an error will mean losing the current page. commit f1f2125699a1810dc228ed2b2b65c596402fd902 Author: Tung Jin Chew Date: Fri Feb 10 14:00:59 2017 +0000 Enabling CORS on the map view seems to help with downloading multiple maps in parallel. commit 6e7e7fc5e36b53899bd57015e64cf3e3f63aee73 Author: Tung Jin Chew Date: Fri Feb 10 13:31:40 2017 +0000 Allow text with zero margins; or margins without text. Allow customizing font-family and font-size. commit 88bfc8edc7a5dbe774bd16018f920c4a0467c90f Author: Tung Jin Chew Date: Thu Feb 9 18:00:42 2017 +0000 Tweak saved-search-link to correct position commit 31e8bb544da77bb549c350e6c1c5ee08a0c0186f Author: Tung Jin Chew Date: Thu Feb 9 17:11:10 2017 +0000 Make the title part of the SunburstData object, since it looks quite silly on the report to have multiple sunbursts without any title to tell them apart. commit 8601d4bc3286cae6fbd95351c09bc24f4a3a879f Author: Tung Jin Chew Date: Thu Feb 9 16:45:50 2017 +0000 Misc. cleanup commit 3602e9e4e210ed30df7f6edd3a2f831126b65510 Author: Tung Jin Chew Date: Thu Feb 9 16:26:12 2017 +0000 Refactor dategraph so we can draw multiple dategraphs on a single slide, by working with raw XML objects. Allow reports to draw date graphs. Remove old loadTemplate() method. commit 6a2a21d20c8445dfffb6fc52989199e29a4bf3e9 Author: Tung Jin Chew Date: Thu Feb 9 15:55:12 2017 +0000 Ability to composite multiple sunburst charts onto a single report commit eda0b34b60f65e6a065e896e04a292a0009dc484 Author: Tung Jin Chew Date: Thu Feb 9 15:22:06 2017 +0000 Rewrite the sunburst doughnut-chart handling code so it's done via processing raw XML objects; which should allow us to add multiple charts. commit c114ecfdaced0a0f635a3687421b693e8458c9c0 Author: Tung Jin Chew Date: Thu Feb 9 11:25:08 2017 +0000 Make it take 10%, or 70 pixels, whichever is bigger, unless that's more than 50% of the overall space, in which case limit it to 50%. commit ab1a681caea1b89fdc5f9e1ce302084a64a825f9 Author: Tung Jin Chew Date: Thu Feb 9 11:11:20 2017 +0000 Tweak column ratio commit b748bcfd265e5a6ed6a83f12165e76503bd2a4bf Author: Tung Jin Chew Date: Wed Feb 8 21:03:28 2017 +0000 Allocate more space to text and less to the count; for the common case. commit 84c164bd8290d229d6fe04dd6b40ddc0afb246dd Author: Tung Jin Chew Date: Wed Feb 8 20:55:38 2017 +0000 Crop the tables view to the anchor point heights in the table view; to the best that we can (which is pretty limited, since the POI table API doesn't account for word-wrap when estimating row heights). commit c09d439ac340f43db512a81cb62764a64e2bde70 Author: Tung Jin Chew Date: Wed Feb 8 19:47:44 2017 +0000 Add anchor-rectangle bounds support for the list view; refactor it into a reusable method which can be run multiple times on a single slide. Only render its results and sort-by textboxes if they're not null; since the composite view disables it. Add list view rendering support for the report view. Sort the report's children so the chart-based views (sunburst, date graph) are rendered first; since any of the getShape() and createShape() methods run by the others will break things if the chart views subsequently add chart objects directly to the XML. commit 33a296b6635f93a1874938bdf9c20838abd8737d Author: Tung Jin Chew Date: Wed Feb 8 17:05:39 2017 +0000 Switch to an interface. Add 'report' api which allows placing multiple chart objects in different positions (varying levels of support for the different chart components at the moment). commit a1b1e23fa1f53992d0fc79248122fb998a1f390a Author: Tung Jin Chew Date: Wed Feb 8 16:16:29 2017 +0000 Tag the various composable chart elements with an abstract class; give them a consistent naming convention. commit 734180be61ffcadce4f1e462dcc153825c6a9516 Author: Tung Jin Chew Date: Wed Feb 8 16:08:33 2017 +0000 Wrapper objects for topic map map and results view data, in preparation for composite slides commit a0006251ba1787b24907c453858bd86958484a11 Author: Tung Jin Chew Date: Wed Feb 8 11:20:19 2017 +0000 Refactor table slide-drawing code into its own method with defined anchors and horizontal align commit ff7f6a8de01da34a2b9668c5908f79d434520090 Author: Tung Jin Chew Date: Tue Feb 7 20:17:33 2017 +0000 i18n for table view. Use JSON request structure in preparation for multi-element slides. commit 41637f09bd547df0f69f0fa5102c3f151e219fd7 Author: Tung Jin Chew Date: Tue Feb 7 20:16:41 2017 +0000 Cleanup: header year commit 8db3e4ac6e08703576cc90236f0921742a18cd33 Author: Tung Jin Chew Date: Tue Feb 7 19:57:21 2017 +0000 Cleanup: dedupe internal workbook rewrite commit f728910bc4a08f54c3735ab43687924d3825896d Author: Tung Jin Chew Date: Tue Feb 7 19:45:44 2017 +0000 Fix marker offset calculations if the object isn't full-width commit ce24968487b7d5d9152c65524298196e17e6d43c Author: Tung Jin Chew Date: Tue Feb 7 19:31:50 2017 +0000 Refactor map slide-drawing code into its own method with defined anchors and horizontal align-center vertical align-top; in preparation for multi-object charts commit de9997c6afd1958f8584d50b636a1079fad704ab Author: Tung Jin Chew Date: Tue Feb 7 18:44:58 2017 +0000 Refactor topicmap slide-drawing code into its own method with defined anchors; in preparation for multi-object charts commit 6a68c50b8e6a129aa65276b695d423f1971ab5ea Author: Tung Jin Chew Date: Tue Feb 7 18:03:50 2017 +0000 i18n for sunburst pptx export; change data format to take a JSON object in preparation for multi-slide write commit 85f6594a0a7da6cfcb59d90d33b9572d3ee90ede Author: Tung Jin Chew Date: Tue Feb 7 16:34:26 2017 +0000 Use the same base presentation for all powerpoint exports; primarily so we'll inherit the page size settings, but will also give us any other random bits of the slide template. commit 5d1375a85fa23f68d9b267824003fefb6a57116c Author: Tung Jin Chew Date: Mon Feb 6 20:34:32 2017 +0000 Fix encoding issues which were causing e.g. corrupt Unicode characters in the list view and nonsensical rendering of non-breaking-space characters in the date graph; turns out the CharacterEncodingFilter wasn't being called in time on multipart/form-data requests . commit c65e3795f9966e38ae112e37d94847aa4bbe0eb2 Author: Tung Jin Chew Date: Mon Feb 6 18:17:02 2017 +0000 Use a single PowerPoint template containing the graphs instead of multiple templates. commit 880d34ce9969ec1d4a4dc902a266002e7f7226f0 Author: Tung Jin Chew Date: Mon Feb 6 11:33:02 2017 +0000 Open map view in _self, otherwise the Chrome popup blocker will complain (otherwise we'd use _blank so we don't lose our current page in case an error happens). (cherry picked from commit 675b9e8) commit 2763c941950b7f90c47e4a35df11fc067d981c65 Author: Tung Jin Chew Date: Mon Feb 6 11:14:47 2017 +0000 Correct paths so PowerPoint export works when reverse proxied. (cherry picked from commit 7c8b10c) commit c78337475b897dec2223b95e7788772e000915a8 Author: Tung Jin Chew Date: Mon Feb 6 12:03:52 2017 +0000 Fix path to the saved-search-status page when using a reverse proxy. (cherry picked from commit 44fd04e) commit f68d21ee76b87a273a85465b53ea68963d9049e2 Author: Tung Jin Chew Date: Fri Feb 3 21:19:19 2017 +0000 There's encoding issues; the non-breaking-spaces are being parsed incorrectly server side since it's not recognizing the UTF-8 encoding. Workaround for now is to use normal spaces in the PowerPoint. commit cfcfd9cde9cc175021b86fbe12e3f00b43d12dce Author: Tung Jin Chew Date: Thu Feb 2 16:32:57 2017 +0000 Catch runtime exception rather than exception; and just suppress it rather than logging commit ac23b46a2e58c0bb385de22c6a22e4bb08168cf5 Author: Tung Jin Chew Date: Thu Feb 2 16:01:20 2017 +0000 Add highlighting support for the bold text elements tagged with . Coalesce newlines and multiple whitespace in the summary to a single whitespace to match HTML rendering behaviour. Slight refactoring to make things neater. commit 2bb0cd55c43ea6e5f525e7c6bae84f29c762f8b9 Author: Tung Jin Chew Date: Thu Feb 2 15:04:48 2017 +0000 Use relative dates for consistency with the exported UI (admittedly they make less sense if the PowerPoint is viewed later; but it's closer to the normal display) commit e6732f35b3b66d78aeff11c9677d4776e71f76b4 Author: Tung Jin Chew Date: Thu Feb 2 14:56:25 2017 +0000 Use page dimensions from the slides rather than hardcoded values commit 2412d8c1f046e364ef15a6fddcbdb08f500d8bbc Author: Tung Jin Chew Date: Thu Feb 2 14:43:34 2017 +0000 Use paragraphs for different text so we can conditionally import them and apply margins; tweak thumbnail scale factor. commit 90781258753467a11defed96dc450c2d2ad2faab Author: Tung Jin Chew Date: Thu Feb 2 14:13:27 2017 +0000 Add thumbnail support to list PowerPoint export commit dc041d89159cd884edec9ba354d3550cf72a8dfd Author: Tung Jin Chew Date: Thu Feb 2 11:57:47 2017 +0000 We don't need to reserve the margin; we just need enough space for the document. commit b19fec6db7bf2bc2134d86e01a9bc61308dcf3ae Author: Tung Jin Chew Date: Thu Feb 2 11:32:23 2017 +0000 Ensure the parametric graphing buttons only appear if you have the graph view enabled commit 750692f6b3959df2ce76d2fb7f656ea3e9c9ad50 Author: Tung Jin Chew Date: Thu Feb 2 11:01:41 2017 +0000 If the user doesn't have the BI role, hide the powerpoint button, since all export stuff is restricted to BI users. commit baad26319cfc87d7dec11b4dbbd3a85ec4c98da8 Author: Tung Jin Chew Date: Wed Feb 1 19:54:08 2017 +0000 Working multi-page list PowerPoint export ! commit ca57e7a55e9d36a93a982e14f451bad08d65e86a Author: Tung Jin Chew Date: Wed Feb 1 19:01:31 2017 +0000 Cleanup copyright commit 23155e1fe8801037fb7fc7e76b5d67b2764120e4 Author: Tung Jin Chew Date: Wed Feb 1 13:41:27 2017 +0000 The topic map changes have been made into a pull request upstream which Alex has merged; so we can just update to that version and delete the local variants. commit 9b43b72e0e278076bf2bcade40da564aad1cb8d3 Author: Tung Jin Chew Date: Wed Feb 1 12:10:55 2017 +0000 Apply changes suggested by Alex commit 99a6a3de6a568d947e7ccf9f91f84ef7401f7c2b Author: Tung Jin Chew Date: Wed Feb 1 11:47:33 2017 +0000 Update to the new code which is configurable; which has been made in to a pull request commit 1c4e02e46ae4c4f6e560477b214ebb6c11e8ac79 Author: Tung Jin Chew Date: Tue Jan 31 14:05:26 2017 +0000 Actually, we have to find the marker z-index and sort the markers by it; otherwise the markers on the compare-query 'exclusive to saved search / common to both' may not appear in the correct order. commit 84f0eceb19147ba36f404f0261b63e73a25d8f0c Author: Tung Jin Chew Date: Tue Jan 31 12:08:51 2017 +0000 The cluster markers should be pushed to the front of the marker list so that they appear behind the normal markers (they potentially overlap if a cluster marker has been expanded) commit f5919ebc23b8bca28051fbe12a8de9bcc0b9fdc8 Author: Tung Jin Chew Date: Tue Jan 31 11:53:16 2017 +0000 When you click on a cluster marker, it expands its children but also fades out in the UI. Replicate the faded-out state in PowerPoint commit 81ef1c7354079dc541e31e1a27231d2abc35f3e6 Author: Tung Jin Chew Date: Mon Jan 30 18:16:22 2017 +0000 Further tweak topicmap for speed commit 28553e400688b389d328a2f2573e1af2d9037086 Author: Tung Jin Chew Date: Mon Jan 30 14:58:13 2017 +0000 Restrict to only supporting POST on the export methods commit 4c4e758c31b117ef838b2a8567f6a56ad48480ea Author: Tung Jin Chew Date: Mon Jan 30 14:55:07 2017 +0000 It's more efficient to use form-data for these large POST uploads commit 600773260a21e56bac0641b2f6281f9f704544dc Author: Tung Jin Chew Date: Mon Jan 30 14:02:50 2017 +0000 Increase limits to 16MB, since experimenting with random data to create a 1920x1080 PNG gives a 12MB file; so 10MB might not be enough and 16MB seems a sensible upper limit. commit 00eb7edeba502e5f5ff27b6ebd0ef850ff80151b Author: Tung Jin Chew Date: Mon Jan 30 12:45:04 2017 +0000 In addition to the Spring form parsing side, we also have to increase the embedded Tomcat connector's max post size (otherwise it defaults to 2MB, which some map exports can exceed; e.g. when testing in IE11 on a 1600x900 screen) . commit 750963eb81bcb78aa5ba443442b6ee00b762616f Author: Tung Jin Chew Date: Mon Jan 30 11:56:03 2017 +0000 A lossless PNG actually compresses to be smaller than a high quality 1.0 JPEG of similar resolution, which explains why the source map tiles were served as PNG as well. Seems to work in Chrome, Firefox and IE11 commit 481550ff50beb272431d32deddfc392efaf52ef2 Author: Tung Jin Chew Date: Mon Jan 30 11:42:44 2017 +0000 It turns out the reason the images were being truncated when we used image/jpeg with quality 1.0 is because there's a default 524288 length limit for a input field; and there's a bug in Chrome (https://bugs.webkit.org/show_bug.cgi?id=44883) which prevents us increasing this limit. Switch to a textarea instead to work around the limit. We also have to increase the Spring Boot max file input length limit from 1MB since on a high-res screen a high-quality JPEG may take ~2MB; set it at 10MB arbitrarily. commit 691fe00e05f6f0d8c6d595d325f73c56e887de97 Author: Tung Jin Chew Date: Mon Jan 30 10:40:07 2017 +0000 Excessively high quality settings seem to cause problems with the image; only part of it appears. commit a1214e240067b9df43050e93c6c05b1135041af8 Author: Tung Jin Chew Date: Fri Jan 27 19:04:59 2017 +0000 Ask for maximum quality JPEG export (which sadly is still not lossless). We can't export PNG lossless since it contains an alpha layer, and OpenOffice doesn't seem to handle alpha channels commit 139f725c5466126d4968947f0867df90f3aaed0d Author: Tung Jin Chew Date: Fri Jan 27 18:23:46 2017 +0000 Make font colour customizable; since it's black on the normal map but white on the compare-results map commit 90196a500fe1b173d702f6974de3b151a5883ec9 Author: Tung Jin Chew Date: Fri Jan 27 17:56:12 2017 +0000 Use grouped shapes to give the mix of opaque-text-center and semitransparent-larger-circle to match the UI. Set font to be white, since the compare map uses white font. commit aa88e5d29790788c22bbfb7152aa3cdc9afc32b9 Author: Tung Jin Chew Date: Fri Jan 27 17:24:48 2017 +0000 Use appropriate colours for the markers when exporting in comparison map view (there's no way to get the data programatically for the single-point markers, since it's a background image not the literal CSS colour mapping; so we just use a table of CSS class names to image background colours) commit a9a2825762c43a64446eb1c7799d45b8c37f3d7f Author: Tung Jin Chew Date: Fri Jan 27 17:22:02 2017 +0000 Center the title text in PowerPoint commit 6992fbd4184f3ddfaa35481054a3c53be6f2484e Author: Tung Jin Chew Date: Fri Jan 27 16:19:24 2017 +0000 Add powerpoint export to comparison view commit 167b26edcc5f96c4861b0df5e52ceb711510b24f Author: Tung Jin Chew Date: Fri Jan 27 16:18:58 2017 +0000 Fix title, use textarea to allow multiline input. commit ea05709a8b648773461ea18799dc4e26fffd0949 Author: Tung Jin Chew Date: Fri Jan 27 15:49:01 2017 +0000 Disable console logging commit 90d1308a2503f0c89e5f6137caab23a3745e12bc Author: Tung Jin Chew Date: Fri Jan 27 15:45:48 2017 +0000 Move marker code to the map-view.js; which is a better place for it now that we've done away with the requirement for an external marker list commit e7d8c17882072fc2c398e4f2e598fc296403c390 Author: Tung Jin Chew Date: Fri Jan 27 15:30:28 2017 +0000 Refactor to use the map layers directly instead of using references to external markers commit 823a46b5b282ff8ad1172a5339844859cd48b881 Author: Tung Jin Chew Date: Fri Jan 27 15:14:09 2017 +0000 Tweak selector logic in preparation for refactoring into common area commit 58f55592b60e014407bcfd42e89d0e07c5accfab Author: Tung Jin Chew Date: Fri Jan 27 12:37:16 2017 +0000 Tweak the markers so the cluster markers are centered on the target lat-long, whereas the single-point markers have the tail of the marker pointing at the target lat-long. Add semitransparency to the cluster markers matching the original; keeping a lighter semitransparency effect on the single-point marker fill since it looks nice. commit ab68761e8ac406ed35816c970a0b15f5a56ad5c1 Author: Tung Jin Chew Date: Fri Jan 27 12:10:15 2017 +0000 Rotate point markers so they point downwards; give them a dark line color edge, add alpha shading, make their anchor size slightly smaller than the round circle types (which makes them about the same size in the PowerPoint). commit 47c8b066970a31aec2c13735df0050ace79fadc0 Author: Tung Jin Chew Date: Fri Jan 27 12:01:25 2017 +0000 Tweak tolerances so markers are only drawn when outside the bounds if they're within 0.1% of the border. Update comments. commit ffbef8ebba2770f887c90a09953e7653ca9fa255 Author: Tung Jin Chew Date: Fri Jan 27 11:16:31 2017 +0000 Workaround htmlcanvas not working with 3d transforms on Firefox and IE11 by disabling the use of 3d transforms in those browsers; required so that we can correctly export the background image to PowerPoint on those browsers (otherwise it'll have blank areas and/or show the wrong area of the screen). commit a0a3b65a2f8154c6591a6900450a1e52bb079a2a Author: Tung Jin Chew Date: Thu Jan 26 20:06:49 2017 +0000 Create a hyperlink linking back to the slide; which really is just to get us a hover tooltip on the leaf marker so we can put the title text of the document on it. commit 24ea2ddb8245f28c201c457182bf85c1cb54ea85 Author: Tung Jin Chew Date: Thu Jan 26 20:01:54 2017 +0000 Use the map API to compute lat/lon positions directly rather than trying to compute backwards from rendered markers; gives proper locations cross-browser. commit bb6295d39b7fd12ad85e5b2af899b45e929e3b34 Author: Tung Jin Chew Date: Thu Jan 26 18:20:41 2017 +0000 Improve marker rendering commit 79cdfb0c746246fbd91f6fc65066acbf78f88fe4 Author: Tung Jin Chew Date: Thu Jan 26 17:31:16 2017 +0000 Marker export; has some offset/scaling issues due to leaflet's use of translate3d / translate CSS transforms which jquery's .position doesn't seem to account for commit fa45215f2bb1aca188c1df86c0134508a01f7a51 Author: Tung Jin Chew Date: Thu Jan 26 12:48:45 2017 +0000 Re-add the tile layer local proxy commit 1e0d4bd6ea1a421873e65633420b5ac3662e3169 Author: Tung Jin Chew Date: Thu Jan 26 12:40:57 2017 +0000 Partial workaround for rendering issues on IE11; Chrome is fine commit 7aa06f1be5ecc60d9b5ecadc54a0a907885e151e Author: Tung Jin Chew Date: Thu Jan 26 11:53:33 2017 +0000 Working map tile export commit 366560aa9550259c7bfabd1783d0b9b3a2458647 Author: Tung Jin Chew Date: Wed Jan 25 19:49:04 2017 +0000 html2canvas-compatible base64 image proxy; works to get around CORS image tile fetch problems commit d72afd6671c61055261f705eeeedbb435e67bf7e Author: Tung Jin Chew Date: Wed Jan 25 19:44:15 2017 +0000 Tweak topicmap timings for faster rendering commit dbabc5af4dae914758d575e05f4da9bc2a3c21b7 Author: Tung Jin Chew Date: Wed Jan 25 18:50:27 2017 +0000 Implementation of the Express-style proxy ala https://github.com/niklasvh/html2canvas-proxy-nodejs/blob/master/server.js , but it seems to return an object; and the html2canvas code expects a string commit 7961352bf0887c93936529f3ad89f45a1eee11f8 Author: Tung Jin Chew Date: Wed Jan 25 18:21:57 2017 +0000 Add html2canvas library. To get around the CORS problem, we proxy the images locally. commit 0bb13d1b4f83ab5663f8e1047a580c0272f170a7 Author: Tung Jin Chew Date: Tue Jan 24 19:49:17 2017 +0000 Multicolumn table export commit 054c49ac7426d38f0a5d5fa3e167fa49a90a54be Author: Tung Jin Chew Date: Tue Jan 24 19:15:29 2017 +0000 Improved rendering. Button to export tables. commit 98503e64eb99cbc18de826703db3434a6aeee951 Author: Tung Jin Chew Date: Tue Jan 24 18:13:03 2017 +0000 First draft of Powerpoint table export commit 5602acddc42c36ac87c579b652a33d5d91869e2a Author: Tung Jin Chew Date: Tue Jan 24 17:24:48 2017 +0000 Tweak wording; add sanity check commit bafa2385759cb991abb821683010e775c5924bf0 Author: Tung Jin Chew Date: Tue Jan 24 13:42:16 2017 +0000 Fix the embedded spreadsheet info and ranges; previously pressing 'Edit Data' in PowerPoint caused the chart to be messed up commit 6fbfbdccef8f11983725515901d124aef1a20161 Author: Tung Jin Chew Date: Tue Jan 24 13:16:22 2017 +0000 One-dimension sunburst export commit dea09c28c7b649d08a0abcbd80f119a76113b8be Author: Tung Jin Chew Date: Tue Jan 24 12:04:41 2017 +0000 Fix corruption of the template powerpoint; it was due to resource filtering, so we need to configure the maven-resources-plugin not to filter pptx files. Move it back to the templates folder, and use it in the sunburst export step. commit 0e0162e442db1084ad656e001f4a6f1d8acbd163 Author: Tung Jin Chew Date: Tue Jan 24 11:13:47 2017 +0000 First draft of sunburst single-level view export; requires a template file (which currently has to be loaded from the filesystem due to issues with resource fetching returning the wrong number of bytes). commit 196341c45990f97a76676aae09913b7f964f94fd Author: Tung Jin Chew Date: Fri Jan 20 19:38:45 2017 +0000 Match the gradient angle from my original topicmap commit 30090ddc8687387306cecb57100c28af3c497a84 Author: Tung Jin Chew Date: Fri Jan 20 19:32:40 2017 +0000 Gradients with semitransparency! commit f01b6da92ef2f1683df38618724acd695e44eb4c Author: Tung Jin Chew Date: Fri Jan 20 19:17:44 2017 +0000 White font with bold text; requires using the objects directly commit f55bc0a406593604ed8ccf0aa4559d3436c5f6e6 Author: Tung Jin Chew Date: Fri Jan 20 18:50:38 2017 +0000 GET has its own issues; it doesn't work for complicated topicmaps in IE (tested with IE11) since there's a URL GET max length limit. Switch back to POST; we can make the POST request work by appending the temporary form to the page temporarily (if it wasn't appended; it'd work in Chrome but not Firefox or IE11). Now works in all three browsers. commit 4e35423af60cf63c6c2b8ad10d6863179c17dc08 Author: Tung Jin Chew Date: Fri Jan 20 18:13:26 2017 +0000 Cleanup commit 24973897ec0970a3ca1bad7706aee978eedb952c Author: Tung Jin Chew Date: Fri Jan 20 18:12:48 2017 +0000 Firefox doesn't allow the injected
to open a new window; for some reason (presumably security) so we have to use GET instead commit 3066f104948a27ed21d0c277ae0ea74fa8776e27 Author: Tung Jin Chew Date: Fri Jan 20 18:04:29 2017 +0000 Show inline if possible commit f98eb64ba4cff70e1c5c02a074e0e0cd2002ecc0 Author: Tung Jin Chew Date: Fri Jan 20 18:04:17 2017 +0000 Use the modified copy of the topicmap commit 2e56a8570088792372f29950ab0debb45de56515 Author: Tung Jin Chew Date: Fri Jan 20 17:45:45 2017 +0000 Commit copy of published topicmap commit 452241f4629d6046c733cc3223eb247cbe4ac81e Author: Tung Jin Chew Date: Fri Jan 20 17:41:31 2017 +0000 Topicmap rendering as PPTX --- webapp/core/bower.json | 3 +- webapp/core/pom.xml | 5 + .../beanconfiguration/AppConfiguration.java | 13 +- .../core/beanconfiguration/TomcatConfig.java | 8 + .../find/core/configuration/FindConfig.java | 2 + .../core/configuration/PowerPointConfig.java | 159 ++++++++++++++++++ .../PowerPointConfigValidator.java | 24 +++ .../SamplePowerPointController.java | 32 ++++ .../find/core/export/ExportController.java | 147 +++++++++++++++- .../frontend/find/core/map/MapController.java | 92 ++++++++++ webapp/core/src/main/less/app-include.less | 71 ++++++++ .../app/page/abstract-find-settings-page.js | 11 +- .../search/results/entity-topic-map-view.js | 21 +++ .../page/search/results/map-results-view.js | 4 + .../find/app/page/search/results/map-view.js | 132 ++++++++++++++- .../search/results/results-number-view.js | 4 + .../app/page/search/results/results-view.js | 26 +++ .../app/page/search/results/sunburst-view.js | 2 + .../page/search/results/table/table-view.js | 30 ++++ .../js/find/app/page/search/sort-view.js | 7 + .../app/page/settings/powerpoint-widget.js | 107 ++++++++++++ .../static/js/find/app/util/topic-map-view.js | 4 + .../public/static/js/find/nls/root/bundle.js | 16 ++ .../search/results/entity-topic-map-view.html | 1 + .../page/search/results/map-results-view.html | 1 + .../app/page/search/results/results-view.html | 1 + .../app/page/settings/powerpoint-widget.html | 25 +++ .../leaflet.notransform.js | 7 + .../main/public/static/js/require-config.js | 7 +- .../core/export/ExportControllerTest.java | 3 + webapp/hod/pom.xml | 5 + .../find/hod/configuration/HodFindConfig.java | 7 + .../find/hod/export/HodExportController.java | 9 +- .../js/find/app/page/find-settings-page.js | 13 +- .../src/main/resources/application.properties | 3 + .../resources/custom-application.properties | 4 +- .../main/resources/defaultHodConfigFile.json | 3 + .../hod/export/HodExportControllerTest.java | 6 +- webapp/idol/pom.xml | 7 +- .../idol/configuration/IdolFindConfig.java | 7 + .../idol/export/IdolExportController.java | 9 +- .../js/find/app/page/find-settings-page.js | 11 +- .../app/page/search/results/comparison-map.js | 9 + .../comparison/map-comparison-view.html | 2 +- .../src/main/resources/application.properties | 3 + .../resources/custom-application.properties | 2 + .../main/resources/defaultIdolConfigFile.json | 3 + .../idol/export/IdolExportControllerTest.java | 6 +- webapp/pom.xml | 1 + 49 files changed, 1054 insertions(+), 21 deletions(-) create mode 100644 webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfig.java create mode 100644 webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfigValidator.java create mode 100644 webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/SamplePowerPointController.java create mode 100644 webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/map/MapController.java create mode 100644 webapp/core/src/main/public/static/js/find/app/page/settings/powerpoint-widget.js create mode 100644 webapp/core/src/main/public/static/js/find/templates/app/page/settings/powerpoint-widget.html create mode 100644 webapp/core/src/main/public/static/js/leaflet.notransform/leaflet.notransform.js diff --git a/webapp/core/bower.json b/webapp/core/bower.json index 098f62e419..582d468f0b 100644 --- a/webapp/core/bower.json +++ b/webapp/core/bower.json @@ -54,7 +54,8 @@ "datatables.net-fixedcolumns-bs": "3.2.2", "hp-autonomy-about-page": "0.3.0", "bootstrap": "3.3.7", - "moment-timezone": "0.5.11" + "moment-timezone": "0.5.11", + "html2canvas": "~0.4.1" }, "devDependencies": { "hp-autonomy-js-testing-utils": "2.2.0", diff --git a/webapp/core/pom.xml b/webapp/core/pom.xml index 4fa9528c15..104a5cbbdd 100644 --- a/webapp/core/pom.xml +++ b/webapp/core/pom.xml @@ -138,6 +138,11 @@ haven-search-components-core ${haven.search.components.version} + + com.hp.autonomy.frontend.reports.powerpoint + powerpoint-report + ${powerpoint.report.version} + org.springframework.boot diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/AppConfiguration.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/AppConfiguration.java index 67c2b79485..457b64ba4f 100644 --- a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/AppConfiguration.java +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/AppConfiguration.java @@ -19,6 +19,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.LocaleResolver; @@ -65,12 +67,19 @@ public EmbeddedServletContainerCustomizer containerCustomizer() { * since that's apparently what the servlet spec specifies, * It's required despite URIEncoding="UTF-8" on the connector since that only works on GET parameters. * Jetty doesn't have this problem, it seems to use UTF-8 as the default. + * It also has to be a FilterRegistrationBean and be explicitly marked HIGHEST-PRECEDENCE otherwise it'll have no + * effect if other filters run getParameter() before this filter is called. */ @Bean - public CharacterEncodingFilter characterEncodingFilter() { + @Order(Ordered.HIGHEST_PRECEDENCE) + public FilterRegistrationBean characterEncodingFilter() { final CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); - return characterEncodingFilter; + + final FilterRegistrationBean frb = new FilterRegistrationBean(characterEncodingFilter); + frb.addUrlPatterns("/*"); + + return frb; } @Bean diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/TomcatConfig.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/TomcatConfig.java index c65053c7dc..fc9184930e 100644 --- a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/TomcatConfig.java +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/beanconfiguration/TomcatConfig.java @@ -25,6 +25,9 @@ public class TomcatConfig { @Value("${server.tomcat.resources.max-cache-kb}") private long webResourcesCacheSize; + @Value("${server.tomcat.connector.max-post-size}") + private int connectorMaxPostSize; + @Bean public EmbeddedServletContainerFactory servletContainer() { final TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); @@ -40,6 +43,10 @@ public EmbeddedServletContainerFactory servletContainer() { context.setResources(resources); }); + tomcat.addConnectorCustomizers(connector -> { + connector.setMaxPostSize(connectorMaxPostSize); + }); + return tomcat; } @@ -47,6 +54,7 @@ private Connector createAjpConnector() { final Connector connector = new Connector("AJP/1.3"); connector.setPort(ajpPort); connector.setAttribute("tomcatAuthentication", false); + connector.setMaxPostSize(connectorMaxPostSize); return connector; } } diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/FindConfig.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/FindConfig.java index 310e9f5e89..b475996f0f 100644 --- a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/FindConfig.java +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/FindConfig.java @@ -31,4 +31,6 @@ public interface FindConfig, B extends FindConfigBuil Integer getTopicMapMaxResults(); B toBuilder(); + + PowerPointConfig getPowerPoint(); } diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfig.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfig.java new file mode 100644 index 0000000000..5ef5058694 --- /dev/null +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfig.java @@ -0,0 +1,159 @@ +/* + * Copyright 2015-2017 Hewlett-Packard Enterprise Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.core.configuration; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.hp.autonomy.frontend.configuration.ConfigException; +import com.hp.autonomy.frontend.configuration.validation.OptionalConfigurationComponent; +import com.hp.autonomy.frontend.configuration.validation.ValidationResult; +import com.hp.autonomy.frontend.reports.powerpoint.PowerPointServiceImpl; +import com.hp.autonomy.frontend.reports.powerpoint.TemplateLoadException; +import com.hp.autonomy.frontend.reports.powerpoint.TemplateSettingsSource; +import com.hp.autonomy.frontend.reports.powerpoint.dto.Anchor; +import java.io.File; +import java.io.FileInputStream; +import lombok.Data; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.apache.commons.lang.StringUtils; + +import static com.hp.autonomy.frontend.find.core.configuration.PowerPointConfig.Validation.INVALID_MARGINS; + +@JsonDeserialize(builder = PowerPointConfig.Builder.class) +@Data +public class PowerPointConfig implements OptionalConfigurationComponent { + private final String templateFile; + + private final Double marginTop, marginLeft, marginRight, marginBottom; + + private PowerPointConfig(final Builder builder) { + templateFile = builder.templateFile; + marginTop = builder.marginTop; + marginLeft = builder.marginLeft; + marginRight = builder.marginRight; + marginBottom = builder.marginBottom; + } + + @Override + public PowerPointConfig merge(final PowerPointConfig savedSearchConfig) { + return savedSearchConfig != null ? + new PowerPointConfig.Builder() + .setTemplateFile(templateFile == null ? savedSearchConfig.templateFile : templateFile) + .setMarginTop(marginTop == null ? savedSearchConfig.marginTop : marginTop) + .setMarginLeft(marginLeft == null ? savedSearchConfig.marginLeft : marginLeft) + .setMarginRight(marginRight == null ? savedSearchConfig.marginRight : marginRight) + .setMarginBottom(marginBottom == null ? savedSearchConfig.marginBottom : marginBottom) + .build() + : this; + } + + @Override + public void basicValidate(final String section) throws ConfigException { + final ValidationResult result = validate(); + + if (!result.isValid()) { + throw new ConfigException(section, String.valueOf(result.getData())); + } + } + + @JsonIgnore + public Anchor getAnchor() { + final Anchor anchor = new Anchor(); + + final double tmpMarginTop = marginTop == null ? 0 : marginTop; + final double tmpMarginLeft = marginLeft == null ? 0 : marginLeft; + final double tmpMarginRight = marginRight == null ? 0 : marginRight; + final double tmpMarginBottom = marginBottom == null ? 0 : marginBottom; + + anchor.setX(tmpMarginLeft); + anchor.setY(tmpMarginTop); + anchor.setWidth(1 - tmpMarginLeft - tmpMarginRight); + anchor.setHeight(1 - tmpMarginTop - tmpMarginBottom); + + return anchor; + } + + public ValidationResult validate() { + if(rangeIsInvalid(marginTop)) { + return new ValidationResult<>(false, INVALID_MARGINS); + } + if(rangeIsInvalid(marginLeft)) { + return new ValidationResult<>(false, INVALID_MARGINS); + } + if(rangeIsInvalid(marginRight)) { + return new ValidationResult<>(false, INVALID_MARGINS); + } + if(rangeIsInvalid(marginBottom)) { + return new ValidationResult<>(false, INVALID_MARGINS); + } + if(differenceIsInvalid(marginLeft, marginRight)) { + return new ValidationResult<>(false, INVALID_MARGINS); + } + if(differenceIsInvalid(marginTop, marginBottom)) { + return new ValidationResult<>(false, INVALID_MARGINS); + } + + if(StringUtils.isNotBlank(templateFile)) { + final File file = new File(templateFile); + + if (!file.exists()) { + return new ValidationResult<>(false, Validation.TEMPLATE_FILE_NOT_FOUND); + } + + final PowerPointServiceImpl service = new PowerPointServiceImpl(() -> new FileInputStream(file), TemplateSettingsSource.DEFAULT); + + try { + service.validateTemplate(); + } + catch(TemplateLoadException e) { + return new ValidationResult<>(false, Validation.TEMPLATE_INVALID); + } + } + + return new ValidationResult<>(true, null); + } + + private boolean differenceIsInvalid(final Double min, final Double max) { + double realMin = min == null ? 0 : min; + double realMax = max == null ? 0 : max; + if (1 - realMin - realMax <= 0) { + return true; + } + return false; + } + + private static boolean rangeIsInvalid(final Double value) { + return value != null && (value > 1 || value < 0); + } + + @Override + public Boolean getEnabled() { + return true; + } + + @Setter + @Accessors(chain = true) + @JsonPOJOBuilder(withPrefix = "set") + public static class Builder { + private String templateFile; + private Double marginTop, marginLeft, marginRight, marginBottom; + + public PowerPointConfig build() { + return new PowerPointConfig(this); + } + } + + public enum Validation { + TEMPLATE_FILE_NOT_FOUND, + TEMPLATE_INVALID, + INVALID_MARGINS; + + private Validation() { + } + } +} diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfigValidator.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfigValidator.java new file mode 100644 index 0000000000..99fb23afcd --- /dev/null +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/PowerPointConfigValidator.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2017 Hewlett-Packard Enterprise Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.core.configuration; + +import com.hp.autonomy.frontend.configuration.ConfigException; +import com.hp.autonomy.frontend.configuration.validation.ValidationResult; +import com.hp.autonomy.frontend.configuration.validation.Validator; +import org.springframework.stereotype.Component; + +@Component +public class PowerPointConfigValidator implements Validator { + @Override + public ValidationResult validate(final PowerPointConfig config) { + return config.validate(); + } + + @Override + public Class getSupportedClass() { + return PowerPointConfig.class; + } +} diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/SamplePowerPointController.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/SamplePowerPointController.java new file mode 100644 index 0000000000..95f46a531d --- /dev/null +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/configuration/SamplePowerPointController.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Hewlett-Packard Enterprise Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.core.configuration; + +import com.hp.autonomy.frontend.reports.powerpoint.TemplateSource; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping({"/api/admin/config", "/api/config/config"}) +public class SamplePowerPointController { + + @RequestMapping(value = "/template.pptx", method = RequestMethod.GET) + public HttpEntity template() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IOUtils.copyLarge(TemplateSource.DEFAULT.getInputStream(), baos); + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.presentationml.presentation")); + headers.set("Content-Disposition", "inline; filename=template.pptx"); + return new HttpEntity<>(baos.toByteArray(), headers); + } +} diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/export/ExportController.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/export/ExportController.java index c14d389065..bc12a28066 100644 --- a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/export/ExportController.java +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/export/ExportController.java @@ -5,11 +5,32 @@ package com.hp.autonomy.frontend.find.core.export; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.core.configuration.FindConfig; +import com.hp.autonomy.frontend.find.core.configuration.PowerPointConfig; import com.hp.autonomy.frontend.find.core.web.ControllerUtils; import com.hp.autonomy.frontend.find.core.web.ErrorModelAndViewInfo; import com.hp.autonomy.frontend.find.core.web.RequestMapper; +import com.hp.autonomy.frontend.reports.powerpoint.PowerPointService; +import com.hp.autonomy.frontend.reports.powerpoint.PowerPointServiceImpl; +import com.hp.autonomy.frontend.reports.powerpoint.TemplateLoadException; +import com.hp.autonomy.frontend.reports.powerpoint.TemplateSettings; +import com.hp.autonomy.frontend.reports.powerpoint.TemplateSettingsSource; +import com.hp.autonomy.frontend.reports.powerpoint.TemplateSource; +import com.hp.autonomy.frontend.reports.powerpoint.dto.DategraphData; +import com.hp.autonomy.frontend.reports.powerpoint.dto.ListData; +import com.hp.autonomy.frontend.reports.powerpoint.dto.MapData; +import com.hp.autonomy.frontend.reports.powerpoint.dto.ReportData; +import com.hp.autonomy.frontend.reports.powerpoint.dto.SunburstData; +import com.hp.autonomy.frontend.reports.powerpoint.dto.TableData; +import com.hp.autonomy.frontend.reports.powerpoint.dto.TopicMapData; import com.hp.autonomy.searchcomponents.core.search.QueryRequest; +import java.io.FileInputStream; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -33,16 +54,47 @@ public abstract class ExportController, E extends Exception> { static final String EXPORT_PATH = "/api/bi/export"; static final String CSV_PATH = "/csv"; + static final String PPT_TOPICMAP_PATH = "/ppt/topicmap"; + static final String PPT_SUNBURST_PATH = "/ppt/sunburst"; + static final String PPT_TABLE_PATH = "/ppt/table"; + static final String PPT_MAP_PATH = "/ppt/map"; + static final String PPT_LIST_PATH = "/ppt/list"; + static final String PPT_DATEGRAPH_PATH = "/ppt/dategraph"; + static final String PPT_REPORT_PATH = "/ppt/report"; static final String SELECTED_EXPORT_FIELDS_PARAM = "selectedFieldIds"; static final String QUERY_REQUEST_PARAM = "queryRequest"; private static final String EXPORT_FILE_NAME = "query-results"; private final RequestMapper requestMapper; private final ControllerUtils controllerUtils; + private final ObjectMapper objectMapper; + + private final PowerPointService pptService; protected ExportController(final RequestMapper requestMapper, - final ControllerUtils controllerUtils) { + final ControllerUtils controllerUtils, + final ObjectMapper objectMapper, + final ConfigService configService) { this.requestMapper = requestMapper; this.controllerUtils = controllerUtils; + this.objectMapper = objectMapper; + + this.pptService = new PowerPointServiceImpl(() -> { + final PowerPointConfig powerPoint = configService.getConfig().getPowerPoint(); + + if (powerPoint != null) { + final String template = powerPoint.getTemplateFile(); + + if (StringUtils.isNotBlank(template)) { + return new FileInputStream(template); + } + } + + return TemplateSource.DEFAULT.getInputStream(); + }, () -> { + final PowerPointConfig powerPoint = configService.getConfig().getPowerPoint(); + + return powerPoint == null ? TemplateSettingsSource.DEFAULT.getSettings() : new TemplateSettings(powerPoint.getAnchor()); + }); } @RequestMapping(value = CSV_PATH, method = RequestMethod.POST) @@ -98,4 +150,95 @@ private ResponseEntity outputStreamToResponseEntity(final ExportFormat e return new ResponseEntity<>(output, headers, HttpStatus.OK); } -} + + @RequestMapping(value = PPT_TOPICMAP_PATH, method = RequestMethod.POST) + public HttpEntity topicmap( + @RequestParam("data") final String topicMapStr + ) throws IOException, TemplateLoadException { + final TopicMapData data = objectMapper.readValue(topicMapStr, TopicMapData.class); + return writePPT(pptService.topicmap(data), "topicmap.pptx"); + } + + private HttpEntity writePPT(final XMLSlideShow ppt, final String filename) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ppt.write(baos); + ppt.close(); + + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.presentationml.presentation")); + headers.set("Content-Disposition", "inline; filename=" + filename); + return new HttpEntity<>(baos.toByteArray(), headers); + } + + @RequestMapping(value = PPT_SUNBURST_PATH, method = RequestMethod.POST) + public HttpEntity sunburst( + @RequestParam("data") final String dataStr + ) throws IOException, TemplateLoadException { + final SunburstData data = objectMapper.readValue(dataStr, SunburstData.class); + + final XMLSlideShow ppt = pptService.sunburst(data); + + return writePPT(ppt, "sunburst.pptx"); + } + + @RequestMapping(value = PPT_TABLE_PATH, method = RequestMethod.POST) + public HttpEntity table( + @RequestParam("title") final String title, + @RequestParam("data") final String dataStr + + ) throws IOException, TemplateLoadException { + final TableData tableData = objectMapper.readValue(dataStr, TableData.class); + + final XMLSlideShow ppt = pptService.table(tableData, title); + + return writePPT(ppt, "table.pptx"); + } + + @RequestMapping(value = PPT_MAP_PATH, method = RequestMethod.POST) + public HttpEntity map( + @RequestParam("title") final String title, + @RequestParam("data") final String markerStr + ) throws IOException, TemplateLoadException { + final MapData map = objectMapper.readValue(markerStr, MapData.class); + + final XMLSlideShow ppt = pptService.map(map, title); + + return writePPT(ppt, "map.pptx"); + } + + @RequestMapping(value = PPT_LIST_PATH, method = RequestMethod.POST) + public HttpEntity list( + @RequestParam("results") final String results, + @RequestParam("sortBy") final String sortBy, + @RequestParam("data") final String docsStr + ) throws IOException, TemplateLoadException { + final ListData documentList = objectMapper.readValue(docsStr, ListData.class); + + final XMLSlideShow ppt = pptService.list(documentList, results, sortBy); + + return writePPT(ppt, "list.pptx"); + } + + @RequestMapping(value = PPT_DATEGRAPH_PATH, method = RequestMethod.POST) + public HttpEntity graph( + @RequestParam("data") final String dataStr + ) throws IOException, TemplateLoadException { + final DategraphData data = objectMapper.readValue(dataStr, DategraphData.class); + + final XMLSlideShow ppt = pptService.graph(data); + + return writePPT(ppt, "dategraph.pptx"); + } + + @RequestMapping(value = PPT_REPORT_PATH, method = RequestMethod.POST) + public HttpEntity report( + @RequestParam("data") final String dataStr + ) throws IOException, TemplateLoadException { + final ReportData report = objectMapper.readValue(dataStr, ReportData.class); + + final XMLSlideShow ppt = pptService.report(report); + + return writePPT(ppt, "report.pptx"); + } + +} \ No newline at end of file diff --git a/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/map/MapController.java b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/map/MapController.java new file mode 100644 index 0000000000..68d7247cab --- /dev/null +++ b/webapp/core/src/main/java/com/hp/autonomy/frontend/find/core/map/MapController.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 Hewlett-Packard Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +package com.hp.autonomy.frontend.find.core.map; + +import com.hp.autonomy.frontend.configuration.ConfigService; +import com.hp.autonomy.frontend.find.core.configuration.FindConfig; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping(MapController.MAP_PATH) +public class MapController { + public static final String MAP_PATH = "/api/public/map"; + public static final String TILE_PATH = "/tile"; + public static final String PROXY_PATH = "/proxy"; + + @Autowired + public MapController(final ConfigService configService) { + this.configService = configService; + } + + private final ConfigService configService; + + @RequestMapping(value = TILE_PATH, method = RequestMethod.GET) + public ResponseEntity tile( + @RequestParam("x") final String x, + @RequestParam("y") final String y, + @RequestParam("z") final String z + ) throws IOException { + final String tileUrlTemplate = configService.getConfig().getMap().getTileUrlTemplate(); + final String url = tileUrlTemplate.replace("{x}", x).replace("{y}", y).replace("{z}", z); + + final URLConnection urlConnection = new URL(url).openConnection(); + final String contentType = urlConnection.getContentType(); + try(final InputStream is = urlConnection.getInputStream(); final ByteArrayOutputStream baos = new ByteArrayOutputStream();) { + IOUtils.copyLarge(is, baos); + + return ResponseEntity + .ok() + .contentType(MediaType.parseMediaType(contentType)) + .body(baos.toByteArray()); + } + } + + @RequestMapping(value = PROXY_PATH, method = RequestMethod.GET) + @ResponseBody + public String tile( + @RequestParam("url") final String url, + @RequestParam("callback") final String callback + ) { + if(!callback.matches("\\w+")) { + throw new IllegalArgumentException("Invalid callback function name"); + } + + try { + final String tileUrlTemplate = configService.getConfig().getMap().getTileUrlTemplate(); + + final URL target = new URL(url), validate = new URL(tileUrlTemplate); + + if (!validate.getProtocol().equals(target.getProtocol()) || !validate.getHost().equals(target.getHost()) || validate.getPort() != target.getPort()) { + throw new IllegalArgumentException("We only allow proxying to the tile server"); + } + + final URLConnection urlConnection = target.openConnection(); + final String contentType = urlConnection.getContentType(); + try (final InputStream is = urlConnection.getInputStream(); final ByteArrayOutputStream baos = new ByteArrayOutputStream();) { + IOUtils.copyLarge(is, baos); + + return callback + "(\"data:" + contentType + ";base64," + new String(Base64.encodeBase64(baos.toByteArray(), false, false)) + "\")"; + } + } + catch(IOException e) { + return callback + "(\"error:Application error\")"; + } + } +} \ No newline at end of file diff --git a/webapp/core/src/main/less/app-include.less b/webapp/core/src/main/less/app-include.less index 5e0d931cc5..2aa0e47dd6 100644 --- a/webapp/core/src/main/less/app-include.less +++ b/webapp/core/src/main/less/app-include.less @@ -2254,3 +2254,74 @@ h4.similar-dates-message { height: @lgHeightScreenTopicMapHeight; } } + +.results-view-pptx { + margin-left: 5px; +} + +.report-pptx { + position: absolute; + z-index: 2; + top: 0; + right: 0; + opacity: 0.1; + transition: 0.5s opacity; +} + +.report-pptx:hover { + opacity: 1; +} + +.powerpoint-margins { + position: relative; + border: 1px lightgray solid; + width: 250px; + height: 140px; + margin-bottom: 5px; +} + +.powerpoint-margins-docbox { + font-size: 54px; + position: absolute; + left: 0; + right: 0; + top: 25%; + bottom: 0; + text-align: center; +} + +.powerpoint-margins > .template-input-margin { + position: absolute; + width: 70px; +} + +.template-input-marginTop { + left: 50%; + top: 2px; + text-align: center; + margin-left: -35px; +} + +.template-input-marginBottom { + left: 50%; + bottom: 2px; + text-align: center; + margin-left: -35px; +} + +.template-input-marginLeft { + left: 2px; + top: 36%; + text-align: right; +} + +.template-input-marginRight { + right: 2px; + top: 36%; + text-align: left; +} + +.powerpoint-margins-indicator { + position:absolute; + border: 1px solid #60798d; +} \ No newline at end of file diff --git a/webapp/core/src/main/public/static/js/find/app/page/abstract-find-settings-page.js b/webapp/core/src/main/public/static/js/find/app/page/abstract-find-settings-page.js index 6275a2abd2..04a38b5532 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/abstract-find-settings-page.js +++ b/webapp/core/src/main/public/static/js/find/app/page/abstract-find-settings-page.js @@ -30,6 +30,7 @@ define([ urlRoot: urlRoot, vent: vent, validateUrl: urlRoot + 'config-validation', + pptxTemplateUrl: urlRoot + 'template.pptx', widgetGroupParent: 'form .row', strings: { @@ -79,6 +80,11 @@ define([ validatePortInvalid: i18n['settings.test.portInvalid'], validateUsernameBlank: i18n['settings.test.usernameBlank'], validateSuccess: i18n['settings.test.ok'], + templateFile: i18n['settings.powerpoint.template.file'], + templateFileDefault: i18n['settings.powerpoint.template.file.default'], + templateSampleDownload: i18n['settings.powerpoint.template.sample.download'], + templateValidate: i18n['settings.powerpoint.template.file.validate'], + templateMargins: i18n['settings.powerpoint.template.margins'], CONNECTION_ERROR: i18n['settings.CONNECTION_ERROR'], FETCH_PORT_ERROR: i18n['settings.FETCH_PORT_ERROR'], FETCH_SERVICE_PORT_ERROR: i18n['settings.FETCH_SERVICE_PORT_ERROR'], @@ -87,7 +93,10 @@ define([ REQUIRED_FIELD_MISSING: i18n['settings.REQUIRED_FIELD_MISSING'], REGULAR_EXPRESSION_MATCH_ERROR: i18n['settings.REGULAR_EXPRESSION_MATCH_ERROR'], SERVICE_AND_INDEX_PORT_ERROR: i18n['settings.SERVICE_AND_INDEX_PORT_ERROR'], - SERVICE_PORT_ERROR: i18n['settings.SERVICE_PORT_ERROR'] + SERVICE_PORT_ERROR: i18n['settings.SERVICE_PORT_ERROR'], + TEMPLATE_FILE_NOT_FOUND: i18n['settings.powerpoint.template.error.TEMPLATE_FILE_NOT_FOUND'], + TEMPLATE_INVALID: i18n['settings.powerpoint.template.error.TEMPLATE_INVALID'], + INVALID_MARGINS: i18n['settings.powerpoint.template.error.INVALID_MARGINS'] }; }, diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/entity-topic-map-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/entity-topic-map-view.js index 9e9af7c297..0288fb870f 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/entity-topic-map-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/entity-topic-map-view.js @@ -46,9 +46,30 @@ define([ var maxResults = event.value; this.model.set('maxResults', maxResults); + }, + 'click .entity-topic-map-pptx': function(evt){ + evt.preventDefault() + + var data = this.exportPPTData(); + + if (data) { + // We need to append the temporary form to the document.body or Firefox and IE11 won't download the file. + // Previously used GET; but IE11 has a limited GET url length and loses data. + var $form = $(''); + $form[0].data.value = JSON.stringify(data) + $form.appendTo(document.body).submit().remove() + } } }, + exportPPTData: function(){ + var paths = this.topicMap.exportPaths(); + + return paths ? { + paths: _.flatten(paths.slice(1).reverse()) + } : null + }, + initialize: function(options) { this.queryState = options.queryState; diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/map-results-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/map-results-view.js index 961edae71a..2e8b29222c 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/map-results-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/map-results-view.js @@ -29,6 +29,10 @@ define([ }, 'click .map-popup-title': function (e) { vent.navigateToDetailRoute(this.documentsCollection.get(e.currentTarget.getAttribute('cid'))); + }, + 'click .map-pptx': function(e){ + e.preventDefault(); + this.mapResultsView.exportPPT('Showing field ' + this.fieldSelectionView.model.get('displayValue')) } }, diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/map-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/map-view.js index 3a39cd76d1..d891a93c05 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/map-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/map-view.js @@ -6,13 +6,20 @@ define([ 'find/app/vent', 'leaflet', 'Leaflet.awesome-markers', - 'leaflet.markercluster' - + 'leaflet.markercluster', + 'html2canvas' ], function (Backbone, _, $, configuration, vent, leaflet) { 'use strict'; var INITIAL_ZOOM = 3; + var leafletMarkerColorMap = { + 'green': '#70ad25', + 'orange': '#f0932f', + 'red': '#d33d2a', + 'blue': '#37a8da' + } + return Backbone.View.extend({ initialize: function (options) { this.addControl = options.addControl || false; @@ -148,6 +155,127 @@ define([ if (this.map) { this.map.remove(); } + }, + + exportPPTData: function() { + var deferred = $.Deferred(); + + var map = this.map, + mapSize = map.getSize(), + $mapEl = $(map.getContainer()), + markers = []; + + function lPad(str) { + return str.length < 2 ? '0' + str : str + } + + function hexColor(str){ + var match; + if (match = /rgba\((\d+),\s*(\d+),\s*(\d+),\s*([0-9.]+)\)/.exec(str)) { + return '#' + lPad(Number(match[1]).toString(16)) + + lPad(Number(match[2]).toString(16)) + + lPad(Number(match[3]).toString(16)) + } + else if (match = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(str)) { + return '#' + lPad(Number(match[1]).toString(16)) + + lPad(Number(match[2]).toString(16)) + + lPad(Number(match[3]).toString(16)) + } + return str + } + + map.eachLayer(function(layer){ + if (layer instanceof leaflet.Marker) { + var pos = map.latLngToContainerPoint(layer.getLatLng()) + + var isCluster = layer.getChildCount + + var xFraction = pos.x / mapSize.x; + var yFraction = pos.y / mapSize.y; + var tolerance = 0.001; + + if (xFraction > -tolerance && xFraction < 1 + tolerance && yFraction > -tolerance && yFraction < 1 + tolerance) { + var fontColor = '#000000', + color = '#37a8da', + match, + fade = false, + text = ''; + + var $iconEl = $(layer._icon); + if (isCluster) { + color = hexColor($iconEl.css('background-color')); + fontColor = hexColor($iconEl.children('div').css('color')) + fade = +$iconEl.css('opacity') < 1 + text = layer.getChildCount(); + } else if (match=/awesome-marker-icon-(\w+)/.exec(layer._icon.classList)) { + if (leafletMarkerColorMap.hasOwnProperty(match[1])) { + color = leafletMarkerColorMap[match[1]] + } + + var popup = layer.getPopup(); + if (popup && popup._content) { + text = $(popup._content).find('.map-popup-title').text() + } + } + + var marker = { + x: xFraction, + y: yFraction, + text: text, + cluster: !!isCluster, + color: color, + fontColor: fontColor, + fade: fade, + z: +$iconEl.css('z-index') + }; + + markers.push(marker) + } + } + }) + + var $objs = $mapEl.find('.leaflet-objects-pane').addClass('hide') + + html2canvas($mapEl, { + // This seems to avoid issues with IE11 only rendering a small portion of the map the size of the window + // If width and height are undefined, Firefox sometimes renders black areas. + // If width and height are equal to the $mapEl.width()/height(), then Chrome has the same problem as IE11. + width: $(document).width(), + height: $(document).height(), + proxy: 'api/public/map/proxy', + useCORS: true, + onrendered: function(canvas) { + $objs.removeClass('hide') + + deferred.resolve({ + // ask for lossless PNG image + image: canvas.toDataURL('image/png'), + markers: markers.sort(function(a, b){ + return a.z - b.z; + }).map(function(a){ + return _.omit(a, 'z') + }) + }); + } + }); + + return deferred.promise(); + }, + + exportPPT: function(title){ + this.exportPPTData().done(function(data){ + // We use a textarea for the title so we can have newlines, and a textarea for the image to work + // around a hard 524288 limit imposed by a WebKit bug (affects Chrome 55). + // See https://bugs.webkit.org/show_bug.cgi?id=44883 + // We open in _self (despite the chance of having errors) since otherwise the popup blocker + /// will block it, since it's a javascript object which doesn't originate directly from a user event. + var $form = $('
'); + $form[0].title.value = title + + $form[0].data.value = JSON.stringify(data) + + $form.appendTo(document.body).submit().remove() + }) } }); }); diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/results-number-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/results-number-view.js index 62d80ea328..bab51d3895 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/results-number-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/results-number-view.js @@ -39,6 +39,10 @@ define([ this.$totalNumber.text(this.documentsCollection.totalResults || 0); this.$firstNumber.text(this.documentsCollection.length ? 1 : 0); } + }, + + getText: function() { + return $.trim(this.$el.text().replace(/\s+/g, ' ')); } }); }); diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/results-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/results-view.js index d931731a25..effc5f7321 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/results-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/results-view.js @@ -70,6 +70,28 @@ define([ documentModel = this.promotionsCollection.get(cid); } vent.navigateToSuggestRoute(documentModel); + }, + 'click .results-view-pptx': function(evt) { + evt.preventDefault(); + + var $form = $('
'); + + $form[0].sortBy.value = this.sortView.getText(); + $form[0].results.value = this.resultsNumberView.getText(); + + $form[0].data.value = JSON.stringify({ + docs: this.documentsCollection.map(function(model){ + return { + title: model.get('title'), + date: model.has('date') ? model.get('date').fromNow() : '', + ref: model.get('reference'), + summary: model.get('summary'), + thumbnail: model.get('thumbnail') + } + }) + }) + + $form.appendTo(document.body).submit().remove() } }; @@ -152,6 +174,10 @@ define([ this.sortView.setElement(this.$('.sort-container')).render(); this.resultsNumberView.setElement(this.$('.results-number-container')).render(); + if (!_.contains(configuration().roles, 'ROLE_BI')) { + this.$('.results-view-pptx').addClass('hide'); + } + if(this.questionsView) { this.questionsView.setElement(this.$('.main-results-content .answered-questions')).render(); } diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js index 0770560fc4..4ad8ac3efd 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js @@ -158,6 +158,8 @@ define([ render: function() { ParametricResultsView.prototype.render.apply(this); + this.$('.col-md-12').prepend(' PPTX'); + this.$content.addClass('sunburst'); } }); diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js index 450213f704..f93a109eed 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js @@ -43,6 +43,36 @@ define([ render: function() { ParametricResultsView.prototype.render.apply(this); + this.$('.col-md-12').prepend(' PPTX'); + + this.$el.on('click', '.table-pptx', _.bind(function(evt){ + evt.preventDefault(); + + var $form = $('
'); + $form[0].title.value = i18n['search.resultsView.table.breakdown.by'](this.fieldsCollection.at(0).get('displayValue')); + + var rows = this.$table.find('tr'), nCols = 0; + + var cells = []; + + rows.each(function(idx, el){ + var tds = $(el).find('th,td'); + nCols = tds.length; + + tds.each(function (idx, el) { + cells.push($(el).text()); + }) + }); + + $form[0].data.value = JSON.stringify({ + rows: rows.length, + cols: nCols, + cells: cells + }); + + $form.appendTo(document.body).submit().remove(); + }, this)) + this.$content.html(this.tableTemplate()); this.$table = this.$('table'); diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/sort-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/sort-view.js index dbafcb4770..2f5cdcae30 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/sort-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/sort-view.js @@ -42,6 +42,13 @@ define([ if(this.$currentSort) { this.$currentSort.text(i18n['search.resultsSort.' + this.queryModel.get('sort')]); } + }, + + getText: function() { + if (this.$currentSort) { + return i18n['search.resultsSort'] + ' ' + i18n['search.resultsSort.' + this.queryModel.get('sort')]; + } + return ''; } }); }); diff --git a/webapp/core/src/main/public/static/js/find/app/page/settings/powerpoint-widget.js b/webapp/core/src/main/public/static/js/find/app/page/settings/powerpoint-widget.js new file mode 100644 index 0000000000..d5807d25fe --- /dev/null +++ b/webapp/core/src/main/public/static/js/find/app/page/settings/powerpoint-widget.js @@ -0,0 +1,107 @@ +/* + * Copyright 2016 Hewlett-Packard Enterprise Development Company, L.P. + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + */ + +define([ + 'jquery', + 'settings/js/widget', + 'text!find/templates/app/page/settings/widget.html', + 'text!find/templates/app/page/settings/powerpoint-widget.html', + 'underscore' +], function($, Widget, widgetTemplate, template, _) { + 'use strict'; + + return Widget.extend({ + widgetTemplate: _.template(widgetTemplate), + template: _.template(template), + + className: 'panel-group', + controlGroupClass: 'form-group', + formControlClass: 'form-control', + errorClass: 'has-error', + successClass: 'has-success', + + events: _.extend({ + 'click button[name=validate]': 'triggerValidation', + 'change .template-input-margin': 'updateMarginIndicator', + 'keyup .template-input-margin': 'updateMarginIndicator' + }, Widget.prototype.events), + + initialize: function(options) { + Widget.prototype.initialize.apply(this, arguments); + this.pptxTemplateUrl = options.pptxTemplateUrl; + }, + + render: function() { + Widget.prototype.render.apply(this, arguments); + + this.$content.html(this.template({ + strings: this.strings, + pptxTemplateUrl: this.pptxTemplateUrl + })); + + this.$templateFile = this.$('.template-file-input'); + this.$validity = this.$('.settings-client-validation'); + + this.$marginLeft = this.$('.template-input-marginLeft'); + this.$marginTop = this.$('.template-input-marginTop'); + this.$marginRight = this.$('.template-input-marginRight'); + this.$marginBottom = this.$('.template-input-marginBottom'); + + this.$marginIndicator = this.$('.powerpoint-margins-indicator'); + }, + + handleValidation: function(config, response) { + if (response.valid) { + this.setValidationFormatting(this.successClass); + this.hideValidationInfo(); + } else { + this.setValidationFormatting(this.errorClass); + this.$validity.text(this.strings[response.data]) + .stop() + .animate({opacity: 1}) + .removeClass('hide'); + } + }, + + triggerValidation: function() { + this.setValidationFormatting('clear'); + this.hideValidationInfo(); + + if (this.validateInputs()) { + this.trigger('validate'); + } + }, + + getConfig: function() { + return { + templateFile: this.$templateFile.val(), + marginLeft: this.$marginLeft.val(), + marginTop: this.$marginTop.val(), + marginRight: this.$marginRight.val(), + marginBottom: this.$marginBottom.val() + } + }, + + updateConfig: function(config) { + if (config) { + this.$templateFile.val(config.templateFile); + this.$marginLeft.val(config.marginLeft); + this.$marginTop.val(config.marginTop); + this.$marginRight.val(config.marginRight); + this.$marginBottom.val(config.marginBottom); + this.updateMarginIndicator(); + } + }, + + updateMarginIndicator: function() { + this.$marginIndicator.css({ + left: 100 * Math.min(1, Math.max(0, this.$marginLeft.val())) + '%', + top: 100 * Math.min(1, Math.max(0, this.$marginTop.val())) + '%', + right: 100 * Math.min(1, Math.max(0, this.$marginRight.val())) + '%', + bottom: 100 * Math.min(1, Math.max(0, this.$marginBottom.val())) + '%' + }) + } + }); +}); diff --git a/webapp/core/src/main/public/static/js/find/app/util/topic-map-view.js b/webapp/core/src/main/public/static/js/find/app/util/topic-map-view.js index 30080b48c9..38bdb490d8 100644 --- a/webapp/core/src/main/public/static/js/find/app/util/topic-map-view.js +++ b/webapp/core/src/main/public/static/js/find/app/util/topic-map-view.js @@ -74,6 +74,10 @@ define([ size: 1.0, children: this.data }); + }, + + exportPaths: function(){ + return this.$el.topicmap('exportPaths'); } }); }); diff --git a/webapp/core/src/main/public/static/js/find/nls/root/bundle.js b/webapp/core/src/main/public/static/js/find/nls/root/bundle.js index 52c010a096..c6edcff1be 100644 --- a/webapp/core/src/main/public/static/js/find/nls/root/bundle.js +++ b/webapp/core/src/main/public/static/js/find/nls/root/bundle.js @@ -191,6 +191,8 @@ define([ 'search.resultsView.sunburst.error.query': 'Error: could not display Sunburst View', 'search.resultsView.sunburst.error.noDependentParametricValues': 'There are too many parametric fields to display in Sunburst View', 'search.resultsView.sunburst.error.noSecondFieldValues': 'There are no documents with values for both fields. Showing results for only first field.', + 'search.resultsView.sunburst.others': 'Others', + 'search.resultsView.sunburst.breakdown.by': 'Breakdown by {0}', 'search.resultsView.map': 'Map', 'search.resultsView.map.show.more': 'Show More', 'search.resultsView.table': 'Table', @@ -205,9 +207,13 @@ define([ 'search.resultsView.table.previous': 'Previous', 'search.resultsView.table.searchInResults': 'Search in Results', 'search.resultsView.table.zeroRecords': 'No matching records found', + 'search.resultsView.table.breakdown.by': 'Breakdown by {0}', 'search.resultsView.amount.shown': 'Showing {0} to {1} of {2} results', 'search.resultsView.amount.shown.no.increment': 'Showing the top {0} results of {1}', 'search.resultsView.amount.shown.no.results': 'There are no results with the location field selected', + 'search.resultsView.dategraph': 'Date', + 'search.resultsView.dategraph.error': 'Failed to load data', + 'search.resultsView.dategraph.noValues': 'No values', 'search.answeredQuestion': 'Answered question', 'search.answeredQuestion.systemName': 'Answered by {0}', 'search.promoted': 'Promoted', @@ -306,6 +312,16 @@ define([ 'settings.password': 'Password', 'settings.password.description': 'Password will be stored encrypted', 'settings.password.redacted': '(redacted)', + 'settings.powerpoint': 'PowerPoint', + 'settings.powerpoint.description': 'A custom two-slide PowerPoint .pptx file can be optionally provided; with a doughnut chart on the first slide and a line chart with a date x-axis and two numeric y-axes on the second slide. To reserve space for your own logos etc. on other visualizations, customize the margins below.', + 'settings.powerpoint.template.file': 'Template File', + 'settings.powerpoint.template.sample.download': 'Download Sample Template', + 'settings.powerpoint.template.file.default': 'default', + 'settings.powerpoint.template.file.validate': 'Test Template', + 'settings.powerpoint.template.error.TEMPLATE_FILE_NOT_FOUND': 'PowerPoint template file not found', + 'settings.powerpoint.template.error.TEMPLATE_INVALID': 'PowerPoint template file has invalid format', + 'settings.powerpoint.template.error.INVALID_MARGINS': 'Invalid margins supplied', + 'settings.powerpoint.template.margins': 'Margins', 'settings.queryManipulation': 'Query Manipulation', 'settings.queryManipulation.blacklist': 'Blacklist Name', 'settings.queryManipulation.description': 'Enable query manipulation with QMS', diff --git a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/entity-topic-map-view.html b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/entity-topic-map-view.html index a97ea7a79a..effd9a20fa 100644 --- a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/entity-topic-map-view.html +++ b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/entity-topic-map-view.html @@ -1,5 +1,6 @@
<% if (showSlider) { %> + PPTX
diff --git a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/map-results-view.html b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/map-results-view.html index 44f8878bbe..33b8a902c5 100644 --- a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/map-results-view.html +++ b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/map-results-view.html @@ -1,4 +1,5 @@ + PPTX

\ No newline at end of file diff --git a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/results-view.html b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/results-view.html index 46d7aacd07..85c9ee98ca 100644 --- a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/results-view.html +++ b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/results-view.html @@ -2,6 +2,7 @@ diff --git a/webapp/core/src/main/public/static/js/find/templates/app/page/settings/powerpoint-widget.html b/webapp/core/src/main/public/static/js/find/templates/app/page/settings/powerpoint-widget.html new file mode 100644 index 0000000000..6f10e462d4 --- /dev/null +++ b/webapp/core/src/main/public/static/js/find/templates/app/page/settings/powerpoint-widget.html @@ -0,0 +1,25 @@ +<% var uid = _.uniqueId('powerpoint') %> + +
+ + + +
+ +
+
+
+ +
+ + + + +
+
+
+ +
+ + <%-strings.templateSampleDownload%> +
\ No newline at end of file diff --git a/webapp/core/src/main/public/static/js/leaflet.notransform/leaflet.notransform.js b/webapp/core/src/main/public/static/js/leaflet.notransform/leaflet.notransform.js new file mode 100644 index 0000000000..9cae46531a --- /dev/null +++ b/webapp/core/src/main/public/static/js/leaflet.notransform/leaflet.notransform.js @@ -0,0 +1,7 @@ +define([], function(){ + // Disables 3d transforms on leaflet, since they interfere with proper operation of html2canvas in Firefox/IE11 + // which can be observed by zooming in on the map, panning, then exporting the map as a PPT + if (!/Chrome/.test(navigator.userAgent)) { + window.L_DISABLE_3D=true + } +}) \ No newline at end of file diff --git a/webapp/core/src/main/public/static/js/require-config.js b/webapp/core/src/main/public/static/js/require-config.js index fd202c9fc4..28ab7389aa 100644 --- a/webapp/core/src/main/public/static/js/require-config.js +++ b/webapp/core/src/main/public/static/js/require-config.js @@ -28,6 +28,7 @@ require.config({ json2: '../bower_components/json/json2', 'login-page': '../bower_components/hp-autonomy-login-page/src', leaflet: '../bower_components/leaflet/dist/leaflet-src', + 'leaflet.notransform': 'leaflet.notransform/leaflet.notransform', 'Leaflet.awesome-markers': '../bower_components/Leaflet.awesome-markers/dist/leaflet.awesome-markers', 'leaflet.markercluster': '../bower_components/leaflet.markercluster/dist/leaflet.markercluster-src', moment: '../bower_components/moment/moment', @@ -39,7 +40,8 @@ require.config({ sunburst: '../bower_components/hp-autonomy-sunburst/src', topicmap: '../bower_components/hp-autonomy-topic-map/src', underscore: '../bower_components/underscore/underscore', - typeahead: '../bower_components/corejs-typeahead/dist/typeahead.jquery' + typeahead: '../bower_components/corejs-typeahead/dist/typeahead.jquery', + 'html2canvas': '../bower_components/html2canvas/build/html2canvas' }, shim: { 'backbone': { @@ -59,6 +61,7 @@ require.config({ exports: '_' }, 'Leaflet.awesome-markers': ['leaflet'], - 'leaflet.markercluster': ['leaflet'] + 'leaflet.markercluster': ['leaflet'], + 'leaflet': ['leaflet.notransform'] } }); diff --git a/webapp/core/src/test/java/com/hp/autonomy/frontend/find/core/export/ExportControllerTest.java b/webapp/core/src/test/java/com/hp/autonomy/frontend/find/core/export/ExportControllerTest.java index db14962025..d5babf4d68 100644 --- a/webapp/core/src/test/java/com/hp/autonomy/frontend/find/core/export/ExportControllerTest.java +++ b/webapp/core/src/test/java/com/hp/autonomy/frontend/find/core/export/ExportControllerTest.java @@ -5,6 +5,7 @@ package com.hp.autonomy.frontend.find.core.export; +import com.fasterxml.jackson.databind.ObjectMapper; import com.hp.autonomy.frontend.find.core.web.ControllerUtils; import com.hp.autonomy.frontend.find.core.web.ErrorModelAndViewInfo; import com.hp.autonomy.frontend.find.core.web.RequestMapper; @@ -33,6 +34,8 @@ public abstract class ExportControllerTest, E extends protected RequestMapper requestMapper; @Mock protected ControllerUtils controllerUtils; + @Mock + protected ObjectMapper objectMapper; private ExportController controller; diff --git a/webapp/hod/pom.xml b/webapp/hod/pom.xml index ccde578741..aabd1b2bd0 100644 --- a/webapp/hod/pom.xml +++ b/webapp/hod/pom.xml @@ -118,6 +118,11 @@ full provided + + com.hp.autonomy.frontend.reports.powerpoint + powerpoint-report + ${powerpoint.report.version} + com.hp.autonomy.hod.redis diff --git a/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/configuration/HodFindConfig.java b/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/configuration/HodFindConfig.java index 63e6905dc9..7bc1a591e1 100644 --- a/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/configuration/HodFindConfig.java +++ b/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/configuration/HodFindConfig.java @@ -17,6 +17,7 @@ import com.hp.autonomy.frontend.find.core.configuration.FindConfig; import com.hp.autonomy.frontend.find.core.configuration.FindConfigBuilder; import com.hp.autonomy.frontend.find.core.configuration.MapConfiguration; +import com.hp.autonomy.frontend.find.core.configuration.PowerPointConfig; import com.hp.autonomy.frontend.find.core.configuration.SavedSearchConfig; import com.hp.autonomy.frontend.find.core.configuration.UiCustomization; import com.hp.autonomy.hod.client.api.authentication.ApiKey; @@ -51,6 +52,7 @@ public class HodFindConfig extends AbstractConfig implements HodS private final UiCustomization uiCustomization; private final Integer minScore; private final Integer topicMapMaxResults; + private final PowerPointConfig powerPoint; @JsonProperty("savedSearches") private final SavedSearchConfig savedSearchConfig; @@ -71,6 +73,7 @@ public HodFindConfig merge(final HodFindConfig config) { .savedSearchConfig(savedSearchConfig == null ? config.savedSearchConfig : savedSearchConfig.merge(config.savedSearchConfig)) .minScore(minScore == null ? config.minScore : minScore) .topicMapMaxResults(topicMapMaxResults == null ? config.topicMapMaxResults : topicMapMaxResults) + .powerPoint(powerPoint == null ? config.powerPoint : powerPoint.merge(config.powerPoint)) .build() : this; } @@ -101,6 +104,10 @@ public void basicValidate(final String section) throws ConfigException { queryManipulation.basicValidate(SECTION); savedSearchConfig.basicValidate(SECTION); + if (powerPoint != null) { + powerPoint.basicValidate("powerPoint"); + } + if (map != null) { map.basicValidate("map"); } diff --git a/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/export/HodExportController.java b/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/export/HodExportController.java index 6bb12a6411..a600d0c05c 100644 --- a/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/export/HodExportController.java +++ b/webapp/hod/src/main/java/com/hp/autonomy/frontend/find/hod/export/HodExportController.java @@ -5,11 +5,14 @@ package com.hp.autonomy.frontend.find.hod.export; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hp.autonomy.frontend.configuration.ConfigService; import com.hp.autonomy.frontend.find.core.export.ExportController; import com.hp.autonomy.frontend.find.core.export.ExportFormat; import com.hp.autonomy.frontend.find.core.export.ExportService; import com.hp.autonomy.frontend.find.core.web.ControllerUtils; import com.hp.autonomy.frontend.find.core.web.RequestMapper; +import com.hp.autonomy.frontend.find.hod.configuration.HodFindConfig; import com.hp.autonomy.hod.client.api.textindex.query.search.Print; import com.hp.autonomy.hod.client.error.HodErrorException; import com.hp.autonomy.searchcomponents.hod.search.HodDocumentsService; @@ -33,8 +36,10 @@ class HodExportController extends ExportController requestMapper, final ControllerUtils controllerUtils, final HodDocumentsService documentsService, - final ExportService exportService) { - super(requestMapper, controllerUtils); + final ExportService exportService, + final ObjectMapper objectMapper, + final ConfigService configService) { + super(requestMapper, controllerUtils, objectMapper, configService); this.documentsService = documentsService; this.exportService = exportService; } diff --git a/webapp/hod/src/main/public/static/js/find/app/page/find-settings-page.js b/webapp/hod/src/main/public/static/js/find/app/page/find-settings-page.js index bac4e8e82e..4f4bdd83eb 100644 --- a/webapp/hod/src/main/public/static/js/find/app/page/find-settings-page.js +++ b/webapp/hod/src/main/public/static/js/find/app/page/find-settings-page.js @@ -4,11 +4,13 @@ */ define([ + 'i18n!find/nls/bundle', 'find/app/page/abstract-find-settings-page', 'find/app/page/settings/iod-widget', + 'find/app/page/settings/powerpoint-widget', 'settings/js/widgets/single-user-widget', 'underscore' -], function(SettingsPage, IodWidget, SingleUserWidget, _) { +], function(i18n, SettingsPage, IodWidget, PowerPointWidget, SingleUserWidget, _) { 'use strict'; return SettingsPage.extend({ @@ -50,6 +52,15 @@ define([ }, title: i18n['settings.adminUser'] }) + ], [ + new PowerPointWidget({ + configItem: 'powerPoint', + description: i18n['settings.powerpoint.description'], + isOpened: true, + title: i18n['settings.powerpoint'], + strings: this.serverStrings(), + pptxTemplateUrl: this.pptxTemplateUrl + }) ] ]; } diff --git a/webapp/hod/src/main/resources/application.properties b/webapp/hod/src/main/resources/application.properties index d9171d84e5..6b27e6eb6a 100644 --- a/webapp/hod/src/main/resources/application.properties +++ b/webapp/hod/src/main/resources/application.properties @@ -32,3 +32,6 @@ spring.jpa.properties.hibernate.default_schema=find spring.jpa.hibernate.ddl-auto=none spring.main.banner-mode=off spring.messages.basename=i18n/hod-errors,i18n/errors +# Increase the default max file upload size from 1MB, since we use large base64-encoded images for map .pptx export +spring.http.multipart.max-file-size=16Mb +spring.http.multipart.max-request-size=16Mb diff --git a/webapp/hod/src/main/resources/custom-application.properties b/webapp/hod/src/main/resources/custom-application.properties index 3f3c8f8dc8..3bdc320c6a 100644 --- a/webapp/hod/src/main/resources/custom-application.properties +++ b/webapp/hod/src/main/resources/custom-application.properties @@ -9,4 +9,6 @@ hp.find.enableBi=true server.reverseProxy=false # Only used if server.reverseProxy is true server.ajp.port=8009 -server.tomcat.resources.max-cache-kb=20480 \ No newline at end of file +server.tomcat.resources.max-cache-kb=20480 +# Increase the connector max post size from 2097152, since we use large base64-encoded images for map .pptx export +server.tomcat.connector.max-post-size=16777216 \ No newline at end of file diff --git a/webapp/hod/src/main/resources/defaultHodConfigFile.json b/webapp/hod/src/main/resources/defaultHodConfigFile.json index 1502d5c483..08ecc89590 100644 --- a/webapp/hod/src/main/resources/defaultHodConfigFile.json +++ b/webapp/hod/src/main/resources/defaultHodConfigFile.json @@ -56,6 +56,9 @@ } ] }, + "powerpoint": { + "templateFile": "" + }, "uiCustomization": { "options": { "directAccessLink": { diff --git a/webapp/hod/src/test/java/com/hp/autonomy/frontend/find/hod/export/HodExportControllerTest.java b/webapp/hod/src/test/java/com/hp/autonomy/frontend/find/hod/export/HodExportControllerTest.java index 0962f1897a..df09134819 100644 --- a/webapp/hod/src/test/java/com/hp/autonomy/frontend/find/hod/export/HodExportControllerTest.java +++ b/webapp/hod/src/test/java/com/hp/autonomy/frontend/find/hod/export/HodExportControllerTest.java @@ -5,8 +5,10 @@ package com.hp.autonomy.frontend.find.hod.export; +import com.hp.autonomy.frontend.configuration.ConfigService; import com.hp.autonomy.frontend.find.core.export.ExportController; import com.hp.autonomy.frontend.find.core.export.ExportControllerTest; +import com.hp.autonomy.frontend.find.hod.configuration.HodFindConfig; import com.hp.autonomy.hod.client.error.HodErrorException; import com.hp.autonomy.searchcomponents.hod.search.HodDocumentsService; import com.hp.autonomy.searchcomponents.hod.search.HodQueryRequest; @@ -29,6 +31,8 @@ public class HodExportControllerTest extends ExportControllerTest hodFindConfig; @Override protected ExportController constructController() throws IOException { @@ -41,7 +45,7 @@ protected ExportController constructControll when(queryRequest.getMaxResults()).thenReturn(Integer.MAX_VALUE); - return new HodExportController(requestMapper, controllerUtils, documentsService, exportService); + return new HodExportController(requestMapper, controllerUtils, documentsService, exportService, objectMapper, hodFindConfig); } @Override diff --git a/webapp/idol/pom.xml b/webapp/idol/pom.xml index 8e75ffe680..c0b6c26661 100644 --- a/webapp/idol/pom.xml +++ b/webapp/idol/pom.xml @@ -123,6 +123,11 @@ haven-search-components-idol ${haven.search.components.version} + + com.hp.autonomy.frontend.reports.powerpoint + powerpoint-report + ${powerpoint.report.version} + commons-io @@ -132,7 +137,7 @@ org.apache.commons commons-collections4 - 4.0 + 4.1 diff --git a/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/configuration/IdolFindConfig.java b/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/configuration/IdolFindConfig.java index 2d676df8ef..70ffe1c532 100644 --- a/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/configuration/IdolFindConfig.java +++ b/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/configuration/IdolFindConfig.java @@ -19,6 +19,7 @@ import com.hp.autonomy.frontend.find.core.configuration.FindConfig; import com.hp.autonomy.frontend.find.core.configuration.FindConfigBuilder; import com.hp.autonomy.frontend.find.core.configuration.MapConfiguration; +import com.hp.autonomy.frontend.find.core.configuration.PowerPointConfig; import com.hp.autonomy.frontend.find.core.configuration.SavedSearchConfig; import com.hp.autonomy.frontend.find.core.configuration.UiCustomization; import com.hp.autonomy.frontend.find.idol.configuration.IdolFindConfig.IdolFindConfigBuilder; @@ -61,6 +62,7 @@ public class IdolFindConfig extends AbstractConfig implements Us private final Integer minScore; private final StatsServerConfig statsServer; private final Integer topicMapMaxResults; + private final PowerPointConfig powerPoint; @JsonIgnore private volatile Map> productMap; @@ -82,6 +84,7 @@ public IdolFindConfig merge(final IdolFindConfig maybeOther) { .minScore(minScore == null ? other.minScore : minScore) .statsServer(statsServer == null ? other.statsServer : statsServer.merge(other.statsServer)) .topicMapMaxResults(topicMapMaxResults == null ? other.topicMapMaxResults : topicMapMaxResults) + .powerPoint(powerPoint == null ? other.powerPoint : powerPoint.merge(other.powerPoint)) .build()) .orElse(this); } @@ -124,6 +127,10 @@ public void basicValidate(final String section) throws ConfigException { content.basicValidate("content"); savedSearchConfig.basicValidate(SECTION); + if (powerPoint != null) { + powerPoint.basicValidate("powerPoint"); + } + if (map != null) { map.basicValidate("map"); } diff --git a/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/export/IdolExportController.java b/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/export/IdolExportController.java index 38d8e54ba0..08d63c312c 100644 --- a/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/export/IdolExportController.java +++ b/webapp/idol/src/main/java/com/hp/autonomy/frontend/find/idol/export/IdolExportController.java @@ -6,11 +6,14 @@ package com.hp.autonomy.frontend.find.idol.export; import com.autonomy.aci.client.services.AciErrorException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hp.autonomy.frontend.configuration.ConfigService; import com.hp.autonomy.frontend.find.core.export.ExportController; import com.hp.autonomy.frontend.find.core.export.ExportFormat; import com.hp.autonomy.frontend.find.core.export.ExportService; import com.hp.autonomy.frontend.find.core.web.ControllerUtils; import com.hp.autonomy.frontend.find.core.web.RequestMapper; +import com.hp.autonomy.frontend.find.idol.configuration.IdolFindConfig; import com.hp.autonomy.searchcomponents.core.search.StateTokenAndResultCount; import com.hp.autonomy.searchcomponents.idol.search.IdolDocumentsService; import com.hp.autonomy.searchcomponents.idol.search.IdolQueryRequest; @@ -31,8 +34,10 @@ class IdolExportController extends ExportController requestMapper, final ControllerUtils controllerUtils, final IdolDocumentsService documentsService, - final ExportService exportService) { - super(requestMapper, controllerUtils); + final ExportService exportService, + final ObjectMapper objectMapper, + final ConfigService configService) { + super(requestMapper, controllerUtils, objectMapper, configService); this.documentsService = documentsService; this.exportService = exportService; } diff --git a/webapp/idol/src/main/public/static/js/find/app/page/find-settings-page.js b/webapp/idol/src/main/public/static/js/find/app/page/find-settings-page.js index 8e78fb2284..117ca0b4c0 100644 --- a/webapp/idol/src/main/public/static/js/find/app/page/find-settings-page.js +++ b/webapp/idol/src/main/public/static/js/find/app/page/find-settings-page.js @@ -15,10 +15,11 @@ define([ 'find/app/page/settings/saved-search-widget', 'find/app/page/settings/stats-server-widget', 'find/app/page/settings/view-widget', + 'find/app/page/settings/powerpoint-widget', 'i18n!find/nls/bundle', 'text!find/templates/app/page/settings/community-widget.html' ], function (_, SettingsPage, AciWidget, AnswerServerWidget, CommunityWidget, MapWidget, MmapWidget, QueryManipulationWidget, - SavedSearchWidget, StatsServerWidget, ViewWidget, i18n, dropdownTemplate) { + SavedSearchWidget, StatsServerWidget, ViewWidget, PowerPointWidget, i18n, dropdownTemplate) { return SettingsPage.extend({ initializeWidgets: function () { @@ -45,6 +46,14 @@ define([ loginTypeLabel: i18n['settings.community.login.type'], validateFailed: i18n['settings.test.failed'] }) + }), + new PowerPointWidget({ + configItem: 'powerPoint', + description: i18n['settings.powerpoint.description'], + isOpened: true, + title: i18n['settings.powerpoint'], + strings: this.serverStrings(), + pptxTemplateUrl: this.pptxTemplateUrl }) ], [ new QueryManipulationWidget({ diff --git a/webapp/idol/src/main/public/static/js/find/idol/app/page/search/results/comparison-map.js b/webapp/idol/src/main/public/static/js/find/idol/app/page/search/results/comparison-map.js index 281ccaac94..cc8fbf9b85 100644 --- a/webapp/idol/src/main/public/static/js/find/idol/app/page/search/results/comparison-map.js +++ b/webapp/idol/src/main/public/static/js/find/idol/app/page/search/results/comparison-map.js @@ -45,6 +45,15 @@ define([ 'click .map-popup-title': function (e) { const allCollections = _.chain(this.comparisons).pluck('collection').pluck('models').flatten().value(); vent.navigateToDetailRoute(_.findWhere(allCollections, {cid: e.currentTarget.getAttribute('cid')})); + }, + 'click .map-pptx': function(e){ + e.preventDefault(); + this.mapView.exportPPT( + '\'' + this.searchModels.first.get('title') + '\' v.s. \'' + this.searchModels.second.get('title') + '\'' + + '\n' + '(' + _.unique(_.map([this.firstSelectionView, this.bothSelectionView, this.secondSelectionView], function(view){ + return view.model.get('displayValue'); + })).join(', ') + ')' + ) } }, diff --git a/webapp/idol/src/main/public/static/js/find/idol/templates/comparison/map-comparison-view.html b/webapp/idol/src/main/public/static/js/find/idol/templates/comparison/map-comparison-view.html index 65fd1cabde..0fba26cbfd 100644 --- a/webapp/idol/src/main/public/static/js/find/idol/templates/comparison/map-comparison-view.html +++ b/webapp/idol/src/main/public/static/js/find/idol/templates/comparison/map-comparison-view.html @@ -16,7 +16,7 @@

<%-secondLabel%>

- + PPTX
diff --git a/webapp/idol/src/main/resources/application.properties b/webapp/idol/src/main/resources/application.properties index ace93051ad..dc29a395e0 100644 --- a/webapp/idol/src/main/resources/application.properties +++ b/webapp/idol/src/main/resources/application.properties @@ -31,3 +31,6 @@ spring.jpa.properties.hibernate.default_schema=find spring.jpa.hibernate.ddl-auto=none spring.main.banner-mode=off spring.messages.basename=i18n/idol-errors,i18n/errors +# Increase the default max file upload size from 1MB, since we use large base64-encoded images for map .pptx export +spring.http.multipart.max-file-size=16Mb +spring.http.multipart.max-request-size=16Mb diff --git a/webapp/idol/src/main/resources/custom-application.properties b/webapp/idol/src/main/resources/custom-application.properties index 8675b0c725..6ebde724d3 100644 --- a/webapp/idol/src/main/resources/custom-application.properties +++ b/webapp/idol/src/main/resources/custom-application.properties @@ -12,4 +12,6 @@ idol.log.timing.enabled=true # Only used if server.reverseProxy is true server.ajp.port=8009 server.tomcat.resources.max-cache-kb=20480 +# Increase the connector max post size from 2097152, since we use large base64-encoded images for map .pptx export +server.tomcat.connector.max-post-size=16777216 find.reverse-proxy.pre-authenticated-roles=FindUser diff --git a/webapp/idol/src/main/resources/defaultIdolConfigFile.json b/webapp/idol/src/main/resources/defaultIdolConfigFile.json index 82b041abe0..1af0121fc2 100644 --- a/webapp/idol/src/main/resources/defaultIdolConfigFile.json +++ b/webapp/idol/src/main/resources/defaultIdolConfigFile.json @@ -107,6 +107,9 @@ "enabled": false, "baseUrl": "" }, + "powerpoint": { + "templateFile": "" + }, "uiCustomization": { "options": { "directAccessLink": { diff --git a/webapp/idol/src/test/java/com/hp/autonomy/frontend/find/idol/export/IdolExportControllerTest.java b/webapp/idol/src/test/java/com/hp/autonomy/frontend/find/idol/export/IdolExportControllerTest.java index 2153c5f567..62a1f473a0 100644 --- a/webapp/idol/src/test/java/com/hp/autonomy/frontend/find/idol/export/IdolExportControllerTest.java +++ b/webapp/idol/src/test/java/com/hp/autonomy/frontend/find/idol/export/IdolExportControllerTest.java @@ -6,8 +6,10 @@ package com.hp.autonomy.frontend.find.idol.export; import com.autonomy.aci.client.services.AciErrorException; +import com.hp.autonomy.frontend.configuration.ConfigService; import com.hp.autonomy.frontend.find.core.export.ExportController; import com.hp.autonomy.frontend.find.core.export.ExportControllerTest; +import com.hp.autonomy.frontend.find.idol.configuration.IdolFindConfig; import com.hp.autonomy.searchcomponents.core.search.StateTokenAndResultCount; import com.hp.autonomy.searchcomponents.core.search.TypedStateToken; import com.hp.autonomy.searchcomponents.idol.search.IdolDocumentsService; @@ -33,6 +35,8 @@ public class IdolExportControllerTest extends ExportControllerTest idolFindConfig; @Override protected ExportController constructController() throws IOException { @@ -49,7 +53,7 @@ protected ExportController constructControl when(queryRestrictionsBuilder.stateMatchId(anyString())).thenReturn(queryRestrictionsBuilder); when(queryRestrictionsBuilder.build()).thenReturn(queryRestrictions); - return new IdolExportController(requestMapper, controllerUtils, documentsService, exportService); + return new IdolExportController(requestMapper, controllerUtils, documentsService, exportService, objectMapper, idolFindConfig); } @Override diff --git a/webapp/pom.xml b/webapp/pom.xml index c7faa2e581..6ef7aae029 100644 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -29,6 +29,7 @@ HEAD 0.42.0 + 1.0.0-SNAPSHOT ${project.build.outputDirectory} true From bfea7b8b6c31d8d6662b2058da404286b76cd080 Mon Sep 17 00:00:00 2001 From: Tung Jin Chew Date: Tue, 21 Feb 2017 10:20:35 +0000 Subject: [PATCH 02/28] Embed the pptx button in the parametric results view directly --- .../static/js/find/app/page/search/results/sunburst-view.js | 2 -- .../js/find/app/page/search/results/table/table-view.js | 4 +--- .../app/page/search/results/parametric-results-view.html | 1 + 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js index 4ad8ac3efd..0770560fc4 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/sunburst-view.js @@ -158,8 +158,6 @@ define([ render: function() { ParametricResultsView.prototype.render.apply(this); - this.$('.col-md-12').prepend(' PPTX'); - this.$content.addClass('sunburst'); } }); diff --git a/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js b/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js index f93a109eed..f37f6f8223 100644 --- a/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js +++ b/webapp/core/src/main/public/static/js/find/app/page/search/results/table/table-view.js @@ -43,9 +43,7 @@ define([ render: function() { ParametricResultsView.prototype.render.apply(this); - this.$('.col-md-12').prepend(' PPTX'); - - this.$el.on('click', '.table-pptx', _.bind(function(evt){ + this.$el.on('click', '.parametric-pptx', _.bind(function(evt){ evt.preventDefault(); var $form = $(''); diff --git a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/parametric-results-view.html b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/parametric-results-view.html index 73820357b6..e4cfb745a6 100644 --- a/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/parametric-results-view.html +++ b/webapp/core/src/main/public/static/js/find/templates/app/page/search/results/parametric-results-view.html @@ -1,4 +1,5 @@
+ PPTX
+ + +
\ No newline at end of file From dce499e9278bf399e485d54d2a6fc27b5414c115 Mon Sep 17 00:00:00 2001 From: Tung Jin Chew Date: Fri, 3 Mar 2017 14:55:55 +0000 Subject: [PATCH 13/28] Hide the powerpoint export button until the widgets have all loaded --- .../public/static/js/find/idol/app/page/dashboard-page.js | 8 ++++++++ .../js/find/idol/templates/page/dashboard-page.html | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/webapp/idol/src/main/public/static/js/find/idol/app/page/dashboard-page.js b/webapp/idol/src/main/public/static/js/find/idol/app/page/dashboard-page.js index 98d3d17da2..dee687417b 100644 --- a/webapp/idol/src/main/public/static/js/find/idol/app/page/dashboard-page.js +++ b/webapp/idol/src/main/public/static/js/find/idol/app/page/dashboard-page.js @@ -125,6 +125,14 @@ define([ widget.view.setElement($div).render(); }.bind(this)); + var $exportBtn = this.$('.report-pptx-group'); + + $.when.apply($, _.map(this.widgetViews, function(widget){ + return widget.view.savedSearchPromise + })).done(function(){ + $exportBtn.removeClass('hide'); + }) + this.listenTo(vent, 'vent:resize', this.onResize); this.listenTo(this.sidebarModel, 'change:collapsed', this.onResize); }, diff --git a/webapp/idol/src/main/public/static/js/find/idol/templates/page/dashboard-page.html b/webapp/idol/src/main/public/static/js/find/idol/templates/page/dashboard-page.html index b48061ca78..a969f912c3 100644 --- a/webapp/idol/src/main/public/static/js/find/idol/templates/page/dashboard-page.html +++ b/webapp/idol/src/main/public/static/js/find/idol/templates/page/dashboard-page.html @@ -1,4 +1,4 @@ -
+