Skip to content

Commit

Permalink
build(cicd) introduce separate front-end deployment for Haida project (
Browse files Browse the repository at this point in the history
…#389)

* WIP add experimental function to Jenkinsfile

* WIP refactor the branch condition

* WIP break out function to copy env dependent content config

* update sample content config with more realistic text

* comment out ci stage for troubleshooting purposes

* force deployment stage to run for this PR

* WIP Introduce separate Haida staging front-end build

* WIP refactor Jenkinsfile

* WIP configure auth for the staging environment

* remove project-specific labels for staging data from front-end

* force staging build to use production config

* WIP troubleshoot build

* Use actual test data term for word-of-the-day in sample content config

* troubleshoot build

* troubleshoot build

* nuke node modules

* troubleshoot nx issues

* revert Jenkinsfile

* fix front-end tests

* break out build steps in Jenkinsfile

* break out function to copy the staging content config

* refactor Jenkinsfile to use multiple stages for deployment

* break out dependency install into separate stage

* break the front-end build out into a separate stage

* break out back-end and cli builds into separate stages

* parametrize the front-end build

* break out entire front-end build block

* opt back in to the front-end deployment

* tweak front-end artifacts pattern

* troubleshoot front-end deployment

* troubleshoot deployment...

* update front-end artifacts path in publisher

* tweak deployment

* reinstate full front-end deployment

* move front-end deployment logic into reuseable function

* revert to working state

* tweak deployment

* archive the front-end only

* break out front-end deployment into reuseable function

* Add Haida front-end deployment

* reformat publish step for readability

* wrap publish over ssh in simpler API

* revert previous

* wrap artifact publication block in simpler API

* pass through additional params

* flow through remaining parameters

* refactor front-end deployment block

* run backend deployment

* use multi-line strings and function calls for readability

* deploy cli

* fix Groovy lint

* revert prevoius change

* fix placement of cli deploy post block

* copy cli to different directory

* deploy node_modules with back-end build

* skip nx cache for front-end build

* restore proper stage conditionals

* restore another conditional stage
  • Loading branch information
aaron-plahn committed May 29, 2023
1 parent 439bd4d commit 2291d0b
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 83 deletions.
226 changes: 192 additions & 34 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* groovylint-disable NestedBlockDepth */
def nodeInstallationName = 'NodeJS 18.12.0'

/* groovylint-disable DuplicateStringLiteral */
Expand Down Expand Up @@ -79,7 +80,7 @@ pipeline {
}
}
}
stage('deploy to staging') {
stage('build and deploy to staging') {
agent {
label 'jenkins-build-agent'
}
Expand All @@ -102,43 +103,200 @@ pipeline {
when {
branch 'integration'
}
steps {
configFileProvider([configFile(fileId:'42feff14-78da-45fc-a8ee-5f98213a313f', \
targetLocation: 'apps/coscrad-frontend/src/auth_config.json')]) {
echo 'Merged to integration'
echo "NODE ENV: ${NODE_ENV}"
echo 'Installing dependencies'
sh 'npm ci --legacy-peer-deps'
stages {
stage('install dependencies') {
steps {
echo 'Running staging build'
echo "NODE ENV: ${NODE_ENV}"
echo 'Installing dependencies'
sh 'npm ci --legacy-peer-deps'
}
}
stage('build front-end for COSCRAD sandbox') {
steps {
runFrontendBuild('COSCRAD')
}
post {
success {
deployFrontend('COSCRAD')
}
}
}
stage('build front-end for Haida sandbox') {
steps {
runFrontendBuild('Haida')
}
post {
success {
deployFrontend('Haida')
}
}
}
stage('build back-end') {
steps {
sh 'npx nx run build api --prod'
}
post {
success {
deployBackend()
}
}
}
stage('build cli') {
steps {
sh 'npx nx run api:build:cli'
}
post {
success {
deployCli()
}
}
}
}
}
}
}

echo 'Building COSCRAD'
echo 'with node version'
sh 'node --version'
/**
* TODO We might be able to have a single function that switches on `target` and
* sets several global variables to be used in various steps.
*/
String getContentConfigFilename(String target) {
if (target == 'COSCRAD') { return 'content.config.SAMPLE.ts' }

/**
* Note that the sample content config is actually valid for
* our staging build.
**/
echo 'copying sample content config for test build'
/* groovylint-disable-next-line LineLength */
sh 'cp apps/coscrad-frontend/src/configurable-front-matter/data/content.config.SAMPLE.ts apps/coscrad-frontend/src/configurable-front-matter/data/content.config.ts'
if (target == 'Haida') { return 'content.config.STAGING.ts' }

sh 'npm run build:coscrad:prod'
error "unsupported deployment target: ${target}"

sh 'npx nx run api:build:cli'
}
}
post {
success {
archiveArtifacts artifacts: 'dist/**, node_modules/**', followSymlinks: false
// Deploy front-end build to staging
sshPublisher(
publishers: [sshPublisherDesc(configName: '[email protected]', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'rm -rf /var/www/html && mv build/dist/apps/coscrad-frontend /var/www/html && rm -rf build', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'build', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'dist/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

// Deploy back-end build to staging
sshPublisher(
publishers: [sshPublisherDesc(configName: '[email protected]', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'rm -rf archive ; mv build archive; touch archive/dist/apps/api/staging.env; PATH=$PATH://home/coscradmin/.nvm/versions/node/v18.16.0/bin pm2 restart main; echo API restarted', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'build', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'dist/**, node_modules/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
// This is to satisfy the linter. Maybe we should be explicitly throwing in the line above?
return ''
}

/**
* # Available Targets
- COSCRAD
- Haida
**/
void copyContentConfig(String target) {
String contentConfigDirectory = 'apps/coscrad-frontend/src/configurable-front-matter/data/'

/**
* Note that the sample content config is actually valid for
* our staging build.
**/
echo "attempting to copy sample content config for test build for target ${target}"

/* groovylint-disable-next-line LineLength */
sh "cp ${contentConfigDirectory}${getContentConfigFilename(target)} ${contentConfigDirectory}content.config.ts"

return
}

void runFrontendBuild(String target) {
configFileProvider([configFile(fileId:'staging.auth.config', \
targetLocation: 'apps/coscrad-frontend/src/auth_config.json')]) {
echo "Building COSCRAD front-end for target project: ${target}"
echo 'with node version'
sh 'node --version'

copyContentConfig(target)
sh 'npx nx build coscrad-frontend --prod --skip-nx-cache'

echo 'build contents'
sh 'ls dist/apps'
}
}
}
}

String getDeploymentDirectoryForFrontendBuild(String target) {
if (target == 'COSCRAD') {
return 'html'
}

if (target == 'Haida') {
return 'haida'
}

error "No deployment directory found for unknown build target: ${target}"

return ''
}

void publishArtifacts(String sshConfigName, \
String postTransferCommand, \
String remoteDirectory, \
String sourceFilePattern) {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: sshConfigName,
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: postTransferCommand,
execTimeout: 120000,
flatten: false, makeEmptyDirs:
false, noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: remoteDirectory,
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: sourceFilePattern
)],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: false
)])
}

void archiveArtifacts(String sourceFilePattern) {
archiveArtifacts artifacts: sourceFilePattern, followSymlinks: false
}

void deployFrontend(String target) {
String basePath = '/var/www/'

String deploymentDirectory = getDeploymentDirectoryForFrontendBuild(target)

String fullDeploymentPath = "${basePath}${deploymentDirectory}"

String command = "rm -rf ${fullDeploymentPath} \
&& mv build/dist/apps/coscrad-frontend \
${fullDeploymentPath} && rm -rf build "

String sourceFilePattern = 'dist/apps/coscrad-frontend/**'

archiveArtifacts(sourceFilePattern)

publishArtifacts('[email protected]', command, 'build', sourceFilePattern)
}

void deployBackend() {
String backendArtifactsPattern = 'dist/apps/api/**, node_modules/**'

String postDeployCommand = 'rm -rf archive ; \
mv build archive; \
touch archive/dist/apps/api/staging.env; \
PATH=$PATH://home/coscradmin/.nvm/versions/node/v18.16.0/bin pm2 restart main; \
echo API restarted'

archiveArtifacts(backendArtifactsPattern)

publishArtifacts('[email protected]',
postDeployCommand, \
'build', \
backendArtifactsPattern)
}

void deployCli() {
String backendArtifactsPattern = 'dist/apps/coscrad-cli/**'

String postDeployCommand = 'echo CLI build copied'

archiveArtifacts(backendArtifactsPattern)

publishArtifacts('[email protected]',
postDeployCommand, \
'cli', \
backendArtifactsPattern)
}
2 changes: 1 addition & 1 deletion apps/api/src/test-data/buildUserTestData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const dtos: DTO<CoscradUser>[] = [
{
type: AggregateType.user,
id: '1',
authProviderUserId: 'auth0|123',
authProviderUserId: 'auth0|5db729701ead110c5b254553',
profile: new CoscradUserProfile(dummyProfile),
username: 'cool-james',
roles: [CoscradUserRole.viewer],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { renderWithProviders } from '../../utils/test-utils';
import { getDummyConfigurableContent } from '../../utils/test-utils/get-dummy-configurable-content';
import { COSCRADLogo } from './coscrad-logo';

const coscradLogoUrl = 'coscrad-logo.png';

const act = () =>
renderWithProviders(<COSCRADLogo />, {
contentConfig: getDummyConfigurableContent({
coscradLogoUrl: coscradLogoUrl,
}),
});

describe('COSCRADLogo', () => {
it('should render the logo image successfully', () => {
const { baseElement } = renderWithProviders(<COSCRADLogo />);
const { baseElement } = act();

expect(baseElement).toBeTruthy();
});

it('should display the logo image', () => {
const { baseElement } = renderWithProviders(<COSCRADLogo />);
const { baseElement } = act();

const logo = baseElement.querySelector('img');

expect(logo.src).toContain('coscrad-logo.png');
expect(logo.src).toContain(coscradLogoUrl);
});
});
2 changes: 1 addition & 1 deletion apps/coscrad-frontend/src/components/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const Home = (): JSX.Element => {
src: siteHomeImageUrl,
width: 2000,
height: 1329,
title: 'Haida play Singii Ganguu',
title: 'Home',
};

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { ICategorizableDetailQueryResult, IPhotographViewModel } from '@coscrad/api-interfaces';
import {
ICategorizableDetailQueryResult,
IPhotographViewModel,
MultilingualTextItemRole,
} from '@coscrad/api-interfaces';
import { SinglePropertyPresenter } from '../../../utils/generic-components';
import { ImageFullPageWidth } from '../../../utils/generic-components/presenters/image-full-page-width';
import { ResourceNamePresenter } from '../../../utils/generic-components/presenters/resource-name-presenter';

export const PhotographDetailFullViewPresenter = ({
id,
imageUrl,
name,
}: ICategorizableDetailQueryResult<IPhotographViewModel>): JSX.Element => {
const name = 'Totem Pole';

// Simulating image object retrieved from Digital Asset Manager
const image = {
src: imageUrl,
width: 2000,
height: 1329,
title: 'Haida play Singii Ganguu',
title:
name?.items.find(({ role }) => role === MultilingualTextItemRole.original)?.text ||
`photograph/${id}`,
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IPhotographViewModel, ResourceType } from '@coscrad/api-interfaces';
import {
IPhotographViewModel,
LanguageCode,
MultilingualTextItemRole,
ResourceType,
} from '@coscrad/api-interfaces';
import { getConfig } from '../../../config';
import { assertElementWithTestIdOnScreen, assertNotFound } from '../../../utils/test-utils';
import { buildMockSuccessfulGETHandler } from '../../../utils/test-utils/build-mock-successful-get-handler';
Expand All @@ -12,8 +17,17 @@ const idToFind = '123';

const photographToFind: IPhotographViewModel = {
id: idToFind,
name: {
items: [
{
text: 'doghouse',
role: MultilingualTextItemRole.original,
languageCode: LanguageCode.English,
},
],
},
photographer: 'Johnny Blue',
imageURL: 'https://jazzysnaps.images.com/doghouse.png',
imageUrl: 'https://jazzysnaps.images.com/doghouse.png',
};

/**
Expand Down
Loading

0 comments on commit 2291d0b

Please sign in to comment.