@kalisio/kdk 2.1.9 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/.travis.test.sh +42 -10
  2. package/README.md +2 -2
  3. package/core/api/application.js +6 -1
  4. package/core/api/authentication.js +17 -1
  5. package/core/api/db.js +7 -2
  6. package/core/api/hooks/hooks.authentication.js +4 -2
  7. package/core/api/hooks/hooks.authorisations.js +12 -2
  8. package/core/api/hooks/hooks.model.js +5 -5
  9. package/core/api/hooks/hooks.organisations.js +0 -4
  10. package/core/api/services/account/account.hooks.js +10 -6
  11. package/core/api/services/account/account.service.js +1 -1
  12. package/{map/api/services/geocoder/geocoder.hooks.js → core/api/services/import-export/import-export.hooks.js} +7 -5
  13. package/core/api/services/import-export/import-export.service.js +11 -0
  14. package/core/api/services/index.js +13 -1
  15. package/core/api/services/users/users.hooks.js +2 -3
  16. package/core/client/api.js +16 -14
  17. package/core/client/capabilities.js +6 -2
  18. package/core/client/components/KContent.vue +11 -1
  19. package/core/client/components/KDialog.vue +17 -15
  20. package/core/client/components/KSponsor.vue +1 -1
  21. package/core/client/components/KTextArea.vue +5 -1
  22. package/core/client/components/app/KAbout.vue +1 -2
  23. package/core/client/components/app/KWelcome.vue +3 -5
  24. package/core/client/components/chart/KTimeSeriesChart.vue +24 -37
  25. package/core/client/components/collection/KColumn.vue +20 -17
  26. package/core/client/components/editor/KModalEditor.vue +0 -2
  27. package/core/client/components/form/KChipsField.vue +12 -2
  28. package/core/client/components/form/KColorField.vue +12 -2
  29. package/core/client/components/form/KColorScaleField.vue +12 -2
  30. package/core/client/components/form/KDateTimeRangeField.vue +12 -2
  31. package/core/client/components/form/KDatetimeField.vue +12 -2
  32. package/core/client/components/form/KEmailField.vue +12 -2
  33. package/core/client/components/form/KFileField.vue +12 -2
  34. package/core/client/components/form/KForm.vue +43 -9
  35. package/core/client/components/form/KIconField.vue +12 -2
  36. package/core/client/components/form/KItemField.vue +25 -4
  37. package/core/client/components/form/KNumberField.vue +12 -2
  38. package/core/client/components/form/KOptionsField.vue +12 -2
  39. package/core/client/components/form/KPasswordField.vue +12 -2
  40. package/core/client/components/form/KPhoneField.vue +13 -3
  41. package/core/client/components/form/KPropertyItemField.vue +12 -2
  42. package/core/client/components/form/KResolutionField.vue +126 -0
  43. package/core/client/components/form/KRoleField.vue +12 -2
  44. package/core/client/components/form/KSelectField.vue +14 -4
  45. package/core/client/components/form/KTextField.vue +12 -2
  46. package/core/client/components/form/KTextareaField.vue +13 -3
  47. package/core/client/components/form/KToggleField.vue +12 -2
  48. package/core/client/components/form/KTokenField.vue +12 -2
  49. package/core/client/components/form/KUnitField.vue +12 -2
  50. package/core/client/components/form/KUrlField.vue +12 -2
  51. package/core/client/components/input/KIconChooser.vue +10 -12
  52. package/core/client/components/input/KPalette.vue +2 -1
  53. package/core/client/components/layout/KPage.vue +5 -4
  54. package/core/client/components/layout/KWindow.vue +10 -10
  55. package/core/client/components/media/KColorScale.vue +26 -20
  56. package/core/client/components/media/KImageViewer.vue +57 -33
  57. package/core/client/components/media/KShape.vue +14 -103
  58. package/core/client/components/screen/KRegisterScreen.vue +0 -1
  59. package/core/client/components/screen/KScreenFooter.vue +0 -18
  60. package/core/client/components/team/KAddMember.vue +16 -22
  61. package/core/client/components/team/KGroupsActivity.vue +14 -0
  62. package/core/client/components/team/KMembersActivity.vue +12 -0
  63. package/core/client/components/team/KTagsActivity.vue +14 -0
  64. package/core/client/components/time/KDateTime.vue +23 -7
  65. package/core/client/components/time/KTimeControl.vue +142 -0
  66. package/core/client/components/tool/KExportTool.vue +57 -0
  67. package/core/client/composables/collection.js +0 -1
  68. package/core/client/composables/pwa.js +0 -1
  69. package/core/client/composables/schema.js +1 -1
  70. package/core/client/composables/session.js +30 -6
  71. package/core/client/exporter.js +141 -0
  72. package/core/client/i18n/core_en.json +91 -23
  73. package/core/client/i18n/core_fr.json +92 -23
  74. package/core/client/index.js +3 -0
  75. package/core/client/layout.js +34 -14
  76. package/core/client/local-storage.js +8 -6
  77. package/core/client/mixins/index.js +0 -1
  78. package/core/client/mixins/mixin.base-field.js +24 -2
  79. package/core/client/mixins/mixin.object-proxy.js +0 -1
  80. package/core/client/search.js +2 -1
  81. package/core/client/services/index.js +2 -1
  82. package/core/client/services/local-settings.service.js +4 -4
  83. package/core/client/theme.js +3 -3
  84. package/core/client/time.js +4 -0
  85. package/core/client/units.js +149 -4
  86. package/core/client/utils/index.js +13 -6
  87. package/core/client/utils/utils.account.js +1 -1
  88. package/core/client/utils/utils.colors.js +43 -0
  89. package/core/client/utils/utils.platform.js +0 -1
  90. package/core/client/utils/utils.pwa.js +14 -14
  91. package/core/client/utils/utils.session.js +1 -1
  92. package/core/client/utils/utils.shapes.js +270 -0
  93. package/core/client/utils/utils.time.js +37 -0
  94. package/core/common/permissions.js +3 -0
  95. package/core/common/schemas/settings.update.json +50 -29
  96. package/extras/css/core.variables.scss +3 -1
  97. package/extras/tours/map/navigation-bar.js +17 -15
  98. package/extras/tours/map/timeline.js +33 -33
  99. package/map/api/config/categories.cjs +4 -1
  100. package/map/api/hooks/hooks.catalog.js +39 -0
  101. package/map/api/hooks/hooks.features.js +23 -3
  102. package/map/api/hooks/hooks.query.js +31 -10
  103. package/map/api/models/projects.model.mongodb.js +8 -0
  104. package/map/api/services/catalog/catalog.hooks.js +5 -3
  105. package/map/api/services/features/features.hooks.js +18 -6
  106. package/map/api/services/index.js +22 -6
  107. package/map/api/services/projects/projects.hooks.js +118 -0
  108. package/map/client/capture.js +16 -0
  109. package/map/client/cesium/utils/index.js +3 -0
  110. package/map/client/cesium/utils/utils.events.js +30 -0
  111. package/map/client/cesium/utils/utils.popup.js +17 -0
  112. package/map/client/cesium/{utils.js → utils/utils.style.js} +53 -49
  113. package/map/client/components/KCapture.vue +50 -0
  114. package/map/client/components/KCaptureTextArea.vue +53 -0
  115. package/map/client/components/KCompass.vue +2 -2
  116. package/map/client/components/KFeaturesChart.vue +1 -1
  117. package/map/client/components/KFeaturesFilter.vue +2 -2
  118. package/map/client/components/KLayerStyleForm.vue +256 -430
  119. package/map/client/components/KLevelSlider.vue +1 -1
  120. package/map/client/components/KNorth.vue +31 -0
  121. package/map/client/components/KProjectMenu.vue +88 -0
  122. package/map/client/components/KTimezoneMap.vue +36 -23
  123. package/map/client/components/catalog/KAddLayer.vue +3 -4
  124. package/map/client/components/catalog/KConnectLayer.vue +16 -4
  125. package/map/client/components/catalog/KCreateLayer.vue +1 -2
  126. package/map/client/components/catalog/KCreateProject.vue +100 -0
  127. package/map/client/components/catalog/KCreateView.vue +25 -2
  128. package/map/client/components/catalog/KLayersPanel.vue +24 -27
  129. package/map/client/components/catalog/KLayersSelector.vue +1 -1
  130. package/map/client/components/catalog/KProjectEditor.vue +91 -0
  131. package/map/client/components/catalog/KProjectManager.vue +60 -0
  132. package/map/client/components/catalog/KProjectSelector.vue +38 -0
  133. package/map/client/components/catalog/KProjectsPanel.vue +153 -0
  134. package/map/client/components/catalog/KSelectLayers.vue +96 -0
  135. package/map/client/components/catalog/KSelectViews.vue +96 -0
  136. package/map/client/components/catalog/KViewsPanel.vue +66 -30
  137. package/map/client/components/form/KDirectionField.vue +24 -5
  138. package/map/client/components/form/KLayerCategoryField.vue +12 -2
  139. package/map/client/components/form/KLocationField.vue +20 -5
  140. package/map/client/components/form/KOwsLayerField.vue +12 -2
  141. package/map/client/components/form/KOwsServiceField.vue +12 -2
  142. package/map/client/components/form/KSelectLayersField.vue +159 -0
  143. package/map/client/components/form/KSelectViewsField.vue +121 -0
  144. package/map/client/components/form/KTimezoneField.vue +24 -17
  145. package/map/client/components/legend/KColorScaleLegend.vue +1 -1
  146. package/map/client/components/legend/KLayerLegend.vue +61 -0
  147. package/map/client/components/legend/KLegend.vue +45 -44
  148. package/map/client/components/legend/KLegendRenderer.vue +5 -3
  149. package/map/client/components/legend/KSymbolsLegend.vue +12 -10
  150. package/map/client/components/legend/KVariablesLegend.vue +78 -0
  151. package/map/client/components/location/KGeocodersFilter.vue +2 -4
  152. package/map/client/components/location/KLocationMap.vue +48 -17
  153. package/map/client/components/location/KLocationSearch.vue +13 -3
  154. package/map/client/components/tools/KSearchTool.vue +17 -12
  155. package/map/client/components/widget/KElevationProfile.vue +16 -19
  156. package/map/client/components/widget/KMapillaryViewer.vue +21 -22
  157. package/map/client/components/widget/KTimeSeries.vue +35 -23
  158. package/map/client/composables/activity.js +15 -2
  159. package/map/client/composables/catalog.js +66 -0
  160. package/map/client/composables/highlight.js +56 -20
  161. package/map/client/composables/index.js +2 -0
  162. package/map/client/composables/location.js +25 -18
  163. package/map/client/composables/project.js +122 -0
  164. package/map/client/geolocation.js +1 -1
  165. package/map/client/globe.js +2 -0
  166. package/map/client/i18n/map_en.json +123 -76
  167. package/map/client/i18n/map_fr.json +124 -72
  168. package/map/client/index.js +3 -0
  169. package/map/client/init.js +17 -0
  170. package/map/client/leaflet/GSMaPLayer.js +16 -17
  171. package/map/client/leaflet/ShapeMarker.js +40 -0
  172. package/map/client/leaflet/TiledFeatureLayer.js +1 -1
  173. package/map/client/leaflet/TiledMeshLayer.js +11 -15
  174. package/map/client/leaflet/TiledWindLayer.js +6 -10
  175. package/map/client/leaflet/utils/index.js +4 -0
  176. package/map/client/leaflet/utils/utils.events.js +41 -0
  177. package/map/client/leaflet/utils/utils.popup.js +21 -0
  178. package/map/client/leaflet/utils/utils.style.js +191 -0
  179. package/map/client/leaflet/utils/utils.tiles.js +87 -0
  180. package/map/client/map.js +2 -0
  181. package/map/client/mixins/globe/mixin.base-globe.js +29 -21
  182. package/map/client/mixins/globe/mixin.geojson-layers.js +132 -69
  183. package/map/client/mixins/globe/mixin.popup.js +2 -1
  184. package/map/client/mixins/globe/mixin.style.js +6 -4
  185. package/map/client/mixins/globe/mixin.tooltip.js +8 -3
  186. package/map/client/mixins/map/mixin.base-map.js +13 -11
  187. package/map/client/mixins/map/mixin.edit-layers.js +15 -15
  188. package/map/client/mixins/map/mixin.forecast-layers.js +3 -1
  189. package/map/client/mixins/map/mixin.geojson-layers.js +56 -20
  190. package/map/client/mixins/map/mixin.georaster-layers.js +4 -11
  191. package/map/client/mixins/map/mixin.heatmap-layers.js +1 -1
  192. package/map/client/mixins/map/mixin.popup.js +2 -1
  193. package/map/client/mixins/map/mixin.style.js +4 -67
  194. package/map/client/mixins/map/mixin.tiled-mesh-layers.js +2 -1
  195. package/map/client/mixins/map/mixin.tiled-wind-layers.js +4 -2
  196. package/map/client/mixins/map/mixin.tooltip.js +2 -1
  197. package/map/client/mixins/mixin.activity.js +66 -191
  198. package/map/client/mixins/mixin.catalog-panel.js +6 -6
  199. package/map/client/mixins/mixin.context.js +12 -9
  200. package/map/client/mixins/mixin.feature-service.js +29 -300
  201. package/map/client/mixins/mixin.weacast.js +11 -17
  202. package/map/client/pixi-utils.js +1 -1
  203. package/map/client/planets.js +58 -0
  204. package/map/client/utils/index.js +6 -0
  205. package/map/client/utils/utils.capture.js +176 -0
  206. package/map/client/utils/utils.catalog.js +149 -0
  207. package/map/client/utils/utils.features.js +364 -0
  208. package/map/client/utils/utils.js +0 -151
  209. package/map/client/utils/utils.layers.js +174 -0
  210. package/map/client/utils/utils.location.js +91 -23
  211. package/map/client/utils/utils.project.js +8 -0
  212. package/map/client/utils/utils.schema.js +0 -1
  213. package/map/client/utils/utils.style.js +297 -0
  214. package/map/client/utils.all.js +2 -2
  215. package/map/client/utils.globe.js +1 -1
  216. package/map/client/utils.map.js +1 -1
  217. package/map/common/permissions.js +2 -0
  218. package/map/common/schemas/capture.create.json +132 -0
  219. package/map/common/schemas/projects.create.json +52 -0
  220. package/map/common/schemas/projects.update.json +52 -0
  221. package/package.json +6 -5
  222. package/test/api/core/account.test.js +20 -0
  223. package/test/api/core/config/default.cjs +16 -3
  224. package/test/api/core/import-export.test.js +86 -0
  225. package/test/api/core/test-log-2024-01-04.log +14 -0
  226. package/test/api/map/catalog.test.js +164 -0
  227. package/test/api/map/index.test.js +25 -61
  228. package/test/api/map/test-log-2024-01-04.log +2 -0
  229. package/test/api/map/test-log-2024-01-11.log +1 -0
  230. package/test/api/map/test-log-2024-01-25.log +19 -0
  231. package/test/client/core/layout.js +25 -5
  232. package/test/client/core/utils.js +7 -0
  233. package/test/client/map/catalog.js +78 -1
  234. package/test/client/map/time.js +2 -1
  235. package/core/client/components/screen/KEndpointScreen.vue +0 -80
  236. package/core/client/mixins/mixin.account.js +0 -61
  237. package/extras/icons/kdk.png +0 -0
  238. package/map/api/services/geocoder/geocoder.service.js +0 -79
  239. package/map/client/components/KCaptureToolbar.vue +0 -155
  240. package/map/client/components/KColorLegend.vue +0 -349
  241. package/map/client/components/KTimeline.vue +0 -293
  242. package/map/client/components/KUrlLegend.vue +0 -122
  243. package/map/client/leaflet/utils.js +0 -246
@@ -0,0 +1,86 @@
1
+ import chai from 'chai'
2
+ import chailint from 'chai-lint'
3
+ import core, { kdk, hooks } from '../../../core/api/index.js'
4
+
5
+ const { util, expect } = chai
6
+
7
+ describe('core:import-export', () => {
8
+ let app, server, port, usersService, storageService, importExportService
9
+
10
+ before(async () => {
11
+ chailint(chai, util)
12
+ app = kdk()
13
+ // Register log hook
14
+ app.hooks({ error: { all: hooks.log } })
15
+ port = app.get('port')
16
+ await app.db.connect()
17
+ await app.db.instance.dropDatabase()
18
+ })
19
+
20
+ it('registers the services', async () => {
21
+ await app.configure(core)
22
+ // Ensure the users service exist
23
+ usersService = app.getService('users')
24
+ expect(usersService).toExist()
25
+ // Ensure the storage service exist
26
+ storageService = app.getService('storage')
27
+ expect(storageService).toExist()
28
+ // Ensure the expoter service exist
29
+ importExportService = app.getService('import-export')
30
+ expect(importExportService).toExist()
31
+ // Now app is configured launch the server
32
+ server = await app.listen(port)
33
+ await new Promise(resolve => server.once('listening', () => resolve()))
34
+ })
35
+ // Let enough time to process
36
+ .timeout(10000)
37
+
38
+ it('create a user collection', () => {
39
+ const users = []
40
+ for (let i = 0; i < 5000; i++) {
41
+ users.push({
42
+ email: `kalisio${i}@kalisio.xyz`,
43
+ password: 'Pass;word1',
44
+ description: 'Description for kalisio$[i}',
45
+ name: `user${i}`
46
+ })
47
+ }
48
+ return usersService._create(users, { noVerificationEmail: true })
49
+ })
50
+ // Let enough time to process
51
+ .timeout(50000)
52
+
53
+ it('export users collection in json', async () => {
54
+ const response = await importExportService.create({
55
+ method: 'export',
56
+ servicePath: 'api/users',
57
+ transform: {
58
+ omit: ['_id']
59
+ }
60
+ })
61
+ expect(response.SignedUrl).toExist()
62
+ await storageService.remove('import-export/' + response.id)
63
+ })
64
+ .timeout(30000)
65
+
66
+ it('export users collection in csv', async () => {
67
+ const response = await importExportService.create({
68
+ method: 'export',
69
+ servicePath: 'api/users',
70
+ transform: {
71
+ omit: ['_id']
72
+ },
73
+ format: 'csv'
74
+ })
75
+ expect(response.SignedUrl).toExist()
76
+ await storageService.remove('import-export/' + response.id)
77
+ })
78
+ .timeout(30000)
79
+
80
+ // Cleanup
81
+ after(async () => {
82
+ if (server) await server.close()
83
+ await app.db.instance.dropDatabase()
84
+ await app.db.disconnect()
85
+ })
86
+ })
@@ -0,0 +1,14 @@
1
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
2
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
3
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
4
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
5
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
6
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
7
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
8
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
9
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
10
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
11
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
12
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
13
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
14
+ {"level":"error","message":"error: api/account - Method: create: The provided password does not comply to the password policy"}
@@ -0,0 +1,164 @@
1
+ import chai from 'chai'
2
+ import chailint from 'chai-lint'
3
+ import _ from 'lodash'
4
+ import path from 'path'
5
+ import fs from 'fs-extra'
6
+ import { fileURLToPath } from 'url'
7
+ import core, { kdk, hooks, permissions } from '../../../core/api/index.js'
8
+ import map, {
9
+ permissions as mapPermissions, createCatalogService, createProjectsService
10
+ } from '../../../map/api/index.js'
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
13
+ const { util, expect } = chai
14
+
15
+ describe('map:catalog', () => {
16
+ let app, server, port, // baseUrl,
17
+ userService, userObject, catalogService, defaultLayers,
18
+ zonesLayer, vigicruesLayer, contextObject, projectService, projectObject
19
+
20
+ before(() => {
21
+ chailint(chai, util)
22
+
23
+ // Register all default hooks for authorisation
24
+ // Default rules for all users
25
+ permissions.defineAbilities.registerHook(permissions.defineUserAbilities)
26
+ // Then rules for maps
27
+ permissions.defineAbilities.registerHook(mapPermissions.defineUserAbilities)
28
+
29
+ app = kdk()
30
+ // Register authorisation/log hook
31
+ app.hooks({
32
+ before: { all: [hooks.authorise] },
33
+ error: { all: hooks.log }
34
+ })
35
+ port = app.get('port')
36
+ // baseUrl = `http://localhost:${port}${app.get('apiPath')}`
37
+ return app.db.connect()
38
+ })
39
+
40
+ it('registers the services', async () => {
41
+ await app.configure(core)
42
+ userService = app.getService('users')
43
+ expect(userService).toExist()
44
+ await app.configure(map)
45
+ // Create a global catalog/project service
46
+ await createCatalogService.call(app)
47
+ catalogService = app.getService('catalog')
48
+ expect(catalogService).toExist()
49
+ await createProjectsService.call(app)
50
+ projectService = app.getService('projects')
51
+ expect(projectService).toExist()
52
+ // Now app is configured launch the server
53
+ server = await app.listen(port)
54
+ await new Promise(resolve => server.once('listening', () => resolve()))
55
+ })
56
+ // Let enough time to process
57
+ .timeout(5000)
58
+
59
+ it('creates a test user', async () => {
60
+ userObject = await userService.create({ email: 'test-user@test.org', name: 'test-user' }, { checkAuthorisation: true })
61
+ const users = await userService.find({ query: { 'profile.name': 'test-user' }, user: userObject, checkAuthorisation: true })
62
+ expect(users.data.length > 0).beTrue()
63
+ })
64
+ // Let enough time to process
65
+ .timeout(5000)
66
+
67
+ it('registers the default layer catalog', async () => {
68
+ const layers = await fs.readJson(path.join(__dirname, 'config/layers.json'))
69
+ expect(layers.length > 0)
70
+ // Create a global catalog service
71
+ defaultLayers = await catalogService.create(layers)
72
+ expect(defaultLayers.length > 0)
73
+ zonesLayer = _.find(defaultLayers, { name: 'zones' })
74
+ vigicruesLayer = _.find(defaultLayers, { name: 'vigicrues-stations' })
75
+ })
76
+
77
+ it('manages layers, contexts and categories', async () => {
78
+ const layer = await catalogService.create({ name: 'dummy', type: 'OverlayLayer' })
79
+ // Can't duplicate name
80
+ let doublonLayer
81
+ try {
82
+ doublonLayer = await catalogService.create({ name: 'dummy', type: 'OverlayLayer' })
83
+ } catch (error) {
84
+ expect(error).toExist()
85
+ expect(error.name).to.equal('Conflict')
86
+ expect(error.data.translation.key).to.equal('OBJECT_ID_ALREADY_TAKEN')
87
+ }
88
+ expect(doublonLayer).beUndefined()
89
+ // Can duplicate if we target different types
90
+ let context = await catalogService.create({ name: 'dummy', type: 'Context', layers: ['dummy'] })
91
+ let category = await catalogService.create({ name: 'dummy', type: 'Category', layers: ['dummy'] })
92
+ // Check for context/category filtering by default
93
+ const response = await catalogService.find({ query: {}, paginate: false })
94
+ expect(response.length === defaultLayers.length + 1)
95
+ // Check for automated context/category update whn layer is renamed/removed
96
+ await catalogService.patch(layer._id.toString(), { name: 'oldDummy' })
97
+ context = await catalogService.get(context._id.toString())
98
+ expect(context.layers).to.deep.equal(['oldDummy'])
99
+ category = await catalogService.get(category._id.toString())
100
+ expect(category.layers).to.deep.equal(['oldDummy'])
101
+ await catalogService.remove(layer._id.toString())
102
+ context = await catalogService.get(context._id.toString())
103
+ expect(context.layers).to.deep.equal([])
104
+ await catalogService.remove(context._id.toString())
105
+ category = await catalogService.get(category._id.toString())
106
+ expect(category.layers).to.deep.equal([])
107
+ await catalogService.remove(category._id.toString())
108
+ })
109
+ // Let enough time to process
110
+ .timeout(5000)
111
+
112
+ it('creates a context', async () => {
113
+ contextObject = await catalogService.create({ name: 'context', type: 'Context', layers: ['vigicrues-stations', 'zones'] })
114
+ // Rename layer and check it has been updated in context
115
+ zonesLayer = await catalogService.patch(zonesLayer._id, { name: 'new-zones' })
116
+ contextObject = await catalogService.get(contextObject._id)
117
+ expect(contextObject.layers).to.deep.equal(['vigicrues-stations', 'new-zones'])
118
+ })
119
+
120
+ it('creates a project', async () => {
121
+ projectObject = await projectService.create({
122
+ name: 'project', views: [{ _id: contextObject._id }], layers: [{ _id: zonesLayer._id }, { name: vigicruesLayer.name }]
123
+ })
124
+ // Remove context and check it has been updated in project
125
+ await catalogService.remove(contextObject._id)
126
+ projectObject = await projectService.get(projectObject._id)
127
+ expect(projectObject.views).to.deep.equal([])
128
+ // Remove layer and check it has been updated in project
129
+ await catalogService.remove(zonesLayer._id)
130
+ projectObject = await projectService.get(projectObject._id, { query: { populate: true } })
131
+ expect(projectObject.layers).to.deep.equal([{
132
+ _id: vigicruesLayer._id,
133
+ name: vigicruesLayer.name,
134
+ service: vigicruesLayer.name
135
+ }])
136
+ })
137
+
138
+ it('clears the catalog and projects', async () => {
139
+ await catalogService.remove(null, { query: { } })
140
+ const layers = await catalogService.find({ query: {}, paginate: false })
141
+ expect(layers.length === 0)
142
+ await projectService.remove(projectObject._id)
143
+ const projects = await projectService.find({ query: {}, paginate: false })
144
+ expect(projects.length === 0)
145
+ })
146
+
147
+ it('removes the test user', async () => {
148
+ await userService.remove(userObject._id, {
149
+ user: userObject,
150
+ checkAuthorisation: true
151
+ })
152
+ const users = await userService.find({ query: { name: 'test-user' } })
153
+ expect(users.data.length === 0).beTrue()
154
+ })
155
+
156
+ // Cleanup
157
+ after(async () => {
158
+ if (server) await server.close()
159
+ await catalogService.Model.drop()
160
+ await projectService.Model.drop()
161
+ await userService.Model.drop()
162
+ await app.db.disconnect()
163
+ })
164
+ })
@@ -16,9 +16,9 @@ const { util, expect } = chai
16
16
 
17
17
  describe('map:services', () => {
18
18
  let app, server, port, // baseUrl,
19
- userService, userObject, geocoderService, catalogService, defaultLayers,
20
- zonesService, vigicruesStationsService, vigicruesObsService, adsbObsService,
21
- position, items, eventListeners, eventCount, eventData
19
+ userService, userObject, catalogService, defaultLayers,
20
+ zonesService, vigicruesStationsService, nbStations, vigicruesObsService,
21
+ adsbObsService, items, eventListeners, eventCount, eventData
22
22
 
23
23
  function eventsOn (service) {
24
24
  eventListeners = {}
@@ -81,8 +81,6 @@ describe('map:services', () => {
81
81
  userService = app.getService('users')
82
82
  expect(userService).toExist()
83
83
  await app.configure(map)
84
- geocoderService = app.getService('geocoder')
85
- expect(geocoderService).toExist()
86
84
  // Create a global catalog service
87
85
  await createCatalogService.call(app)
88
86
  catalogService = app.getService('catalog')
@@ -110,41 +108,6 @@ describe('map:services', () => {
110
108
  expect(defaultLayers.length > 0)
111
109
  })
112
110
 
113
- it('manages layers, contexts and categories', async () => {
114
- const layer = await catalogService.create({ name: 'dummy', type: 'OverlayLayer' })
115
- // Can't duplicate name
116
- let doublonLayer
117
- try {
118
- doublonLayer = await catalogService.create({ name: 'dummy', type: 'OverlayLayer' })
119
- } catch (error) {
120
- expect(error).toExist()
121
- expect(error.name).to.equal('Conflict')
122
- expect(error.data.translation.key).to.equal('OBJECT_ID_ALREADY_TAKEN')
123
- }
124
- expect(doublonLayer).beUndefined()
125
- // Can duplicate if we target different types
126
- let context = await catalogService.create({ name: 'dummy', type: 'Context', layers: ['dummy'] })
127
- let category = await catalogService.create({ name: 'dummy', type: 'Category', layers: ['dummy'] })
128
- // Check for context/category filtering by default
129
- const response = await catalogService.find({ query: {}, paginate: false })
130
- expect(response.length === defaultLayers.length + 1)
131
- // Check for automated context/category update whn layer is renamed/removed
132
- await catalogService.patch(layer._id.toString(), { name: 'oldDummy' })
133
- context = await catalogService.get(context._id.toString())
134
- expect(context.layers).to.deep.equal(['oldDummy'])
135
- category = await catalogService.get(category._id.toString())
136
- expect(category.layers).to.deep.equal(['oldDummy'])
137
- await catalogService.remove(layer._id.toString())
138
- context = await catalogService.get(context._id.toString())
139
- expect(context.layers).to.deep.equal([])
140
- await catalogService.remove(context._id.toString())
141
- category = await catalogService.get(category._id.toString())
142
- expect(category.layers).to.deep.equal([])
143
- await catalogService.remove(category._id.toString())
144
- })
145
- // Let enough time to process
146
- .timeout(5000)
147
-
148
111
  it('create and feed the zones service', async () => {
149
112
  // Create the service
150
113
  const zonesLayer = _.find(defaultLayers, { name: 'zones' })
@@ -217,6 +180,7 @@ describe('map:services', () => {
217
180
  expect(vigicruesStationsService).toExist()
218
181
  // Feed the collection
219
182
  const stations = fs.readJsonSync(path.join(__dirname, 'data/vigicrues.stations.json')).features
183
+ nbStations = stations.length
220
184
  await vigicruesStationsService.create(stations)
221
185
  })
222
186
  // Let enough time to process
@@ -315,7 +279,27 @@ describe('map:services', () => {
315
279
  .timeout(5000)
316
280
 
317
281
  it('performs spatial filtering on vigicrues stations service', async () => {
318
- const result = await vigicruesStationsService.find({
282
+ let result = await vigicruesStationsService.find({
283
+ query: { south: -90, north: 90, east: 180, west: -180 },
284
+ paginate: false
285
+ })
286
+ expect(result.features.length).to.equal(nbStations)
287
+ result = await vigicruesStationsService.find({
288
+ query: { south: 80, north: 85, east: 180, west: -180 },
289
+ paginate: false
290
+ })
291
+ expect(result.features.length).to.equal(0)
292
+ result = await vigicruesStationsService.find({
293
+ query: { south: -85, north: -80, east: 180, west: -180 },
294
+ paginate: false
295
+ })
296
+ expect(result.features.length).to.equal(0)
297
+ result = await vigicruesStationsService.find({
298
+ query: { south: -20, north: 20, east: 100, west: -100 },
299
+ paginate: false
300
+ })
301
+ expect(result.features.length).to.equal(0)
302
+ result = await vigicruesStationsService.find({
319
303
  query: {
320
304
  geometry: {
321
305
  $near: {
@@ -512,26 +496,6 @@ describe('map:services', () => {
512
496
  // Let enough time to process
513
497
  .timeout(10000)
514
498
 
515
- it('geocode an address', async () => {
516
- const address = '80 chemin des tournesols, 11400 Castelnaudary'
517
- const response = await geocoderService.create({ address: address }, { user: userObject, checkAuthorisation: true })
518
- expect(response.length === 1).beTrue()
519
- position = response[0]
520
- expect(position.latitude).toExist()
521
- expect(position.longitude).toExist()
522
- })
523
- // Let enough time to process
524
- .timeout(10000)
525
-
526
- it('reverse geocode a position', async () => {
527
- const response = await geocoderService.create(position, { user: userObject, checkAuthorisation: true })
528
- expect(response.length > 0).beTrue()
529
- expect(response[0].country).toExist()
530
- expect(response[0].streetName).toExist()
531
- })
532
- // Let enough time to process
533
- .timeout(10000)
534
-
535
499
  it('clears the layers', async () => {
536
500
  for (let i = 0; i < defaultLayers.length; ++i) {
537
501
  await catalogService.remove(defaultLayers[i]._id)
@@ -0,0 +1,2 @@
1
+ {"level":"error","message":"error: api/catalog - Method: create: Object with name equals to dummy already exist for service catalog"}
2
+ {"level":"error","message":"error: api/catalog - Method: create: Object with name equals to dummy already exist for service catalog"}
@@ -0,0 +1 @@
1
+ {"level":"error","message":"error: api/catalog - Method: create: Object with name equals to dummy already exist for service catalog"}
@@ -0,0 +1,19 @@
1
+ {"level":"info","message":"Logger configured"}
2
+ {"level":"info","message":"Initializing weacast-gfs plugin"}
3
+ {"level":"info","message":"Initializing gfs-world forecast"}
4
+ {"level":"info","message":"Checking for up-to-date forecast data on gfs-world/u-wind"}
5
+ {"level":"info","message":"Checking for up-to-date forecast data on gfs-world/v-wind"}
6
+ {"level":"info","message":"Completed forecast data update on gfs-world/u-wind"}
7
+ {"level":"info","message":"Completed forecast data update on gfs-world/v-wind"}
8
+ {"level":"error","message":"Cannot check alert 65b23e9b0c441d5b46ac6f77 as target probes service is not available"}
9
+ {"level":"error","message":"error: api/alerts - Method: create: Not Found"}
10
+ {"level":"error","message":"Cannot check alert 65b23e9b0c441d5b46ac6f78 as target probes service is not available"}
11
+ {"level":"error","message":"error: api/alerts - Method: create: Not Found"}
12
+ {"level":"error","message":"Cannot check alert 65b23e9b0c441d5b46ac6f79 as target probes service is not available"}
13
+ {"level":"error","message":"error: api/alerts - Method: create: Not Found"}
14
+ {"level":"error","message":"error: api/alerts - Method: create: Not Found"}
15
+ {"level":"error","message":"error: api/alerts - Method: create: Not Found"}
16
+ {"level":"error","message":"Cannot check alert 65b23e9b0c441d5b46ac74bc as no data is available for features service vigicrues-observations"}
17
+ {"level":"error","message":"error: api/alerts - Method: create: Not Found"}
18
+ {"level":"error","message":"error: api/catalog - Method: create: Object with name equals to dummy already exist for service catalog"}
19
+ {"level":"info","message":"Logger configured"}
@@ -1,5 +1,5 @@
1
1
  import makeDebug from 'debug'
2
- import { click, clickAction, isElementVisible } from './utils.js'
2
+ import { countElements, click, clickAction, isElementVisible, isActionVisible } from './utils.js'
3
3
 
4
4
  const debug = makeDebug('kdk:core:test:layout')
5
5
 
@@ -79,26 +79,46 @@ export async function isWindowMaximized (page, placement) {
79
79
  return isElementVisible(page, `#restore-${placement}-window`)
80
80
  }
81
81
 
82
+ async function clickWindowControl (page, placement, control) {
83
+ const action = `${control}-${placement}-window`
84
+ if (!await isActionVisible(page, action)) await clickAction(page, 'window-controls')
85
+ await clickAction(page, action)
86
+ }
87
+
82
88
  export async function closeWindow (page, placement) {
83
- await clickAction(page, `close-${placement}-window`)
89
+ await clickWindowControl(page, placement, 'close')
84
90
  }
85
91
 
86
92
  export async function maximizeWindow (page, placement) {
87
- await clickAction(page, `maximize-${placement}-window`)
93
+ await clickWindowControl(page, placement, 'maximize')
88
94
  }
89
95
 
90
96
  export async function restoreWindow (page, placement) {
91
- await clickAction(page, `restore-${placement}-window`)
97
+ await clickWindowControl(page, placement, 'restore')
92
98
  }
93
99
 
94
100
  export async function pinWindow (page, placement) {
95
- await clickAction(page, `pin-${placement}-window`)
101
+ await clickWindowControl(page, placement, 'pin')
102
+ }
103
+
104
+ export async function unpinWindow (page, placement) {
105
+ await clickWindowControl(page, placement, 'unpin')
96
106
  }
97
107
 
98
108
  export async function clickFab (page) {
99
109
  return clickAction(page, 'fab')
100
110
  }
101
111
 
112
+ export async function clickFabAction (page, action) {
113
+ await clickAction(page, 'fab')
114
+ return clickAction(page, action)
115
+ }
116
+
117
+ export async function countFabActions (page) {
118
+ return countElements(page, '//a[contains(@class, "k-action-fab-action")]')
119
+ }
120
+
121
+
102
122
  export async function closeWelcomeDialog (page) {
103
123
  await click(page, '.q-dialog #close-button')
104
124
  }
@@ -52,6 +52,13 @@ export async function click (page, selector, wait = 500) {
52
52
  }
53
53
  }
54
54
 
55
+ /* Helper function to check whether an action is visible
56
+ */
57
+ export async function isActionVisible (page, action) {
58
+ const selector = `#${action}`
59
+ return isElementVisible(page, selector)
60
+ }
61
+
55
62
  /* Helper function to click on an action selector
56
63
  */
57
64
  export async function clickAction (page, action, wait = 500) {
@@ -29,6 +29,14 @@ export async function clickLayerCategory (page, tabId, categoryId, wait = 500) {
29
29
  await core.clickPaneAction(page, 'right', categoryId, wait)
30
30
  }
31
31
 
32
+ export async function layerExists (page, tabId, layer) {
33
+ const isCatalogOpened = await clickCatalogTab(page, tabId)
34
+ const layerId = getLayerId(layer)
35
+ const exists = await core.elementExists(page, `#${layerId}`)
36
+ if (!isCatalogOpened) await core.clickOpener(page, 'right')
37
+ return exists
38
+ }
39
+
32
40
  export async function clickLayer (page, tabId, layer, wait = 1000) {
33
41
  const isCatalogOpened = await clickCatalogTab(page, tabId)
34
42
  const layerId = getLayerId(layer)
@@ -39,7 +47,7 @@ export async function clickLayer (page, tabId, layer, wait = 1000) {
39
47
  if (!isCategoryOpened) await core.clickPaneAction(page, 'right', categoryId, 1000)
40
48
  }
41
49
  let selector = `#${layerId} .q-toggle`
42
- // some layers have a toggle (regulaer layers), some don't (base layers)
50
+ // some layers have a toggle (regular layers), some don't (base layers)
43
51
  if (!await core.elementExists(page, selector)) {
44
52
  selector = `#${layerId}`
45
53
  }
@@ -111,6 +119,7 @@ export async function dropFile (page, filePath, wait = 2000) {
111
119
  const loaderSelector = '#dropFileInput'
112
120
  const loader = await page.$(loaderSelector)
113
121
  await loader.uploadFile(filePath)
122
+ await page.waitForNetworkIdle()
114
123
  await page.waitForTimeout(wait)
115
124
  }
116
125
 
@@ -126,6 +135,12 @@ export async function addView (page) {
126
135
  await page.waitForTimeout(1000)
127
136
  }
128
137
 
138
+ export async function addProject (page) {
139
+ await core.clickFab(page)
140
+ await core.clickAction(page, 'create-project')
141
+ await page.waitForTimeout(1000)
142
+ }
143
+
129
144
  export async function importLayer (page, filePath, featureId = undefined, wait = 2000) {
130
145
  await addLayer(page)
131
146
  await core.uploadFile(page, '#file-field', filePath)
@@ -189,5 +204,67 @@ export async function removeView (page, tabId, name) {
189
204
  const isCatalogOpened = await clickCatalogTab(page, tabId, 2000)
190
205
  await core.clickItemAction(page, 'catalog/KViewSelector', name, 'view-overflowmenu', 1000)
191
206
  await core.clickAction(page, 'remove-view', 1000)
207
+ await core.click(page, '.q-dialog button:nth-child(2)', 1000)
208
+ if (!isCatalogOpened) await core.clickOpener(page, 'right')
209
+ }
210
+
211
+ export async function createProject (page, name, options, wait = 2000) {
212
+ const { categories, layers, views } = options
213
+ await addProject(page)
214
+ await core.type(page, '#name-field', name)
215
+ await core.type(page, '#description-field', `${name} description`)
216
+ // Open categories first
217
+ for (let i = 0; i < categories.length; i++) {
218
+ const category = categories[i]
219
+ await core.clickXPath(page, `//div[contains(text(), "${category}")]`)
220
+ }
221
+ // Then select layers
222
+ for (let i = 0; i < layers.length; i++) {
223
+ const layer = layers[i]
224
+ await core.clickXPath(page, `//div[contains(text(), "${layer}")]/../../preceding-sibling::div[contains(@class, "q-checkbox")]`)
225
+ }
226
+ if (views) {
227
+ for (let i = 0; i < views.length; i++) {
228
+ const view = views[i]
229
+ await core.clickXPath(page, `//div[contains(text(), "${view}")]/../../preceding-sibling::div[contains(@class, "q-checkbox")]`)
230
+ }
231
+ }
232
+ await core.clickAction(page, 'apply-button', 1000)
233
+ await page.waitForTimeout(wait)
234
+ }
235
+
236
+ export async function projectExists (page, tabId, name) {
237
+ const isCatalogOpened = await clickCatalogTab(page, tabId, 2000)
238
+ const exists = await core.itemExists(page, 'catalog/KProjectSelector', name)
239
+ if (!isCatalogOpened) await core.clickOpener(page, 'right')
240
+ return exists
241
+ }
242
+
243
+ export async function clickProject (page, tabId, name) {
244
+ const isCatalogOpened = await clickCatalogTab(page, tabId, 2000)
245
+ await core.clickItem(page, 'catalog/KProjectSelector', name)
246
+ // Switching to project automatically closes the catalog tab
247
+ if (isCatalogOpened) await core.clickOpener(page, 'right')
248
+ await page.waitForNetworkIdle()
249
+ }
250
+
251
+ export async function switchProject (page, name, wait = 2000) {
252
+ await core.click(page, '#project-menu', 2000)
253
+ await core.clickXPath(page, `//div[contains(@component, "collection/KItem") and contains(., "${name}")]`, 1000)
254
+ await page.waitForNetworkIdle()
255
+ await page.waitForTimeout(wait)
256
+ }
257
+
258
+ export async function closeProject (page, wait = 2000) {
259
+ await core.click(page, '#close-project')
260
+ await page.waitForNetworkIdle()
261
+ await page.waitForTimeout(wait)
262
+ }
263
+
264
+ export async function removeProject (page, tabId, name) {
265
+ const isCatalogOpened = await clickCatalogTab(page, tabId, 2000)
266
+ await core.clickItemAction(page, 'catalog/KProjectSelector', name, 'project-overflowmenu', 1000)
267
+ await core.clickAction(page, 'remove-project', 1000)
268
+ await core.click(page, '.q-dialog button:nth-child(2)', 1000)
192
269
  if (!isCatalogOpened) await core.clickOpener(page, 'right')
193
270
  }
@@ -11,7 +11,8 @@ export async function openTimeline (page) {
11
11
 
12
12
  export async function clickTimelineHour (page, hour, wait = 1000) {
13
13
  const isTimelineOpened = await openTimeline(page)
14
- const xpath = `//div[contains(., "${hour}h") and contains(@class, "k-timeline-hour-frame")]`
14
+ await core.click(page, '#time-button', wait)
15
+ const xpath = `//div[contains(@class, "q-time__clock-pos-${hour}")]`
15
16
  const elements = await page.$x(xpath)
16
17
  if (elements.length > 0) {
17
18
  elements[0].click()