All files / modules/70-pipeline/utils runPipelineUtils.ts

87.76% Statements 86/98
50.93% Branches 82/161
88.24% Functions 15/17
87.36% Lines 76/87

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 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 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257              80x     80x   80x             6x 6x   6x 6x 6x 1x       1x   6x                   80x         17x     17x 6x                               6x     17x 17x                                       17x 17x 2x     2x                     17x     16x           17x       80x 16x 16x 15x 15x 16x 16x 16x       16x     80x 2x 2x 2x 4x       2x     80x           17x 29x         17x 29x     29x   28x     28x   28x               1x     17x     17x     80x 36x 36x 26x   36x 10x   36x         80x             635x 318x   317x 317x 317x 305x   317x 75x 75x   317x                                       80x   80x   215x           233x         80x 432x        
/*
 * Copyright 2021 Harness Inc. All rights reserved.
 * Use of this source code is governed by the PolyForm Shield 1.0.0 license
 * that can be found in the licenses directory at the root of this repository, also available at
 * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
 */
 
import { cloneDeep, defaultTo, set } from 'lodash-es'
import type { SelectOption } from '@wings-software/uicore'
 
import { getStageFromPipeline } from '@pipeline/components/PipelineStudio/PipelineContext/helpers'
import type { AllNGVariables } from '@pipeline/utils/types'
import { FeatureIdentifier } from 'framework/featureStore/FeatureIdentifier'
import type { FeaturesProps } from 'framework/featureStore/featureStoreUtil'
import type { UseStringsReturn } from 'framework/strings'
import type { PipelineInfoConfig, StageElementWrapperConfig } from 'services/cd-ng'
import type { InputSetErrorResponse } from 'services/pipeline-ng'
 
function mergeStage(stage: StageElementWrapperConfig, inputSetPortion: NgPipelineTemplate): StageElementWrapperConfig {
  const stageIdToBeMatched = defaultTo(stage.stage?.identifier, '')
  const matchedStageInInputSet = getStageFromPipeline(stageIdToBeMatched, inputSetPortion.pipeline)
 
  Eif (matchedStageInInputSet.stage) {
    let updatedStageVars = []
    if (stage?.stage?.variables && matchedStageInInputSet?.stage?.stage?.variables) {
      updatedStageVars = getMergedVariables(
        stage?.stage?.variables as AllNGVariables[],
        matchedStageInInputSet.stage.stage?.variables as AllNGVariables[]
      )
      matchedStageInInputSet.stage.stage.variables = updatedStageVars
    }
    return matchedStageInInputSet.stage
  }
 
  return stage
}
 
interface NgPipelineTemplate {
  pipeline: PipelineInfoConfig
}
 
export const mergeTemplateWithInputSetData = (
  templatePipeline: NgPipelineTemplate,
  inputSetPortion: NgPipelineTemplate
): NgPipelineTemplate => {
  // Replace all the matching stages in parsedTemplate with the stages received in input set portion
  const stages = templatePipeline.pipeline.template
    ? (templatePipeline.pipeline.template.templateInputs as PipelineInfoConfig)?.stages
    : templatePipeline.pipeline.stages
  const mergedStages = stages?.map(stage => {
    Iif (stage.parallel) {
      /*
      This stage is parallel. Now loop over all the children stages, and check if any of them match in input set portion
      We update all the parallel stages with the ones matching in the input set portion
      and then finally return the new 'updatedParallelStages' object
      */
      const updatedParallelStages = stage.parallel.map(parallelStage => mergeStage(parallelStage, inputSetPortion))
      // Finally setting the updatedParallelStages in the original object, so that the 'mergedStages' will have the updated values
      stage.parallel = updatedParallelStages
      return stage
    }
 
    /*
    This block will be executed if there are no parallel stages.
    Simply loop over the stages and keep matching and replacing
    */
    return mergeStage(stage, inputSetPortion)
  })
 
  const toBeUpdated = cloneDeep(templatePipeline)
  Iif (toBeUpdated.pipeline.template) {
    set(toBeUpdated, 'pipeline.template.templateInputs.stages', mergedStages)
    if ((inputSetPortion.pipeline?.template?.templateInputs as PipelineInfoConfig).properties?.ci) {
      set(
        toBeUpdated,
        'pipeline.template.templateInputs.properties.ci',
        (inputSetPortion.pipeline?.template?.templateInputs as PipelineInfoConfig).properties?.ci
      )
    }
    if ((inputSetPortion.pipeline?.template?.templateInputs as PipelineInfoConfig).variables) {
      set(
        toBeUpdated,
        'pipeline.template.templateInputs.variables',
        getMergedVariables(
          (toBeUpdated.pipeline?.template?.templateInputs as PipelineInfoConfig).variables as AllNGVariables[],
          (inputSetPortion.pipeline?.template?.templateInputs as PipelineInfoConfig).variables as AllNGVariables[]
        )
      )
    }
  } else {
    toBeUpdated.pipeline.stages = mergedStages
    if (inputSetPortion.pipeline?.properties?.ci) {
      Iif (!toBeUpdated.pipeline.properties) {
        toBeUpdated.pipeline.properties = {}
      }
      toBeUpdated.pipeline.properties.ci = inputSetPortion.pipeline.properties.ci
    }
 
    /*
    Below portion adds variables to the pipeline.
    If your input sets has variables, use them.
    Eventually in run pipeline form -
    If input sets are selected, we will supply the variables from 'toBeUpdated' pipleine
    This is why 'toBeUpdated' pipeline should have the variables
    */
 
    if (inputSetPortion.pipeline.variables) {
      // If we have variables saved in input set, pick them and update
 
      toBeUpdated.pipeline.variables = getMergedVariables(
        toBeUpdated.pipeline.variables as AllNGVariables[],
        inputSetPortion.pipeline.variables as AllNGVariables[]
      ) // inputSetPortion.pipeline.variables
    }
  }
  return toBeUpdated
}
 
// Used in Input Set form and save as input set call in run pipeline
export const getFormattedErrors = (apiErrorMap?: { [key: string]: InputSetErrorResponse }): Record<string, any> => {
  const toReturn: Record<string, any> = {}
  if (apiErrorMap) {
    const apiErrorKeys = Object.keys(apiErrorMap)
    apiErrorKeys.forEach(apiErrorKey => {
      const errorsForKey = apiErrorMap[apiErrorKey].errors || []
      Eif (errorsForKey[0].fieldName) {
        toReturn[errorsForKey[0].fieldName] = `${errorsForKey[0].fieldName}: ${errorsForKey[0].message}`
      }
    })
  }
  return toReturn
}
 
export const getOverlayErrors = (invalidReferences: Record<string, string>): Record<string, any> => {
  const toReturn: Record<string, any> = {}
  Eif (invalidReferences) {
    Object.keys(invalidReferences).forEach(invalidReferenceKey => {
      toReturn[invalidReferenceKey] = `${invalidReferenceKey}: ${invalidReferences[invalidReferenceKey]}`
    })
  }
 
  return toReturn
}
 
export const getMergedVariables = (
  variables: AllNGVariables[],
  inputSetVariables: AllNGVariables[]
): AllNGVariables[] => {
  // create a map of input set variables values for easier lookup
  // we use "name" of the varibale as the key
  const inputSetVariablesMap: Record<string, AllNGVariables> = inputSetVariables.reduce(
    (acc, curr) => ({ ...acc, [defaultTo(curr.name, '')]: curr }),
    {}
  )
 
  // loop over existing variables and update their values from input sets
  const finalVariables: AllNGVariables[] = variables.map((variable): AllNGVariables => {
    const { name = '', type } = variable
 
    // if a variable with same name exists in input set variables
    if (name in inputSetVariablesMap) {
      // copy the variable data
      const newVar: AllNGVariables = { ...inputSetVariablesMap[name] }
 
      // remove the variable from input set variables
      delete inputSetVariablesMap[name]
 
      return {
        ...variable,
        // use new value if the type of varibale is same else use the current value
        value: newVar.type === type ? newVar.value : variable.value
      } as AllNGVariables
    }
 
    // else return original varibale
    return variable
  })
 
  const remainingVariables = Object.values(inputSetVariablesMap)
 
  // append the remaining input set variables to existing variables
  return finalVariables.concat(...remainingVariables)
}
 
export const getRbacButtonModules = (module?: string): string[] => {
  const rbacButtonModules = []
  if (module?.includes('cd')) {
    rbacButtonModules.push('cd')
  }
  if (module?.includes('ci')) {
    rbacButtonModules.push('ci')
  }
  return rbacButtonModules
}
/*
  Get features restriction to pass to 'run/ retry' pipeline button based on the modules the pipeline supports
*/
export const getFeaturePropsForRunPipelineButton = ({
  modules,
  getString
}: {
  modules?: string[]
  getString: UseStringsReturn['getString']
}): FeaturesProps | undefined => {
  if (!modules || !modules?.length) {
    return undefined
  }
  const featureIdentifiers: FeatureIdentifier[] = []
  const additionalFeaturesProps: { warningMessage?: string } = {}
  if (modules.includes('cd')) {
    featureIdentifiers.push(FeatureIdentifier.DEPLOYMENTS_PER_MONTH)
  }
  if (modules.includes('ci')) {
    featureIdentifiers.push(FeatureIdentifier.BUILDS)
    additionalFeaturesProps.warningMessage = getString('pipeline.featureRestriction.unlimitedBuildsRequiredPlan')
  }
  return {
    featuresRequest: {
      featureNames: featureIdentifiers
    },
    ...additionalFeaturesProps
  }
}
 
export interface SelectedStageData {
  stageIdentifier?: string
  stagesRequired?: string[]
  stageName?: string
  message?: string
}
export interface StageSelectionData {
  selectedStages: SelectedStageData[]
  allStagesSelected: boolean
  selectedStageItems: SelectOption[]
}
 
export const POLL_INTERVAL = 1 /* sec */ * 1000 /* ms */
 
export const ALL_STAGE_VALUE = 'all'
 
export const getAllStageData = (getString: UseStringsReturn['getString']): SelectedStageData => ({
  stageIdentifier: ALL_STAGE_VALUE,
  stagesRequired: [],
  stageName: getString('pipeline.allStages')
})
 
export const getAllStageItem = (getString: UseStringsReturn['getString']): SelectOption => ({
  label: getString('pipeline.allStages'),
  value: ALL_STAGE_VALUE
})
 
export function getStageIdentifierFromStageData(selectedStageData: StageSelectionData): string[] {
  return selectedStageData.allStagesSelected
    ? []
    : selectedStageData.selectedStageItems.map(stageData => stageData.value as string)
}