Skip to content
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

Code review for YG TECHNOLOGY INC #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
NODE_ENV=dev
DEBUG=true

# Variables for local worker.
API_KEY=XXX
PROJECT_ID=XXX

# Diffy production variables for AWS infrastructure. These are not needed for local worker.
JOB_QUEUE_NAME=
RESULTS_QUEUE_NAME=
APP_AWS_REGION=
AWS_ACCOUNT_ID=
MAX_ATTEMPTS=
S3_ACCESS_KEY_ID=
SE_ACCESS_KEY_SECRET=
S3_BUCKET=
PROXY=
30 changes: 30 additions & 0 deletions .github/workflows/review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Review

on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]

jobs:
build:
name: Build
runs-on: ubuntu-latest
permissions: read-all
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
# If you wish to fail your job when the Quality Gate is red, uncomment the
# following lines. This would typically be used to fail a deployment.
# We do not recommend to use this in a pull request. Prefer using pull request
# decoration instead.
- uses: sonarsource/sonarqube-quality-gate-action@master
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
.idea
node_modules
107 changes: 107 additions & 0 deletions README copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
This is the code of the screenshot worker that runs on production for Diffy (https://diffy.website).

By open sourcing it we allow local development integrations (i.e. DDEV, Lando).

To start container (default platform is needed if you are on M1 processor)

```shell
docker-compose -f docker-compose.yml up
```

Login to container

```shell
docker-compose -f docker-compose.yml exec node bash
cd /app
```

To start an app with a test job
```shell
node index.js --file=test_jobs/screenshot1.json
```

List of compatible versions of puppeteer and Chrome
https://pptr.dev/supported-browsers

To install specific version of Chromium
https://www.chromium.org/getting-involved/download-chromium/

Chromium 111 was installed from specific source
```shell
add-apt-repository ppa:saiarcot895/chromium-dev
apt update
apt-get install chromium-browser
chromium-browser --version
```

Create a job in SQS. Once created edit it and clear "Access policy" section.

Additionally installed fonts on production workers:
```shell
apt-get update && apt-get install -y fontconfig fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst --no-install-recommends
apt-get install ttf-mscorefonts-installer
apt-get install fonts-ubuntu fonts-noto-color-emoji fonts-noto-cjk fonts-ipafont-gothic fonts-wqy-zenhei fonts-kacst fonts-freefont-ttf fonts-liberation fonts-thai-tlwg fonts-indic
apt-get install fonts-lato fonts-open-sans fonts-roboto
apt install fonts-dejavu-core

fc-cache -f -v
```

To check fonts
fc-match system-ui

### Chrome version validation

To validate Chrome run screenshot on https://vrt-test.diffy.website

Project's settings:
```YAML
basic:
name: 'Chrome validation 1'
environments:
production: 'https://vrt-test.diffy.website'
staging: ''
development: ''
breakpoints:
- 1200
pages:
- /
monitoring:
days: { }
type: ''
schedule_time: '12:30 AM'
schedule_time_zone: Europe/London
compare_with: last
advanced:
mask: ''
remove: '#mask'
isolate: '#remove'
delay: 10
scroll: true
headers:
- { value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0', header: User-Agent }
cookies: CUSTOM=cookie;
custom_js: "var div = document.getElementById('custom-javascript');\ndiv.innerHTML += ' Extra content added!';"
custom_css: "#custom-css {\n background-color: red;\n}"
mock_content:
- { type: title, selector: '#timestamp' }
login:
type: ''
click_element: false
click_element_selector: ''
login_url: ''
username: ''
password: ''
username_selector: ''
password_selector: ''
submit_selector: ''
after_login_selector: ''
performance:
workers_production: 30
workers_nonproduction: 10
workers_production_delay: 0
workers_nonproduction_delay: 0
stabilize: true
</code>
```

108 changes: 108 additions & 0 deletions diffy-screenshots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Example to run
// node diffy-screenshots.js --url=https://diffy.website

const debug = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be moved to the env


require('dotenv').config();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be replaced with built in Node JS env


const { Logger } = require('./lib/logger')
const logger = new Logger(debug);

const { Jobs } = require('./lib/jobs')
const jobs = new Jobs(logger)

const { Api } = require('./lib/api.js')
let api
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable declaration must be changed to const also it can be moved the initialization location.


const process = require("process");
const fs = require("fs");

const apiKey = process.env.API_KEY || ''
if (apiKey == '') {
console.error('Add Diffy API key to .env file. API_KEY=XXX');
return;
}
const projectId = process.env.PROJECT_ID || ''
if (projectId == '') {
console.error('Add Diffy API project ID .env file. PROJECT_ID=XXX');
return;
}

const diffyUrl = 'https://app.diffy.website/api'
const diffyWebsiteUrl = 'https://app.diffy.website/#'

var argv = require('minimist')(process.argv.slice(2));


async function end () {
try {
// Remove tmp files.
// func.cleanTmpDir()
} catch (e) {
console.error(e.message)
}
process.exit(1)
}

process.once('SIGTERM', end)
process.once('SIGINT', end)

process.on('uncaughtException', async (e) => {
console.error('Unhandled exception:', e)
await end()
});

process.on('unhandledRejection', async (reason, p) => {
console.error('Unhandled Rejection at: Promise', p, 'reason:', reason)
await end()
});

(async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execSync blocks the main thread, so the execution does not take place in parallel, but sequentially, which is directly proportional to the number of tasks.
execSync can be replaced with execAsync and in general you can abandon the for cycle in favor of map and process all promises through Promise.all

if (argv.url === undefined) {
console.error('Provide --url parameter. Example --url="https://diffy.website"');
}
const screenshotName = argv['screenshot-name'] ? argv['screenshot-name'] : argv.url;
try {
api = new Api(diffyUrl, apiKey, projectId, logger)
await api.login()
const project = await api.getProject()
const jobsList = jobs.prepareJobs(argv.url, project)

const execSync = require('node:child_process').execSync;
const outputFilepath = '/tmp/screenshot-results.json';
const inputFilepath = '/tmp/screenshot-input.json';
let uploadItems = [];
for (let i = 0; i < jobsList.length; i++) {
let jsonJob = JSON.stringify(jobsList[i]);
try {
fs.writeFileSync(inputFilepath, jsonJob);
} catch (err) {
console.error(err);
}
console.log('Staring screenshot ' + (i + 1) + ' of ' + jobsList.length);
await execSync('node ./index.js --local=true --output-filepath=\'' + outputFilepath + '\' --file=\'' + inputFilepath + '\'', {stdio: 'inherit'});
console.log('Completed screenshot ' + (i + 1) + ' of ' + jobsList.length);
const resultsContent = fs.readFileSync(outputFilepath, 'utf8');
console.log(resultsContent);
let result = JSON.parse(resultsContent);
let uploadItem = {
status: true,
breakpoint: jobsList[i].params.breakpoint,
uri: jobsList[i].params.uri,
filename: result.screenshot,
htmlFilename: result.html,
jsConsoleFilename: result.jsConsole
};
uploadItems.push(uploadItem);
}

// Send screenshots to Diffy.
screenshotId = await api.uploadScreenshots(screenshotName, uploadItems)
console.log('Diffy screenshot url: ', `${diffyWebsiteUrl}/snapshots/${screenshotId}`)

await end()
} catch (e) {
console.error('ERROR:', e.message)
await end()
}
})()
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
node:
build: './docker/'
volumes:
- "./:/app"
command: tail -f /dev/null
tty: true
32 changes: 32 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM --platform=linux/arm64 ubuntu:22.04

ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get install -y gconf-service apt-transport-https ca-certificates libssl-dev wget libasound2 libatk1.0-0 libcairo2 libcups2 libfontconfig1 libgdk-pixbuf2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libxss1 fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils curl build-essential tar gzip findutils net-tools dnsutils telnet ngrep tcpdump
RUN apt-get install software-properties-common -y
RUN add-apt-repository ppa:saiarcot895/chromium-dev

RUN apt update
RUN apt-get install -y chromium-browser

ENV NODE_VERSION 22.5.1

RUN ARCH=arm64 \
&& curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \
&& tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
&& rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs \
# smoke tests
&& node --version \
&& npm --version

RUN ARCH=arm64 \
&& npm install -g npm@10.8.2

RUN apt install -y imagemagick

# Install all the fonts.
RUN apt-get install -y --no-install-recommends fontconfig fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-mscorefonts-installer fonts-ubuntu fonts-noto-color-emoji fonts-noto-cjk fonts-ipafont-gothic fonts-wqy-zenhei fonts-kacst fonts-freefont-ttf fonts-liberation fonts-thai-tlwg fonts-indic fonts-lato fonts-open-sans fonts-roboto fonts-dejavu-core
RUN fc-cache -f -v

#ENTRYPOINT ["/bin/sh", "-c", "bash"]
9 changes: 9 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Scripts used to test various subsystems in isolation i.e. puppeteer, s3 uploads, sqs

To run test scripts make sure to copy .env file to the "examples" folder.

SQS tests
```shell
node example_sqs_send.js ../test_jobs/screenshot1.json
node example_sqs_receive.js
```
18 changes: 18 additions & 0 deletions examples/example_puppeteer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
defaultViewport: {width: 800, height: 600},
// executablePath: '/app/chromium/linux-1083080/chrome-linux/chrome',
headless: 'shell',
dumpio: false,
ignoreHTTPSErrors: true
}
);
const page = await browser.newPage();
await page.goto('https://www.freecodecamp.org/');
await page.screenshot({path: 'freecodecamp.png'});

await browser.close();
})();
14 changes: 14 additions & 0 deletions examples/example_s3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require('dotenv').config();
const process = require('process');

const uploadS3 = require("../lib/uploadS3");
const filename = '/app/screenshot-1714780252-73939221.webp';

(async () => {
s3Url = await uploadS3.upload(filename).catch((err) => {
throw new Error('Can\'t upload screenshot: ' + err.name + ': ' + (err && err.hasOwnProperty('message')) ? err.message : err)
})

console.log(s3Url);

})()
13 changes: 13 additions & 0 deletions examples/example_sqs_receive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require('dotenv').config();
const process = require('process');

const { SqsSender, maxAttempts } = require('../lib/sqsSender');

(async () => {
const sqsSender = new SqsSender(true, false);
let messages = await sqsSender.fetchSQSJob();
console.log(messages, 'received');

await sqsSender.deleteSQSMessage(messages[0]);
})()

24 changes: 24 additions & 0 deletions examples/example_sqs_send.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require('dotenv').config();
const process = require('process');

const { SqsSender, maxAttempts } = require('../lib/sqsSender')
const fs = require("fs");

if (process.argv[2] === undefined) {
console.log('Error. Specify file to json encoded job to post to SQS')
process.exit();
}
let fileContent;
try {
fileContent = fs.readFileSync(process.argv[2], 'utf8');
} catch (err) {
console.error(err);
process.exit();
}

(async () => {
const sqsSender = new SqsSender(true, false);
const result = await sqsSender.sendSQSJob(JSON.parse(fileContent));
console.log(result);
console.log('Job is sent ' + fileContent);
})()
Loading