Skip to content

Commit

Permalink
Advanced collection (#32)
Browse files Browse the repository at this point in the history
* Implemented Collection header update

* Unable to sort folders

* Detect folder sort changes to deploy collection header + check requests and responses object

* Deploy advanced collection

* remove dependency on classic collections for the deploy of the advanced collection
  • Loading branch information
barduinor authored Sep 19, 2023
1 parent d40d7f6 commit ca6a835
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 34 deletions.
43 changes: 42 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,45 @@ jobs:
SLACK_USERNAME: GitHub Actions
SLACK_AVATAR: "https://avatars3.githubusercontent.com/u/8659759?s=200&v=4"
with:
args: "Deployed Japanese Postman collection :rocket:"
args: "Deployed Japanese Postman collection :rocket:"


# Deploys the Advanced version
deploy-adv:
runs-on: ubuntu-latest
timeout-minutes: 20
# needs: deploy-jp

env:
LOCALES: "en"
JP_OAS3_REPO: "https://github.com/box/box-openapi.git#jp"
PRIVATE_EN_POSTMAN_COLLECTION_ID: "8119550-1ca6cb64-7560-4f24-a2b5-89ba095b1c17"
PUBLIC_EN_POSTMAN_COLLECTION_ID: "8119550-373aba62-5af5-459b-b9a4-e9db77f947a5"
POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }}

strategy:
matrix:
node-version: [18.x]

steps:
- name: Check out the repo
uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- name: Deploy the collection
run: |
yarn install
yarn releaseAdvanced en
- name: Send Slack notification
uses: Ilshidur/[email protected]
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_USERNAME: GitHub Actions
SLACK_AVATAR: "https://avatars3.githubusercontent.com/u/8659759?s=200&v=4"
with:
args: "Deployed Advanced Postman collection :rocket:"
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"prebuild:all": "yarn prebuild",
"convert": "node -e 'require(\"./src/scripts/convert.js\").convert()'",
"convert:all": "node -e 'require(\"./src/scripts/convert.js\").convertAll()'",
"prereleaseAdvanced": "yarn build:all",
"prerelease": "yarn build:all",
"prerelease:all": "yarn build:all",
"release": "node -e 'require(\"./src/scripts/release.js\").release()'",
Expand Down
2 changes: 1 addition & 1 deletion src/CollectionAdvanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class CollectionAdvanced extends Collection {
{
key: 'token',
value: '{{access_token}}',
type: 'any'
type: 'string'
}

]
Expand Down
169 changes: 139 additions & 30 deletions src/DeployIncremental.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,42 @@ const deployIncremental = async (privateRemoteCollectionId, localCollection, pub

console.log('Incremental deployment of collection ', localCollection.info.name)

const collectioHeadHasChanged = await upadteCollectionHead(remoteCollection, localCollection)
// const collectioHeadHasChanged =
await upadteCollectionHead(remoteCollection, localCollection)

remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
const foldersHaveChanged = await mergeFolders(remoteCollection, localCollection)

// const foldersHaveChanged =
await mergeFolders(remoteCollection, localCollection)

remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
const requestsHaveChanged = await mergeRequests(remoteCollection, localCollection)

// const requestsHaveChanged =
await mergeRequests(remoteCollection, localCollection)

remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId)
const responsesHaveChanged = await mergeResponses(remoteCollection, localCollection)

if (collectioHeadHasChanged || foldersHaveChanged || requestsHaveChanged || responsesHaveChanged) {
const msg = 'Merging to public collection'
console.log('\n' + msg + '...')
await new pmAPI.Collection(privateRemoteCollectionId).merge(publicRemoteCollectionId)
.then(() => { console.log(msg, '-> OK\n') })
.catch((error) => {
console.log(msg, '-> FAIL')
handlePostmanAPIError(error)
})
}
// const responsesHaveChanged =
await mergeResponses(remoteCollection, localCollection)

// should we always merge into the public collection?
// There is teh case that if an error happens in the merge phase
// the private collection is fully updated
// and in the next run the public collection will NOT be updated
// because there are no changes in the private collection

// if (!(collectioHeadHasChanged || foldersHaveChanged || requestsHaveChanged || responsesHaveChanged)) {
// console.log('Incremental deployment of collection ', localCollection.info.name, ' completed\n\n')
// return
// }
const msg = 'Merging to public collection'
console.log('\n' + msg + '...')
await new pmAPI.Collection(privateRemoteCollectionId).merge(publicRemoteCollectionId)
.then(() => { console.log(msg, '-> OK\n') })
.catch((error) => {
console.log(msg, '-> FAIL')
handlePostmanAPIError(error)
})
console.log('Incremental deployment of collection ', localCollection.info.name, ' completed\n\n')
}

Expand All @@ -56,22 +71,36 @@ async function upadteCollectionHead (remoteCollection, localCollection) {
const localEmptyCollection = { ...localCollection }
localEmptyCollection.item = []

// Check changes in info
const hasChangesInfo = checkInfoChanges(remoteCollection, localCollection)

// Check if there are changes in the Authorization
const hasChangesAuth = checkObjectChanges(remoteCollection.collection.auth, localEmptyCollection.auth)

// Check if there are changes in the Scripts (pre-request and tests)
const hasChangesScripts = checkScriptChanges(remoteCollection, localEmptyCollection)
const hasChangesPreRequestScript = checkScriptChanges('prerequest', remoteCollection, localEmptyCollection)

const hasChangesTestScript = checkScriptChanges('test', remoteCollection, localEmptyCollection)

// Check if there are changes in the Variables
const hasChangesVariables = checkVariableChanges(remoteCollection, localEmptyCollection)

const hasChanges = hasChangesAuth || hasChangesScripts || hasChangesVariables
const hasFolderSortChanges = checkFolderSortChanges(remoteCollection, localCollection)

const hasChanges = (
hasFolderSortChanges ||
hasChangesInfo ||
hasChangesAuth ||
hasChangesPreRequestScript ||
hasChangesTestScript ||
hasChangesVariables
)

if (hasChanges) {
const msg = 'Updating collection head'
console.log('\n' + msg + '...')
await new pmAPI.Collection(remoteCollection.collection.info.uid)
.update({ collection: localCollection })
.update({ collection: localEmptyCollection })
.then(() => { console.log(msg, '-> OK\n') })
.catch((error) => {
console.log(msg, '-> FAIL')
Expand All @@ -81,30 +110,85 @@ async function upadteCollectionHead (remoteCollection, localCollection) {
return hasChanges
}

const checkFolderSortChanges = (remoteCollection, localCollection) => {
const remoteFolders = remoteCollection.collection.item
.map(folder => ({ id: folder.id }))
const localFolders = localCollection.item
.map(folder => ({ id: folder.id }))

const remoteFoldersHash = GenID(JSON.stringify(remoteFolders))
const localFoldersHash = GenID(JSON.stringify(localFolders))

return remoteFoldersHash !== localFoldersHash
}

const checkInfoChanges = (remoteCollection, localEmptyCollection) => {
// collection info does not have a specific id
// so we need to generate a hash and compare them
// The hash is only beig generated for name, description and schema

const { name, description, schema } = remoteCollection.collection.info
const remoteInfo = { name, description, schema }

const { name: localName, description: localDescription, schema: localSchema } = localEmptyCollection.info
const localInfo = { name: localName, description: localDescription, schema: localSchema }

const remoteInfoHash = calculateHash(JSON.stringify(remoteInfo))
const localInfoHash = calculateHash(JSON.stringify(localInfo))

return remoteInfoHash !== localInfoHash
}

const checkObjectChanges = (remoteCollectionObject, localCollectionObject) => {
if (!remoteCollectionObject && !localCollectionObject) {
return false
}
if (!remoteCollectionObject || !localCollectionObject) {
return true
}

// certain object like auth do not have an id,
// so we need to generate on and compare them
const remoteCollectionAuthID = GenID(JSON.stringify(remoteCollectionObject))
const localCollectionAuthID = GenID(JSON.stringify(localCollectionObject))
return remoteCollectionAuthID !== localCollectionAuthID
}

const checkScriptChanges = (remoteCollection, localCollection) => {
let remoteScript = null
let localScript = null
if (remoteCollection.collection.event) {
remoteScript = JSON.stringify(remoteCollection.collection.event)
const checkScriptChanges = (scriptType, remoteCollection, localCollection) => {
const remoteScript = remoteCollection.collection.event.find(event => event.listen === scriptType)
const localScript = localCollection.event.find(event => event.listen === scriptType)

if (!remoteScript && !localScript) {
return false
}
if (localCollection.event) {
localScript = JSON.stringify(localCollection.event)
if (!remoteScript || !localScript) {
return true
}
// files can be big, so we hash them
const remoteHash = calculateHash(remoteScript.script.exec[0])
const localHash = calculateHash(localScript.script.exec[0])

return GenID(remoteScript) !== GenID(localScript)
return remoteHash !== localHash
}

const checkVariableChanges = (remoteCollection, localCollection) => {
const remoteVariablesHash = GenID(JSON.stringify(remoteCollection.collection.variable))
const localVariablesHash = GenID(JSON.stringify(localCollection.variable))
const remoteVariables = remoteCollection.collection.variable
const localVariables = localCollection.variable.map(variable => ({ key: variable.key, value: variable.value }))

// check if null
if (!remoteVariables && !localVariables) {
return false
}
if (!remoteVariables || !localVariables) {
return true
}

// although the local collection does have a deterministic id
// the remote variable looses that value when it is updated
// so we need to generate an id for the remote variable

const remoteVariablesHash = GenID(remoteVariables)
const localVariablesHash = GenID(localVariables)

return remoteVariablesHash !== localVariablesHash
}
Expand Down Expand Up @@ -154,6 +238,21 @@ async function mergeFolders (remoteCollection, localCollection) {
handlePostmanAPIError(error)
})
}

// sort folders is not supported for now
// const order = localFolders.map(folder => folder.id)
// const msg = ' Sorting folders'

// // create a temporsary root folder
// const rootFolder = await new pmAPI.Folder(remoteCollection.collection.info.uid)
// .create({ id: GenID(), name: 'root', folders: order })
// .catch((error) => {
// console.log(msg, '-> FAIL')
// handlePostmanAPIError(error)
// })
// console.log('root folder', rootFolder)
// // move all remote folders into root folder

return hasChanges
}

Expand Down Expand Up @@ -187,16 +286,20 @@ async function mergeRequests (remoteCollection, localCollection) {
for (const request of newRequests) {
const pmRequest = pmConvert.requestFromLocal(request)
const msg = ` Creating new request [${request.name}]`
// console.log('request: \n', JSON.stringify(request, 2))

await new pmAPI.Request(remoteCollection.collection.info.uid)
.create(pmRequest, localFolder.id)
.then(() => {
.then((req) => {
console.log(msg, '-> OK')
return req
})
.catch((error) => {
console.log(msg, '-> FAIL')
handlePostmanAPIError(error)
})
// console.log('\nequest', request)
// console.log('\npmRequest', pmRequest)
// console.log('\nremoteRequest', remoteRequest)
}

// delete old requests
Expand Down Expand Up @@ -320,6 +423,7 @@ async function refreshRemoteCollection (remoteCollectionID) {
})
return remoteCollection
}

// Handle axios error
const handlePostmanAPIError = (error) => {
if (error.response) {
Expand All @@ -336,10 +440,15 @@ const handlePostmanAPIError = (error) => {
}
}
const { method, url, data } = error.config
console.log('REQUEST DETAILS', { method, url, data })
const smallData = data.substring(0, 1000)
console.log('REQUEST DETAILS', { method, url, smallData })
process.exit(1)
}

const calculateHash = (stringToHash) => {
return crypto.createHash('sha256').update(stringToHash).digest('hex')
}

module.exports = {
deployIncremental
}
5 changes: 3 additions & 2 deletions src/PostmanCovertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const requestFromLocal = (localRequest) => {
let headerData = []
if (localRequest.request.header) {
headerData = dataFromLocalURLEncode(localRequest.request.header)
.map((header) => ({ key: header.key, value: header.value, enabled: header.enabled, description: header.description }))
}

const request = {
Expand Down Expand Up @@ -92,7 +93,7 @@ const requestFromLocal = (localRequest) => {
}

const responseFromLocal = (localResponse) => {
// if ()
const headers = localResponse.header.map((item) => ({ key: item.key, value: item.value }))
const response = {
// owner: '8119550',
// lastUpdatedBy: '8119550',
Expand All @@ -108,7 +109,7 @@ const responseFromLocal = (localResponse) => {
detail: ''
},
// time: null,
headers: localResponse.header, //
headers, //
cookies: [],
mime: null,
text: localResponse.body, //
Expand Down

0 comments on commit ca6a835

Please sign in to comment.