@nsidc/snow-today-webapp 0.1.0 → 0.3.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # v0.3.0 (2022-08-22)
2
+
3
+ * Add automated deployment script
4
+
5
+
6
+ # v0.2.0 (2022-08-22)
7
+
8
+ * Use QA data server URL
9
+
10
+
11
+ # v0.1.1 (2022-08-22)
12
+
13
+ * Fix bug with data "leaking" between variables in the plot.
14
+
15
+
16
+ # v0.1.0 (2022-08-18)
17
+
18
+ * Initial release
package/deploy/deploy ADDED
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ ENVIRONMENT="$1"
5
+
6
+ THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ REPO_ROOT_DIR="$(dirname "$THIS_DIR")"
8
+
9
+ VERSION_FILE="$REPO_ROOT_DIR/package.json"
10
+ CANONICAL_VERSION="$(cat "$VERSION_FILE" | jq -r .version)"
11
+
12
+ source /etc/profile.d/envvars.sh
13
+
14
+ cd "$REPO_ROOT_DIR"
15
+
16
+ if [ "$ENVIRONMENT" = "integration" ]; then
17
+ APP_VERSION="latest"
18
+ version_desc="latest"
19
+ else
20
+ APP_VERSION="$CANONICAL_VERSION"
21
+ version_desc="production"
22
+ fi
23
+
24
+ echo "Using $version_desc versions in $ENVIRONMENT..."
25
+ echo "APP_VERSION=$APP_VERSION"
26
+ export APP_VERSION
27
+
28
+ # Replace the current app
29
+ docker-compose pull
30
+ docker-compose down --remove-orphans
31
+ docker-compose up -d
32
+
33
+ # Cleanup
34
+ docker system prune -af
@@ -0,0 +1,14 @@
1
+ # Releasing
2
+
3
+ The `latest` image on Dockerhub will be updated with each commit to `main`.
4
+
5
+ To release a new tagged image to DockerHub and a new version to NPM:
6
+
7
+ * Update version in `package.json`.
8
+ * Run `npm i` to update `package-lock.json`.
9
+ * Update `CHANGELOG.md` following existing convention.
10
+ * Commit
11
+ * Merge to main if necessary
12
+ * Tag the main branch: `git tag vX.Y.Z`
13
+ * Push the tag to GitHub to trigger automated releases of Docker Images and NPM bundle:
14
+ `git push origin vX.Y.Z`
@@ -3,6 +3,6 @@ version: '3.4'
3
3
  services:
4
4
  webapp:
5
5
  ports:
6
- - "80:80"
7
- - "443:443"
6
+ - "8080:80"
7
+ - "4433:443"
8
8
  restart: always
@@ -5,17 +5,7 @@ version: '3.4'
5
5
  services:
6
6
  webapp:
7
7
  image: "nsidc/snow-today-webapp:${APP_VERSION:-latest}"
8
- # TODO: Pull, don't build in non-dev
9
- build: .
10
8
  logging:
11
9
  options:
12
10
  max-size: "10m"
13
11
  max-file: "10"
14
-
15
- data-server:
16
- image: "nginx:1.23"
17
- volumes:
18
- - "/share/apps/snow-today:/usr/share/nginx/html:ro"
19
- - "./nginx/default.dataserver.conf:/etc/nginx/conf.d/default.conf:ro"
20
- ports:
21
- - "8000:80"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "description": "Snow Today Webapp",
3
3
  "name": "@nsidc/snow-today-webapp",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "private": false,
6
6
  "scripts": {
7
7
  "build": "webpack --mode=production --node-env=production",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@fontsource/roboto": "^4.5.7",
18
- "@tanstack/react-query": "^4.0.10",
18
+ "@tanstack/react-query": "^4.2.1",
19
19
  "colormap": "^2.3.2",
20
20
  "highcharts": "^10.2.0",
21
21
  "highcharts-react-official": "^3.1.0",
@@ -12,6 +12,7 @@ import selectedRegionObjectAtom from '../clientState/selectedRegionObject';
12
12
  import selectedSatelliteVariableAtom from '../clientState/selectedSatelliteVariable';
13
13
  import selectedSatelliteVariableObjectAtom from '../clientState/selectedSatelliteVariableObject';
14
14
  import usePlotDataQuery from '../serverState/plotData';
15
+ import {IPlotData} from '../types/query/plotData';
15
16
 
16
17
  HighchartsMore(Highcharts);
17
18
 
@@ -35,7 +36,7 @@ const LinePlot: React.FC = () => {
35
36
  <div className={'card centered-card-text'}><p>Loading...</p></div>
36
37
  );
37
38
  } else if (plotDataQuery.isError) {
38
- console.debug(`Error!: ${plotDataQuery.error as string}`);
39
+ console.debug(`Error!: ${String(plotDataQuery.error)}`);
39
40
  const regionStr = selectedSatelliteVariableObject.longname;
40
41
  const varStr = selectedRegionObject.longname;
41
42
  return (
@@ -63,18 +64,38 @@ const LinePlot: React.FC = () => {
63
64
  const chartTitle = `${regionLongname} - ${varLongname}`;
64
65
  const yAxisTitle = `${varLongname} (${varUnit})`;
65
66
 
67
+ // WARNING: It is _critical_ that the data is copied before passing to
68
+ // Highcharts. Highcharts will mutate the arrays, and we don't want state to
69
+ // be mutated!!!
70
+ type IPlotDataMutable = {
71
+ [Property in keyof IPlotData]: number[];
72
+ }
73
+ // TODO: Is there a programmatic way to do this object transformation
74
+ // *WITHOUT* type casting? This is not friendly to maintain. Easy to
75
+ // make mistakes with values, but Typescript protects us from messing up
76
+ // structure (keys and types of values).
77
+ const data: IPlotDataMutable = {
78
+ day_of_water_year: [...plotDataQuery.data['day_of_water_year']],
79
+ max: [...plotDataQuery.data['max']],
80
+ median: [...plotDataQuery.data['median']],
81
+ min: [...plotDataQuery.data['min']],
82
+ prc25: [...plotDataQuery.data['prc25']],
83
+ prc75: [...plotDataQuery.data['prc75']],
84
+ year_to_date: [...plotDataQuery.data['year_to_date']],
85
+ };
86
+
66
87
  const chartData: Highcharts.SeriesOptionsType[] = [
67
88
  {
68
89
  name: 'Year to date',
69
90
  type: 'line',
70
- data: plotDataQuery.data['year_to_date'],
91
+ data: data['year_to_date'],
71
92
  zIndex: 99,
72
93
  // color: '#000000',
73
94
  },
74
95
  {
75
96
  name: 'Median',
76
97
  type: 'line',
77
- data: plotDataQuery.data['median'],
98
+ data: data['median'],
78
99
  zIndex: 10,
79
100
  color: '#8d8d8d',
80
101
  dashStyle: 'Dash',
@@ -82,7 +103,7 @@ const LinePlot: React.FC = () => {
82
103
  {
83
104
  name: 'Maximum',
84
105
  type: 'line',
85
- data: plotDataQuery.data['max'],
106
+ data: data['max'],
86
107
  zIndex: 9,
87
108
  color: '#666666',
88
109
  dashStyle: 'ShortDashDot',
@@ -90,7 +111,7 @@ const LinePlot: React.FC = () => {
90
111
  {
91
112
  name: 'Minimum',
92
113
  type: 'line',
93
- data: plotDataQuery.data['min'],
114
+ data: data['min'],
94
115
  zIndex: 8,
95
116
  color: '#666666',
96
117
  dashStyle: 'ShortDot',
@@ -98,8 +119,8 @@ const LinePlot: React.FC = () => {
98
119
  {
99
120
  name: 'Interquartile Range',
100
121
  type: 'arearange',
101
- data: plotDataQuery.data['prc25'].map((low, index) => {
102
- return [low, plotDataQuery.data['prc75'][index]];
122
+ data: data['prc25'].map((low, index) => {
123
+ return [low, data['prc75'][index]];
103
124
  }),
104
125
  zIndex: 0,
105
126
  lineWidth: 0,
@@ -1,6 +1,6 @@
1
1
  // TODO: Should we just develop an API that serves the correct TIF or GeoJSON
2
2
  // (or other data)?
3
- export const dataServerUrl = 'http://integration.snow-today.apps.int.nsidc.org:8000'
3
+ export const dataServerUrl = 'http://qa.snow-today.apps.int.nsidc.org'
4
4
 
5
5
  // Information about regions:
6
6
  export const regionsIndexUrl = `${dataServerUrl}/regions.json`;
@@ -20,6 +20,7 @@ const usePlotDataQuery = (
20
20
  {
21
21
  enabled: !!regionId && !!variableId,
22
22
  // Never re-fetch this data!
23
+ cacheTime: Infinity,
23
24
  staleTime: Infinity,
24
25
  // Don't retry failed requests; in this case there is no plot!
25
26
  retry: false,
@@ -16,6 +16,7 @@ const useRegionShape = (regionShapeFilePath: string | undefined) => useQuery<obj
16
16
  {
17
17
  enabled: !!regionShapeFilePath,
18
18
  // Never re-fetch this data!
19
+ cacheTime: Infinity,
19
20
  staleTime: Infinity,
20
21
  },
21
22
  );
@@ -19,6 +19,7 @@ const useRegionsIndex = (
19
19
  // or the state will keep getting re-set...
20
20
  onSuccess: (data: IRegionIndex) => stateSetter(Object.keys(data)[0]),
21
21
  // Never re-fetch this data!
22
+ cacheTime: Infinity,
22
23
  staleTime: Infinity,
23
24
  }
24
25
  );
@@ -19,6 +19,7 @@ const useVariablesIndex = (
19
19
  // or the state will keep getting re-set...
20
20
  onSuccess: (data: ISatelliteVariableIndex) => stateSetter(Object.keys(data)[0]),
21
21
  // Never re-fetch this data!
22
+ cacheTime: Infinity,
22
23
  staleTime: Infinity,
23
24
  }
24
25
  );
@@ -1,9 +1,9 @@
1
1
  export interface IPlotData {
2
- day_of_water_year: number[];
3
- max: number[];
4
- median: number[];
5
- min: number[];
6
- prc25: number[];
7
- prc75: number[];
8
- year_to_date: number[];
2
+ readonly day_of_water_year: readonly number[];
3
+ readonly max: readonly number[];
4
+ readonly median: readonly number[];
5
+ readonly min: readonly number[];
6
+ readonly prc25: readonly number[];
7
+ readonly prc75: readonly number[];
8
+ readonly year_to_date: readonly number[];
9
9
  }
package/webpack.config.js CHANGED
@@ -63,6 +63,9 @@ const config = {
63
63
  resolve: {
64
64
  extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
65
65
  },
66
+ stats: {
67
+ errorDetails: true,
68
+ },
66
69
  };
67
70
 
68
71
  module.exports = () => {
@@ -1,25 +0,0 @@
1
- server {
2
- listen 80;
3
-
4
- server_name localhost;
5
-
6
- real_ip_header X-Forwarded-For;
7
- real_ip_recursive on;
8
-
9
- location / {
10
- root /usr/share/nginx/html;
11
- autoindex on;
12
-
13
- add_header 'Access-Control-Allow-Origin' '*';
14
-
15
- # Hacky support for CORS pre-flight OPTIONS requests
16
- if ($request_method = OPTIONS) {
17
- add_header 'Access-Control-Allow-Origin' '*';
18
- add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
19
- add_header 'Access-Control-Allow-Headers' 'Range, Origin, X-Requested-With, Content-Type, Accept';
20
- add_header 'Content-Type' 'text/plain';
21
- add_header 'Content-Length' '0';
22
- return 204;
23
- }
24
- }
25
- }