-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: add find and replace for passport variable via cli prompts #9
Merged
Mike-Heneghan
merged 5 commits into
main
from
mh/add-script-for-updating-outbuildings-to-outbuilding
Feb 19, 2024
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
15865be
feat: add find and replace for passport variable via cli prompts
Mike-Heneghan 769d674
chore: add missing start script
Mike-Heneghan 4bd0128
fix: update replace to match all occurrences and update values rather…
Mike-Heneghan 7fcb89b
chore: rename subdirectory
Mike-Heneghan f073806
refactor: update from regex replace to replaceAll
Mike-Heneghan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* Find a current passport variable (fn) or (val) and replace it in each node in a flow (live or published | ||
*/ | ||
|
||
function replaceAllOccurrences(fullPassportValue, currentPassportVariable, newPassportVariable) { | ||
const regex = new RegExp('\\b' + currentPassportVariable + '\\b', 'g'); | ||
return fullPassportValue.replace(regex, newPassportVariable); | ||
} | ||
|
||
const updateNodeFn = (flowData, currentPassportVariable, newPassportVariable) => { | ||
let newFlowData = flowData; | ||
Object.entries(flowData) | ||
.filter(([_nodeId, nodeData]) => nodeData?.["data"]?.["fn"] || nodeData?.["data"]?.["val"]) | ||
.forEach(([nodeId, nodeData]) => { | ||
const passportKey = nodeData["data"]["fn"] ? "fn" : "val" | ||
const currentFn = nodeData["data"][`${passportKey}`] | ||
newFlowData[nodeId]["data"][`${passportKey}`] = replaceAllOccurrences(currentFn, currentPassportVariable, newPassportVariable) | ||
}) | ||
return newFlowData; | ||
} | ||
|
||
module.exports = { updateNodeFn }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
const ask = require("prompt"); | ||
const chalk = require("chalk"); | ||
const Client = require("../client"); | ||
|
||
const { findAndReplacePrompts } = require("./prompts"); | ||
const { updateNodeFn } = require("./helpers"); | ||
|
||
ask.start(); | ||
|
||
(async function go() { | ||
// greeting | ||
console.log( | ||
chalk.cyan( | ||
`Hello! These prompts will step you through updating a PlanX passport variables.\nType values when prompted or click 'enter' to accept ${chalk.white( | ||
"(default)" | ||
)} values.\n~~~~~~~~~~~~~~ ${chalk.bold("LET'S START")} ~~~~~~~~~~~~~~` | ||
) | ||
); | ||
|
||
// authentication & setup | ||
const { hasuraEnvironment, hasuraSecret, flowSlug, currentPassportVariable, newPassportVariable } = await ask.get( | ||
findAndReplacePrompts | ||
); | ||
const url = { | ||
production: "https://hasura.editor.planx.uk/v1/graphql", | ||
staging: "https://hasura.editor.planx.dev/v1/graphql", | ||
local: "http://localhost:7100/v1/graphql", | ||
}; | ||
|
||
// create graphQL client | ||
const client = new Client({ | ||
hasuraSecret, | ||
targetURL: url[hasuraEnvironment], | ||
}); | ||
|
||
const formattedSlug = flowSlug.toLowerCase().trim().replaceAll(" ", "-"); | ||
|
||
// Fetch flows matching slugs | ||
const flows = await client.getFlowData(formattedSlug); | ||
if (flows?.length > 0) { | ||
console.log(chalk.white(`Fetched ${flows.length} flows`)); | ||
|
||
flows.forEach(async (flow, i) => { | ||
let liveFlowData; | ||
let publishedFlowData; | ||
let response; | ||
|
||
try { | ||
if (flow.publishedFlows.length > 0) { | ||
// Proceed with migration for flows that are published | ||
console.log(chalk.white(`Updating published flow ${i+1}/${flows.length}: ${flow.team.slug}/${flow.slug}`)); | ||
|
||
// Find nodes in live flow data, update them | ||
// This does NOT require a corresponding operation because we are not creating the flow for the first time | ||
liveFlowData = updateNodeFn(flow.data, currentPassportVariable, newPassportVariable); | ||
|
||
// Find nodes in published flow data, update them directly too | ||
publishedFlowData = updateNodeFn(flow.publishedFlows?.[0]?.data, currentPassportVariable, newPassportVariable); | ||
|
||
// Write update in a single mutation block for postgres transaction-like rollback behavior on error | ||
response = await client.updateFlowAndPublishedFlow(flow.id, liveFlowData, flow.publishedFlows?.[0]?.id, publishedFlowData); | ||
if (response?.update_flows_by_pk?.id) { | ||
console.log( | ||
chalk.green(`Successfully updated flow: ${flow.team.slug}/${flow.slug}`) | ||
); | ||
} | ||
if (response?.update_published_flows_by_pk?.id) { | ||
console.log( | ||
chalk.green(`Successfully updated published version of flow: ${flow.team.slug}/${flow.slug}`) | ||
); | ||
} | ||
} else { | ||
// Proceed with migration for flows that are not published | ||
console.log(chalk.white(`Updating unpublished flow ${i+1}/${flows.length}: ${flow.team.slug}/${flow.slug}`)); | ||
|
||
// Find nodes in live flow data, update them | ||
// This does NOT require a corresponding operation because we are not creating the flow for the first time | ||
liveFlowData = updateNodeFn(flow.data, currentPassportVariable, newPassportVariable); | ||
|
||
// Write update | ||
response = await client.updateFlow(flow.id, liveFlowData); | ||
if (response?.update_flows_by_pk?.id) { | ||
console.log( | ||
chalk.green(`Successfully updated flow: ${flow.team.slug}/${flow.slug}`) | ||
); | ||
} | ||
} | ||
} catch (error) { | ||
console.log(chalk.red(error)); | ||
} | ||
}); | ||
} else { | ||
console.log(chalk.red(`Cannot find any flows matching slug: ${formattedSlug}. Exiting migration script`)); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const { setupPrompts } = require("../prompts"); | ||
|
||
const findAndReplacePrompts= [ | ||
...setupPrompts, | ||
{ | ||
name: "currentPassportVariable", | ||
description: "What is the current passport variable to be replaced?", | ||
default: "outbuildings", | ||
type: "string", | ||
required: true, | ||
}, | ||
{ | ||
name: "newPassportVariable", | ||
description: "What should this passport variable be replaced with?", | ||
default: "outbuilding", | ||
type: "string", | ||
required: true, | ||
}, | ||
|
||
]; | ||
|
||
module.exports = { findAndReplacePrompts }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Sorry - still not personally convinced this regex that protects against partial matches is better approach than plain string
replaceAll()
and I think is going to be ultimately limiting in use of this against "real" content examples!In your example
alter
→change
(but notalteration
→changeation
) that this regex protects against, I think you're overlooking the option to simply passalter.
as the find variable thenreplaceAll()
as expected !!In recent file naming, there were other examples of partial matches like
councilTaxBill
(find =councilTax
) →councilBill
(replace =council
) that wouldn't be supported by this regex.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.
I'm maybe being too risk averse with this approach. As far as I understand the regex would approach would still allow all renaming but might require more runs as matches would need to be made explicitly
councilTaxBill → councilBill
vs the freedom / risk of partial match renames.Passing in variables with
.
as inalter.
is definitely something I hadn't considered and does overcome the issue I'd outlined although I guess it does require protecting against edge cases at the time of running the code and a deeper familiarity with the project types versus having that edge case protection in the code?Although, I'm happy to defer to your judgement though as you're much more familiar with this process and the data.
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.
Updated here: f073806
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.
Thanks ! My theory on these is: always start quick & flexible based on known content examples, and introduce strictness as future use cases necessitate. Because since these scripts are only manually run in very controlled scenarios right now (eg not exposed to editors), we don't need to get hung up anticipating uses beyond the task at hand!