-
Notifications
You must be signed in to change notification settings - Fork 7
WIP: collect metadata for each task #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import path from "path"; | |
|
|
||
| import { generateStyle, generateMBTiles } from "./generate_resources.js"; | ||
| import { requestOnlineTiles } from "./download_resources.js"; | ||
| import { handleError } from "./utils.js"; | ||
|
|
||
| const MBTILES_REGEXP = /mbtiles:\/\/(\S+?)(?=[/"]+)/gi; | ||
|
|
||
|
|
@@ -23,36 +24,54 @@ export const initiateRendering = async ( | |
| ) => { | ||
| console.log("Initiating rendering..."); | ||
|
|
||
| const workBegun = new Date().toISOString(); | ||
|
|
||
| const tempDir = path.join(os.tmpdir(), "mapgl-tile-renderer-temp"); | ||
| if (!fs.existsSync(tempDir)) { | ||
| fs.mkdirSync(tempDir, { recursive: true }); | ||
| try { | ||
| fs.mkdirSync(tempDir, { recursive: true }); | ||
| } catch (error) { | ||
| return handleError(error, "creating the temporary directory"); | ||
| } | ||
| } | ||
| let stylePath = null; | ||
| let styleObject = null; | ||
|
|
||
| // If the style is self-hosted, let's read the style from the file. | ||
| if (style === "self") { | ||
| stylePath = path.resolve(process.cwd(), styleDir); | ||
| styleObject = JSON.parse(fs.readFileSync(stylePath, "utf-8")); | ||
| try { | ||
| styleObject = JSON.parse(fs.readFileSync(stylePath, "utf-8")); | ||
| } catch (error) { | ||
| return handleError(error, "reading the style file"); | ||
| } | ||
| } | ||
|
|
||
| // If the style is not self-hosted, let's generate everything that we need to render tiles. | ||
| if (style !== "self") { | ||
| // Download tiles from the online source | ||
| await requestOnlineTiles( | ||
| style, | ||
| apiKey, | ||
| mapboxStyle, | ||
| monthYear, | ||
| bounds, | ||
| minZoom, | ||
| maxZoom, | ||
| tempDir, | ||
| ); | ||
| try { | ||
| await requestOnlineTiles( | ||
| style, | ||
| apiKey, | ||
| mapboxStyle, | ||
| monthYear, | ||
| bounds, | ||
| minZoom, | ||
| maxZoom, | ||
| tempDir, | ||
| ); | ||
| } catch (error) { | ||
| return handleError(error, "downloading tiles from the online source"); | ||
| } | ||
|
|
||
| // Save the overlay GeoJSON to a file, if provided | ||
| if (overlay) { | ||
| fs.writeFileSync(`${tempDir}/sources/overlay.geojson`, overlay); | ||
| try { | ||
| fs.writeFileSync(`${tempDir}/sources/overlay.geojson`, overlay); | ||
| } catch (error) { | ||
| return handleError(error, "saving the overlay GeoJSON to a file"); | ||
| } | ||
| console.log(`Overlay GeoJSON saved to file!`); | ||
| } | ||
|
|
||
|
|
@@ -66,12 +85,16 @@ export const initiateRendering = async ( | |
|
|
||
| // Generate and save a stylesheet from the online source and overlay source. | ||
| if (styleObject === null) { | ||
| styleObject = generateStyle(style, overlay, tileSize); | ||
| fs.writeFileSync( | ||
| `${tempDir}/style.json`, | ||
| JSON.stringify(styleObject, null, 2), | ||
| ); | ||
| console.log("Stylesheet generated and saved!"); | ||
| try { | ||
| styleObject = generateStyle(style, overlay, tileSize); | ||
| fs.writeFileSync( | ||
| `${tempDir}/style.json`, | ||
| JSON.stringify(styleObject, null, 2), | ||
| ); | ||
| console.log("Stylesheet generated and saved!"); | ||
| } catch (error) { | ||
| return handleError(error, "generating and saving the stylesheet"); | ||
| } | ||
| } | ||
|
|
||
| sourceDir = `${tempDir}/sources`; | ||
|
|
@@ -82,7 +105,7 @@ export const initiateRendering = async ( | |
| if (localMbtilesMatches && !sourceDir) { | ||
| const msg = | ||
| "Stylesheet has local mbtiles file sources, but no sourceDir is set"; | ||
| throw new Error(msg); | ||
| return handleError(new Error(msg), "checking for local mbtiles sources"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an example of a bad request, right? The caller has to change something before this will ever succeed. I might define (in utils.js) two separate error handlers, one for bad client request, one for unexpected server error. The two would set different errorCode (as you have it now) or different status (per an idea I share in src/utils.js). A minor variation of this is to define two different Error subclasses and you only
|
||
| } | ||
|
|
||
| if (localMbtilesMatches) { | ||
|
|
@@ -99,13 +122,16 @@ export const initiateRendering = async ( | |
| name, | ||
| ext: ".mbtiles", | ||
| })} in stylesheet is not found in: ${path.resolve(sourceDir)}`; | ||
| throw new Error(msg); | ||
| return handleError( | ||
| new Error(msg), | ||
| "checking for local mbtiles sources", | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| try { | ||
| await generateMBTiles( | ||
| let metadata = await generateMBTiles( | ||
| styleObject, | ||
| styleDir, | ||
| sourceDir, | ||
|
|
@@ -116,8 +142,31 @@ export const initiateRendering = async ( | |
| outputDir, | ||
| outputFilename, | ||
| ); | ||
|
|
||
| console.log( | ||
| `\x1b[32m${outputFilename}.mbtiles has been successfully generated!\x1b[0m`, | ||
| ); | ||
|
|
||
| // if successful, return the metadata | ||
| return { | ||
| style: style, | ||
| status: metadata.status, | ||
| errorCode: metadata.errorCode, | ||
| errorMessage: metadata.errorMessage, | ||
| filename: metadata.filename, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to be an absolute or relative path? If absolute, is it relative to the local volume mount? A URL? I think I might return a relative path from under the volume mount (i.e. not including the volume mount top level dir); that way the webapp server and this task worker might use different local mount locations, but below that the relative path works in both. An Azure storage URL might be more ideal except that technically the task worker doesn't know about Azure Storage; that's why I might lean away from it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, relative path. GCV works the same way - we provide the mount location as an env var, and we can do similar for the MapPacker front end. |
||
| filesize: metadata.filesize, | ||
| numberOfTiles: metadata.numberOfTiles, | ||
| numberOfAttempts: 1, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking your code does not need to handle retries explicitly; rather the queuing system will retry to deliver the same message if it hadn't succeeded (been deleted from the queue by the task worker) within the lease time. All your task worker code has to do is read const response = await sourceQueueClient.receiveMessages({
visibilityTimeout: 2 * 60 * 60,
});
const message = response.receivedMessageItems[0];
const numberOfAttempts = message.dequeueCount; |
||
| workBegun, | ||
| workEnded: new Date().toISOString(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd report workBegun and workEnded even if there's error.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You know, maybe OTOH this would not be so good if it's important that the CLI keep track of these too.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think it could be interesting that we return these for the CLI. I say this from first-hand experience of running similar Node tools overnight, and not being sure when it actually finished. |
||
| // if status = success, set expiration for one year | ||
| expiration: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have any mechanism (as of now) to actually clean up expired records? If not, I'd be inclined to leave The one reason I can think of to include this right now is if the expiration will need to be surfaced in a UI well in advance.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No mechanism as of yet. I was thinking of our discussion on the MapPacker design doc to expire offline maps, and that the best opportunity for us to set an expiration timestamp would be at this stage of task finishing. But we could definitely leave this out until we're ready to tackle cleanup as a separate batch of work. |
||
| metadata.status === "success" | ||
| ? new Date(Date.now() + 31556952000).toISOString() | ||
| : null, | ||
| }; | ||
| } catch (error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not super experienced with running node scripts from the command line but my guess is that the user experience (for a technical user, anyway) might actually be better if you never catch the exception, if one was thrown. My guess is that, when running from CLI, the script will print the entire stack trace, as well as the error message, and this will be much more helpful for the technical developer to spot what went wrong and how to fix it. In other words, I'm suggesting what if (I am assuming that CLI will not ever write back to the SQL DB)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fully agree with you on this point. I'll push up a commit with a revision of when |
||
| throw new Error("Error generating MBTiles:", error); | ||
| return handleError(error, "generating MBTiles file"); | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,14 @@ export const raiseError = (msg) => { | |
| process.exit(1); | ||
| }; | ||
|
|
||
| export const handleError = (error, message) => { | ||
| return { | ||
| status: "failed", | ||
| errorCode: "500", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know I alluded to 4xx and 5xx codes being used to differentiate between "caller's fault" vs "my fault", but I was thinking of it as an analogy. Since there is no HTTP call, "code 500" feels a bit too literal? You could probably even omit Note that I would expect the webapp's That said, what you have seems clear enough that I'm also fine if you want to leave it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I too was thinking of these codes as an analogy, and wasn't sure yet where to actually go with the error codes - I would have likely rewritten them to be something more accurate on the front end anyway, and I think your proposal to expand the range of possibilities in |
||
| errorMessage: `Error ${message}: ${error.message}`, | ||
| }; | ||
| }; | ||
|
|
||
| // Currently supported list of online styles | ||
| // When adding a new style, make sure to update this list | ||
| const validOnlineStyles = [ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: choose a more specific word. "metadata" can be used for all sorts of stuff.
idea: taskResult, renderResult