fetchData.mjs 9.78 KB
Newer Older
1
2
"use strict";

3
import { getDatastreamIdFromBuildingNumber } from "./getDatastreamId.mjs";
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
 * Create URL to fetch the details of single Datastream
 * @param {String} baseUrl Base URL of the STA server
 * @param {Number} datastreamID Integer representing the Datastream ID
 * @returns {String} URL string for fetching a single Datastream
 */
const createDatastreamUrl = function (baseUrl, datastreamID) {
  if (!datastreamID) return;
  return `${baseUrl}/Datastreams(${datastreamID})`;
};

/**
 * Create URL to fetch Observations
 * @param {String} baseUrl Base URL of the STA server
 * @param {Number} datastreamID Integer representing the Datastream ID
 * @returns {String} URL string for fetching Observations
 */
const createObservationsUrl = function (baseUrl, datastreamID) {
  if (!datastreamID) return;
  return `${baseUrl}/Datastreams(${datastreamID})/Observations`;
};

27
28
29
30
31
32
33
34
35
36
/**
 * Perform a GET request to fetch a single Datastream using the Axios library
 *
 * @param {String} urlDatastream A URL that fetches a single Datastream from an STA instance
 * @returns {Promise} A promise that contains the Datastream metadata when fulfilled
 */
const getDatastream = function (urlDatastream) {
  return axios.get(urlDatastream);
};

37
38
39
40
41
42
/**
 * Perform a GET request using the Axios library
 * @param {String} urlObservations A URL that fetches Observations from an STA instance
 * @param {Object} urlParamObj The URL parameters to be sent together with the GET request
 * @returns {Promise} A promise that contains the first page of results when fulfilled
 */
43
const getObservations = function (urlObservations, urlParamObj) {
44
45
46
47
48
49
  return axios.get(urlObservations, {
    params: urlParamObj,
  });
};

/**
50
 * Extract the metadata from a single datastream
51
52
53
54
 * @async
 * @param {String} urlDatastream A URL that fetches a Datastream from an STA instance
 * @returns {Promise} A promise that contains a metadata object for a Datastream when fulfilled
 */
55
const extractMetadataFromSingleDatastream = async function (urlDatastream) {
56
57
58
59
  try {
    // Extract properties of interest
    const {
      data: { description, name, unitOfMeasurement },
60
    } = await getDatastream(urlDatastream);
61
62
63

    return { description, name, unitOfMeasurement };
  } catch (err) {
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    // Server responded with status code outside of 2xx range
    if (err.response) {
      throw new Error(
        `The request to fetch Datastream metadata was made but the server responded with: \n${err.message}`
      );
    }
    // Request was made but no response was received
    else if (err.request) {
      throw new Error(
        `The request to fetch Datastream metadata was made but no response was received: \n${err.message}`
      );
    }
    // Problem with setting up the request
    else {
      throw new Error(
        `There was a problem setting up the request to fetch Datastream metadata: \n${err.message}`
      );
    }
82
83
84
85
  }
};

/**
86
 * Extract the metadata from multiple datastreams
87
88
89
90
 * @async
 * @param {Array} datastreamsUrlArr An array that contains N Datastream URL strings
 * @returns {Promise} A promise that contains an array of N Datastream metadata objects when fulfilled
 */
91
92
93
const extractMetadataFromMultipleDatastreams = async function (
  datastreamsUrlArr
) {
94
95
96
97
98
99
100
  try {
    // Array to store our final result
    const datastreamMetadataArr = [];

    // Use for/of loop - we need to maintain the order of execution of the async operations
    for (const datastreamUrl of datastreamsUrlArr) {
      // Metadata from a single Datastream
101
      const datastreamMetadata = await extractMetadataFromSingleDatastream(
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        datastreamUrl
      );
      datastreamMetadataArr.push(datastreamMetadata);
    }

    return datastreamMetadataArr;
  } catch (err) {
    console.error(err);
  }
};

/**
 * Traverses all the pages that make up the response from a SensorThingsAPI instance. The link to the next page, if present, is denoted by the presence of a "@iot.nextLink" property in the response object. This function concatenates all the values so that the complete results are returned in one array.
 * @async
 * @param {Promise} httpGetRequestPromise Promise object resulting from an Axios GET request
 * @returns {Promise} A promise that contains an object containing results from all the pages when fulfilled
 */
const combineResultsFromAllPages = async function (httpGetRequestPromise) {
  try {
    if (!httpGetRequestPromise) return;

    const lastSuccess = await httpGetRequestPromise;

    // The "success" objects contain a "data" object which in turn has a "value" property
    // If the "data" object in turn has a "@iot.nextLink" property, then a next page exists
    if (lastSuccess.data["@iot.nextLink"]) {
      const nextLinkSuccess = await combineResultsFromAllPages(
        axios.get(lastSuccess.data["@iot.nextLink"])
      );
      // The "data" object in turn has a "value" property
      // The "value" property's value is an array
      nextLinkSuccess.data.value = lastSuccess.data.value.concat(
        nextLinkSuccess.data.value
      );
      return nextLinkSuccess;
    } else {
      return lastSuccess;
    }
  } catch (err) {
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
    // Server responded with status code outside of 2xx range
    if (err.response) {
      throw new Error(
        `The request to fetch Observations was made but the server responded with: \n${err.message}`
      );
    }
    // Request was made but no response was received
    else if (err.request) {
      throw new Error(
        `The request to fetch Observations was made but no response was received: \n${err.message}`
      );
    }
    // Problem with setting up the request
    else {
      throw new Error(
        `There was a problem setting up the request to fetch Observations: \n${err.message}`
      );
    }
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
  }
};

/**
 * Traverses all the pages that make up the response from a SensorThingsAPI instance and extracts the combined Observations
 * @async
 * @param {Promise} httpGetRequestPromise Promise object resulting from an Axios GET request
 * @returns {Promise} A promise that contains an array of Observations when fulfilled
 */
const extractCombinedObservationsFromAllPages = async function (
  httpGetRequestPromise
) {
  try {
    const successResponse = await combineResultsFromAllPages(
      httpGetRequestPromise
    );

    // Extract value array from the success response object
    const {
      data,
      data: { value: valueArr },
    } = successResponse;

    // Array that will hold the combined observations
    const combinedObservations = [];

    valueArr.forEach((val) => {
      // Each page of results will have a dataArray that holds the observations
      const { dataArray } = val;
      combinedObservations.push(...dataArray);
    });

    return new Promise((resolve, reject) => {
      resolve(combinedObservations);
    });
  } catch (err) {
    console.error(err);
  }
};

/**
 * Retrieve all the Observations from an array of Observations promises
 * @async
 * @param {Promise} observationPromiseArray An array that contains N observation promises
 * @returns {Promise} A promise that contains an array of Observations from multiple Datastreams when fulfilled
 */
const getObservationsFromMultipleDatastreams = async function (
  observationPromiseArray
) {
  try {
    // Array to store our final result
    const observationsAllDatastreamsArr = [];

    // Use for/of loop - we need to maintain the order of execution of the async operations
    for (const observationPromise of observationPromiseArray) {
      // Observations from a single Datastream
      const observations = await observationPromise;
      observationsAllDatastreamsArr.push(observations);
    }

    return observationsAllDatastreamsArr;
  } catch (err) {
    console.error(err);
  }
};

/**
 * Retrieve the metadata from a single Datastream or multiple Datastreams and the Observations corresponding to the Datastream(s)
227
 * @async
228
229
 * @param {String} baseUrl Base URL of the STA server
 * @param {Object} urlParamObj The URL parameters to be sent together with the GET request
Pithon Kabiro's avatar
Pithon Kabiro committed
230
 * @param {Array} bldgSensorSamplingRateNestedArr A N*1 array (where N >= 1) containing a nested array of buildings, sensors & sampling rates as strings, i.e. [["101", "rl", "15min"]] or [["101", "rl", "15min"], ["102", "vl", "60min"]] or [["101", "rl", "15min"], ["102", "vl", "60min"], ["225", "vl", "60min"]], etc
231
232
233
 * @returns {Promise} A promise that contains a 1*2 array (the first element is an array that contans N Observations arrays; and the second element is an array of N Datastream metadata objects) when fulfilled
 */
const getMetadataPlusObservationsFromSingleOrMultipleDatastreams =
Pithon Kabiro's avatar
Pithon Kabiro committed
234
  async function (baseUrl, urlParamObj, bldgSensorSamplingRateNestedArr) {
235
    try {
Pithon Kabiro's avatar
Pithon Kabiro committed
236
      if (!bldgSensorSamplingRateNestedArr) return;
237
238

      // Datastreams IDs
Pithon Kabiro's avatar
Pithon Kabiro committed
239
240
241
      const datastreamsIdsArr = bldgSensorSamplingRateNestedArr.map(
        (bldgSensorSamplingRateArr) =>
          getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRateArr)
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
      );

      // Observations URLs
      const observationsUrlArr = datastreamsIdsArr.map((datastreamId) =>
        createObservationsUrl(baseUrl, datastreamId)
      );

      // Datastreams URLs
      const datastreamsUrlArr = datastreamsIdsArr.map((datastreamId) =>
        createDatastreamUrl(baseUrl, datastreamId)
      );

      // Promise objects - Observations
      const observationsPromisesArr = observationsUrlArr.map((obsUrl) =>
        extractCombinedObservationsFromAllPages(
257
          getObservations(obsUrl, urlParamObj)
258
259
260
261
262
263
264
265
266
        )
      );

      // Observations array
      const observationsArr = await getObservationsFromMultipleDatastreams(
        observationsPromisesArr
      );

      // Metadata array
267
      const metadataArr = await extractMetadataFromMultipleDatastreams(
268
269
270
271
272
273
274
275
276
        datastreamsUrlArr
      );

      return [observationsArr, metadataArr];
    } catch (err) {
      console.error(err);
    }
  };

Pithon Kabiro's avatar
Pithon Kabiro committed
277
export { getMetadataPlusObservationsFromSingleOrMultipleDatastreams };