@eeacms/volto-cca-policy 0.1.52 → 0.1.54

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [0.1.54](https://github.com/eea/volto-cca-policy/compare/0.1.53...0.1.54) - 20 December 2023
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: add mission projects search config + code refactoring [kreafox - [`bdc1e73`](https://github.com/eea/volto-cca-policy/commit/bdc1e7310e9bc8b3d66f2c63f1955c25ca233be6)]
12
+
13
+ #### :bug: Bug Fixes
14
+
15
+ - fix: import [kreafox - [`93e2726`](https://github.com/eea/volto-cca-policy/commit/93e2726b93385bda46b27bab9d8f910d939c4099)]
16
+
17
+ #### :nail_care: Enhancements
18
+
19
+ - change: update mission projects facets [kreafox - [`843a297`](https://github.com/eea/volto-cca-policy/commit/843a29710677a10e8714bbf5a2892841bfb1e029)]
20
+
21
+ #### :house: Internal changes
22
+
23
+ - style: Automated code fix [eea-jenkins - [`8fb22ff`](https://github.com/eea/volto-cca-policy/commit/8fb22ffe57b6be84ed4720a21836579839ecb7ae)]
24
+ - style: Automated code fix [eea-jenkins - [`8bf77c8`](https://github.com/eea/volto-cca-policy/commit/8bf77c8839cec40389a99fae3ab685ed5b69d146)]
25
+
26
+ #### :hammer_and_wrench: Others
27
+
28
+ - Refs #259267 - test [Tripon Eugen - [`74e7710`](https://github.com/eea/volto-cca-policy/commit/74e77106b923a9e146e5d30d9f2410cb06e2c34c)]
29
+ - Refs #259267 - test [Tripon Eugen - [`84039ba`](https://github.com/eea/volto-cca-policy/commit/84039bad25b855a708a601891796da4ff2ac8e71)]
30
+ - Refs #260715 - requested updates [Tripon Eugen - [`89214f6`](https://github.com/eea/volto-cca-policy/commit/89214f6e1b032372a40006706e142f98fb42fa12)]
31
+ - Refs #260715 - requested updates [Tripon Eugen - [`c0353d8`](https://github.com/eea/volto-cca-policy/commit/c0353d8a8016d5c51a0fc6d546f5487080ae253c)]
32
+ - Refs #260715 - requested updates [Tripon Eugen - [`a561804`](https://github.com/eea/volto-cca-policy/commit/a561804bfe548678ae7b497f6d0f645ce0ca928b)]
33
+ - test: fix duplications [kreafox - [`bdea7b7`](https://github.com/eea/volto-cca-policy/commit/bdea7b7fbe01a1f7e591c39010ae8c03b7bbcbe2)]
34
+ - test: increase code coverage [kreafox - [`794d1aa`](https://github.com/eea/volto-cca-policy/commit/794d1aafe7c5577aae089cd781dc997e015ea3a2)]
35
+ - Refs #260715 - filter getContent response to get only folders and test [Tripon Eugen - [`cc967d7`](https://github.com/eea/volto-cca-policy/commit/cc967d7c540d9725993ffe0f227f31ef1bd5a317)]
36
+ - Refs #260715 - filter getContent response to get only folders [Tripon Eugen - [`80b5866`](https://github.com/eea/volto-cca-policy/commit/80b58661648f9caf13b28dab8382d4058d070bd5)]
37
+ ### [0.1.53](https://github.com/eea/volto-cca-policy/compare/0.1.52...0.1.53) - 19 December 2023
38
+
39
+ #### :hammer_and_wrench: Others
40
+
41
+ - Refs #260715 - switch browse folder to getContent test subrequests [Tripon Eugen - [`7744481`](https://github.com/eea/volto-cca-policy/commit/7744481bbebe3a0eedd3d440d58ae3b9adf43a18)]
42
+ - Refs #260715 - switch browse folder to getContent test [Tripon Eugen - [`ca17c9c`](https://github.com/eea/volto-cca-policy/commit/ca17c9c9a2972129fdbcef1d7dcceb2af1062cf2)]
43
+ - Refs #260715 - switch browse folder to getContent jenkins [Tripon Eugen - [`866118c`](https://github.com/eea/volto-cca-policy/commit/866118c4d5537d00c7bb7ffed272c16cd9516194)]
44
+ - Refs #260715 - switch browse folder to getContent [Tripon Eugen - [`4c6cb1b`](https://github.com/eea/volto-cca-policy/commit/4c6cb1b74812c020f670b1e660fae74eca7bbd8d)]
45
+ - WIP [Tiberiu Ichim - [`baef385`](https://github.com/eea/volto-cca-policy/commit/baef385bc65818b1a7b294fd6fcbbe65aef731f2)]
46
+ - WIP [Tiberiu Ichim - [`d64813d`](https://github.com/eea/volto-cca-policy/commit/d64813d66b32c785124adfa4e1e2d95bab512592)]
7
47
  ### [0.1.52](https://github.com/eea/volto-cca-policy/compare/0.1.51...0.1.52) - 15 December 2023
8
48
 
9
49
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -5,21 +5,18 @@ import RASTMap from './RASTMap';
5
5
  import RASTAccordion from './RASTAccordion';
6
6
  import { useLocation } from 'react-router-dom';
7
7
 
8
- import { withContentNavigation } from '@plone/volto/components/theme/Navigation/withContentNavigation';
9
-
10
8
  /**
11
9
  * A navigation slot implementation, similar to the classic Plone navigation
12
10
  * portlet. It uses the same API, so the options are similar to
13
11
  * INavigationPortlet
14
12
  */
15
13
  export function ContextNavigationComponent(props) {
16
- const { navigation = {}, location } = props;
17
- const { items = [] } = navigation;
14
+ const { location, items } = props;
18
15
  let activeMenu = null;
19
16
 
20
17
  const curent_location = useLocation();
21
18
  for (let i = 0; i < items.length; i++) {
22
- let itemUrl = '/' + items[i].href.split('/').slice(3).join('/');
19
+ let itemUrl = '/' + items[i]['@id'].split('/').slice(3).join('/');
23
20
  items[i].is_active = false;
24
21
  if (curent_location.pathname.includes(itemUrl)) {
25
22
  activeMenu = i;
@@ -35,10 +32,15 @@ export function ContextNavigationComponent(props) {
35
32
  activeMenu={activeMenu}
36
33
  />
37
34
  {items.length ? (
38
- <RASTAccordion datasets={items} activeMenu={activeMenu} />
35
+ <RASTAccordion
36
+ items={items}
37
+ curent_location={curent_location}
38
+ activeMenu={activeMenu}
39
+ />
39
40
  ) : null}
40
41
  </>
41
42
  );
42
43
  }
43
44
 
44
- export default compose(withContentNavigation)(ContextNavigationComponent);
45
+ // withContentNavigation
46
+ export default compose()(ContextNavigationComponent);
@@ -1,34 +1,22 @@
1
1
  import React from 'react';
2
2
  import { Accordion, Icon } from 'semantic-ui-react';
3
3
  import RASTAccordionContent from './RASTAccordionContent';
4
+ import { useHistory } from 'react-router-dom';
4
5
 
5
6
  const RASTAccordion = (props) => {
6
- const { datasets = {}, activeMenu } = props;
7
+ const { items = {}, curent_location, activeMenu } = props;
7
8
 
8
- const [activeIndex, setActiveIndex] = React.useState([activeMenu]);
9
+ const history = useHistory();
9
10
 
10
- const handleActiveIndex = (index) => {
11
- setActiveIndex(index);
12
- };
13
- const handleClick = (e, titleProps) => {
14
- const { index } = titleProps;
15
-
16
- const newIndex =
17
- activeIndex.indexOf(index) === -1
18
- ? [...activeIndex, index]
19
- : activeIndex.filter((item) => item !== index);
20
-
21
- handleActiveIndex(newIndex);
22
- };
23
- const isActive = (id) => {
24
- return activeIndex.includes(id);
11
+ const handleClick = (e, item) => {
12
+ let itemUrl = '/' + item['@id'].split('/').slice(3).join('/');
13
+ history.push(itemUrl);
25
14
  };
26
15
  return (
27
16
  <>
28
- {datasets.map((dataset, index) => {
29
- const { id } = dataset;
30
- const active = isActive(index);
31
- let datasetPath = '/' + dataset.href.split('/').slice(3).join('/');
17
+ {items.map((item, index) => {
18
+ const { id } = item;
19
+ const active = activeMenu === index;
32
20
 
33
21
  return (
34
22
  <Accordion id={id} key={index} className="secondary">
@@ -38,15 +26,15 @@ const RASTAccordion = (props) => {
38
26
  active={active}
39
27
  aria-expanded={active}
40
28
  index={index}
41
- onClick={(e) => handleClick(e, { index, id, dataset })}
29
+ onClick={(e) => handleClick(e, item)}
42
30
  onKeyDown={(e) => {
43
31
  if (e.keyCode === 13 || e.keyCode === 32) {
44
32
  e.preventDefault();
45
- handleClick(e, { index, id, dataset });
33
+ handleClick(e, { index, id, item });
46
34
  }
47
35
  }}
48
36
  >
49
- <span className="dataset-title">{dataset.title}</span>
37
+ <span className="item-title">{item.title}</span>
50
38
  {active ? (
51
39
  <Icon className="ri-arrow-up-s-line" />
52
40
  ) : (
@@ -55,18 +43,13 @@ const RASTAccordion = (props) => {
55
43
  </Accordion.Title>
56
44
  <Accordion.Content active={active}>
57
45
  <RASTAccordionContent
46
+ curent_location={curent_location}
58
47
  key={index}
59
- params={{
60
- name: 'CurrentTitle',
61
- includeTop: false,
62
- currentFolderOnly: true,
63
- topLevel: 3,
64
- bottomLevel: 6,
65
- rootPath: datasetPath,
66
- title: dataset.title,
48
+ main={{
49
+ title: item.title,
50
+ href: item['@id'],
51
+ url: item.url,
67
52
  }}
68
- location={{ pathname: datasetPath }}
69
- main={{ title: dataset.title, href: dataset['@id'] }}
70
53
  />
71
54
  </Accordion.Content>
72
55
  </Accordion>
@@ -11,15 +11,17 @@ const mockStore = configureStore();
11
11
  describe('RASTAccordion', () => {
12
12
  it('should render the component', () => {
13
13
  const data = {
14
- datasets: [
14
+ items: [
15
15
  {
16
- id: 'my-dataset',
16
+ id: 'my-item',
17
17
  title: 'Hello',
18
- '@id': '/my-dataset',
19
- href: '/my-dataset-href',
18
+ '@id': '/my-item',
19
+ '@type': 'Folder',
20
+ href: '/my-item-href',
20
21
  },
21
22
  ],
22
23
  activeMenu: 1,
24
+ curent_location: '/',
23
25
  };
24
26
 
25
27
  const store = mockStore({
@@ -1,30 +1,42 @@
1
+ import React from 'react';
1
2
  import { List } from 'semantic-ui-react';
2
3
  import { Link } from 'react-router-dom';
3
4
  import { compose } from 'redux';
4
5
  import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers';
5
-
6
- import { withContentNavigation } from '@plone/volto/components/theme/Navigation/withContentNavigation';
6
+ import { useDispatch, useSelector } from 'react-redux';
7
+ import { getContent } from '@plone/volto/actions';
8
+ // import useChildren from './RASTView';
7
9
 
8
10
  const RASTAccordionContent = (props) => {
9
- const { main } = props;
10
- const items = props.navigation?.items;
11
+ const { main, curent_location } = props;
12
+ const dispatch = useDispatch();
13
+ const location = main.url;
14
+ let items = [];
15
+
16
+ React.useEffect(() => {
17
+ const action = getContent(location, null, location);
18
+ dispatch(action);
19
+ }, [location, dispatch]);
11
20
 
21
+ items = useSelector(
22
+ (state) => state.content?.subrequests?.[location]?.data?.items || [],
23
+ );
24
+ // const items = useChildren(location);
12
25
  return (
13
26
  <div className="dataset-content">
14
27
  <div>
15
- <List.Item key={'0'}>
16
- <List.Content>
17
- <div className="dataset-item">
18
- <Link to={flattenToAppURL(getBaseUrl(main.href))}>
19
- {main.title}
20
- </Link>
21
- </div>
22
- </List.Content>
23
- </List.Item>
24
- {items
25
- ? items.map((item, index) => {
26
- return (
27
- <List.Item key={item.id}>
28
+ {items.length
29
+ ? items
30
+ .filter((item) => item['@type'] === 'Folder')
31
+ .map((item) => (
32
+ <List.Item
33
+ key={item.id}
34
+ className={`${
35
+ item['@id'].endsWith(curent_location.pathname)
36
+ ? 'active'
37
+ : ''
38
+ }`}
39
+ >
28
40
  <List.Content>
29
41
  <div className="dataset-item">
30
42
  <Link to={flattenToAppURL(getBaseUrl(item['@id']))}>
@@ -33,12 +45,11 @@ const RASTAccordionContent = (props) => {
33
45
  </div>
34
46
  </List.Content>
35
47
  </List.Item>
36
- );
37
- })
48
+ ))
38
49
  : null}
39
50
  </div>
40
51
  </div>
41
52
  );
42
53
  };
43
54
 
44
- export default compose(withContentNavigation)(RASTAccordionContent);
55
+ export default compose()(RASTAccordionContent);
@@ -1,27 +1,35 @@
1
1
  import React from 'react';
2
2
  import './styles.less';
3
3
  import ContextNavigation from './ContextNavigation';
4
+ import { useDispatch, useSelector } from 'react-redux';
5
+ import { getContent } from '@plone/volto/actions';
6
+
7
+ function useChildren(location) {
8
+ const dispatch = useDispatch();
9
+ React.useEffect(() => {
10
+ const action = getContent(location, null, location);
11
+ dispatch(action);
12
+ }, [location, dispatch]);
13
+
14
+ const items = useSelector(
15
+ (state) => state.content.subrequests?.[location]?.data?.items || [],
16
+ );
17
+ return items;
18
+ }
4
19
 
5
20
  export default function RASTView(props) {
6
21
  const { data } = props;
7
22
  let root_path = data?.root_path;
8
- let top_level = 1;
9
23
  if (typeof root_path === 'undefined') {
10
24
  root_path = '/';
11
25
  }
12
- top_level = (root_path.match(/\//g) || []).length - 1;
26
+
27
+ const items = useChildren(root_path);
13
28
 
14
29
  return (
15
30
  <div className="block rast-block">
16
31
  <ContextNavigation
17
- params={{
18
- // name: 'CurrentTitle',
19
- // includeTop: false,
20
- // currentFolderOnly: false,
21
- topLevel: top_level,
22
- // topLevel: 2,
23
- // rootPath: '/en/about/test-rast/',
24
- }}
32
+ items={items}
25
33
  location={{
26
34
  pathname: root_path,
27
35
  }}
@@ -13,3 +13,7 @@
13
13
  .circle:hover {
14
14
  fill: @green-1;
15
15
  }
16
+
17
+ .rast-block .item.active a {
18
+ font-weight: bold;
19
+ }
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { Fragment } from 'react';
2
2
  import {
3
3
  HTMLField,
4
4
  ContentMetadata,
@@ -7,7 +7,6 @@ import {
7
7
  ShareInfo,
8
8
  } from '@eeacms/volto-cca-policy/helpers';
9
9
  import { Grid } from 'semantic-ui-react';
10
- import { Fragment } from 'react';
11
10
 
12
11
  const dataDisplay = [
13
12
  {
@@ -1,19 +1,9 @@
1
1
  import { mergeConfig } from '@eeacms/search';
2
2
  import { build_runtime_mappings } from '@eeacms/volto-globalsearch/utils';
3
+ import { getClientProxyAddress } from './utils';
3
4
 
4
5
  import facets from './facets';
5
6
 
6
- // import views from './views';
7
- // import filters from './filters';
8
- // import vocabs from './vocabulary';
9
-
10
- const getClientProxyAddress = () => {
11
- const url = new URL(window.location);
12
- url.pathname = '';
13
- url.search = '';
14
- return url.toString();
15
- };
16
-
17
7
  const ccaConfig = {
18
8
  title: 'ClimateAdapt Main',
19
9
  };
@@ -186,7 +176,5 @@ export default function installMainSearch(config) {
186
176
  process.env.RAZZLE_ES_PROXY_ADDR || getClientProxyAddress();
187
177
  }
188
178
 
189
- // console.log(config.searchui.ccaSearch);
190
-
191
179
  return config;
192
180
  }
@@ -1,19 +1,10 @@
1
1
  import { mergeConfig } from '@eeacms/search';
2
2
  import { build_runtime_mappings } from '@eeacms/volto-globalsearch/utils';
3
+ import { getClientProxyAddress } from './../utils';
3
4
 
4
5
  import facets from './facets-health';
5
6
  import views from './views-health';
6
7
 
7
- // import filters from './filters';
8
- // import vocabs from './vocabulary';
9
-
10
- const getClientProxyAddress = () => {
11
- const url = new URL(window.location);
12
- url.pathname = '';
13
- url.search = '';
14
- return url.toString();
15
- };
16
-
17
8
  const ccaConfig = {
18
9
  title: 'ClimateAdapt Health',
19
10
  ...views,
@@ -1,5 +1,5 @@
1
1
  import { booleanFacet } from '@eeacms/search';
2
- import { getTodayWithTime } from './utils';
2
+ import { getTodayWithTime } from './../utils';
3
3
 
4
4
  const special = [
5
5
  {
@@ -1,30 +1,21 @@
1
1
  import installMainSearch from './config';
2
- import installHealthSearch from './config-health';
3
- import installMissionStoriesSearch from './config-mission-stories';
4
-
5
- // import DatahubCardItem from './components/Result/DatahubCardItem';
6
- // import DatahubItemView from './components/ItemView/ItemView';
7
- //
8
- // import { DatahubResultModel } from './config/models';
9
- //
10
- // import { datahub_results } from './store';
11
-
12
- // function tweakForNLPService(body, config) {
13
- // if (!config.enableNLP) {
14
- // delete body.source;
15
- // delete body.index;
16
- // return body;
17
- // }
18
- // return body;
19
- // }
2
+ import installHealthSearch from './health_observatory/config-health';
3
+ import installMissionStoriesSearch from './mission_stories/config-stories';
4
+ import installMissionProjectsSearch from './mission_projects/config-projects';
20
5
 
21
6
  const applyConfig = (config) => {
22
7
  config.settings.searchlib = installHealthSearch(
23
8
  installMainSearch(config.settings.searchlib),
24
9
  );
10
+
25
11
  config.settings.searchlib = installMissionStoriesSearch(
26
12
  config.settings.searchlib,
27
13
  );
14
+
15
+ config.settings.searchlib = installMissionProjectsSearch(
16
+ installMainSearch(config.settings.searchlib),
17
+ );
18
+
28
19
  return config;
29
20
  };
30
21
 
@@ -0,0 +1,69 @@
1
+ import { mergeConfig } from '@eeacms/search';
2
+ import { build_runtime_mappings } from '@eeacms/volto-globalsearch/utils';
3
+ import { getClientProxyAddress } from './../utils';
4
+
5
+ import facets from './facets-projects';
6
+
7
+ const missionProjectsConfig = {
8
+ title: 'Mission Projects',
9
+ };
10
+
11
+ export const clusters = {
12
+ name: 'op_cluster',
13
+ field: 'objectProvides',
14
+ clusters: [
15
+ // {
16
+ // name: 'Type1',
17
+ // icon: { name: 'bullhorn' },
18
+ // values: ['Video', 'Guidance'],
19
+ // defaultResultView: 'horizontalCard',
20
+ // },
21
+ ],
22
+ };
23
+
24
+ export default function installMainSearch(config) {
25
+ const envConfig = process.env.RAZZLE_ENV_CONFIG
26
+ ? JSON.parse(process.env.RAZZLE_ENV_CONFIG)
27
+ : missionProjectsConfig;
28
+
29
+ const pjson = require('@eeacms/volto-cca-policy/../package.json');
30
+
31
+ envConfig.app_name = pjson.name;
32
+ envConfig.app_version = pjson.version;
33
+
34
+ config.searchui.missionProjects = {
35
+ ...mergeConfig(envConfig, config.searchui.globalsearchbase),
36
+ elastic_index: '_es/globalsearch',
37
+ index_name: 'data_searchui',
38
+ host: process.env.RAZZLE_ES_PROXY_ADDR || 'http://localhost:3000',
39
+ vocab: {
40
+ cluster_name: {
41
+ cca: 'Climate-ADAPT',
42
+ },
43
+ },
44
+ runtime_mappings: build_runtime_mappings(clusters),
45
+ };
46
+
47
+ const { missionProjects } = config.searchui;
48
+
49
+ missionProjects.permanentFilters.push({
50
+ term: {
51
+ cca_include_in_mission: 'true',
52
+ },
53
+ });
54
+
55
+ missionProjects.permanentFilters.push({
56
+ terms: {
57
+ objectProvides: ['Research and knowledge project'],
58
+ },
59
+ });
60
+
61
+ missionProjects.facets = facets;
62
+
63
+ if (typeof window !== 'undefined') {
64
+ config.searchui.missionProjects.host =
65
+ process.env.RAZZLE_ES_PROXY_ADDR || getClientProxyAddress();
66
+ }
67
+
68
+ return config;
69
+ }
@@ -0,0 +1,30 @@
1
+ import { multiTermFacet } from '@eeacms/search';
2
+
3
+ import globalSearchBaseConfig from '@eeacms/volto-globalsearch/config/global-search-base-config.js';
4
+
5
+ const facets = [
6
+ ...globalSearchBaseConfig.facets,
7
+ multiTermFacet({
8
+ field: 'cca_adaptation_sectors.keyword',
9
+ isFilterable: false,
10
+ isMulti: true,
11
+ label: 'Sectors',
12
+ alwaysVisible: false,
13
+ }),
14
+ multiTermFacet({
15
+ field: 'cca_climate_impacts.keyword',
16
+ isFilterable: false,
17
+ isMulti: true,
18
+ label: 'Climate impacts',
19
+ alwaysVisible: false,
20
+ }),
21
+ multiTermFacet({
22
+ field: 'cca_adaptation_elements.keyword',
23
+ isFilterable: false,
24
+ isMulti: true,
25
+ label: 'Adaptation Elements',
26
+ alwaysVisible: false,
27
+ }),
28
+ ];
29
+
30
+ export default facets;
@@ -1,18 +1,10 @@
1
1
  import { mergeConfig } from '@eeacms/search';
2
-
3
- import facets from './facets-mission-stories';
4
-
5
- const getClientProxyAddress = () => {
6
- const url = new URL(window.location);
7
- url.pathname = '';
8
- url.search = '';
9
- return url.toString();
10
- };
2
+ import { getClientProxyAddress } from './../utils';
3
+ import facets from './facets-stories';
11
4
 
12
5
  const missionStoriesConfig = {
13
6
  title: 'Mission stories',
14
7
  ...facets,
15
- // ...views,
16
8
  };
17
9
 
18
10
  export default function installMissionStoriesSearch(config) {
@@ -27,3 +27,10 @@ export function getTodayWithTime() {
27
27
  ].join('');
28
28
  return output;
29
29
  }
30
+
31
+ export function getClientProxyAddress() {
32
+ const url = new URL(window.location);
33
+ url.pathname = '';
34
+ url.search = '';
35
+ return url.toString();
36
+ }
@@ -0,0 +1,11 @@
1
+ import { getTodayWithTime } from './utils';
2
+ import '@testing-library/jest-dom/extend-expect';
3
+
4
+ describe('getTodayWithTime', () => {
5
+ it('should return the current date in UTC format', () => {
6
+ const output = getTodayWithTime();
7
+
8
+ expect(typeof output).toBe('string');
9
+ expect(output).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
10
+ });
11
+ });