Merge branch 'nightly' of https://github.com/shuffle/shuffle into nightly
This commit is contained in:
@@ -14,16 +14,17 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
architecture: [x64, arm64]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up opensearch directory
|
||||
run: mkdir shuffle-database
|
||||
continue-on-error: true
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-compose
|
||||
docker-compose --version
|
||||
|
||||
- name: Elevate rights for it
|
||||
- name: Set up opensearch directory
|
||||
run: chmod -R 777 shuffle-database
|
||||
|
||||
- name: Build the stack
|
||||
@@ -59,7 +60,6 @@ jobs:
|
||||
echo "The word 'Shuffle' was not found in the response."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Register a user and check the status code
|
||||
run: |
|
||||
MAX_RETRIES=30
|
||||
@@ -100,31 +100,36 @@ jobs:
|
||||
echo "User registration failed after $MAX_RETRIES attempts."
|
||||
exit 1
|
||||
|
||||
- name: Run Selenium testing for frontend
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/frontend
|
||||
# write some log to see the current directory
|
||||
chmod +x frontend-testing.sh
|
||||
./frontend-testing.sh
|
||||
|
||||
- name: Get the API key and run a health check
|
||||
id: health_check
|
||||
run: |
|
||||
RESPONSE=$(curl -s -k -u admin:StrongShufflePassword321! 'https://localhost:9200/users/_search')
|
||||
echo "Raw Response: $RESPONSE"
|
||||
|
||||
API_KEY=$(echo "$RESPONSE" | jq -r '.hits.hits[0]._source.apikey')
|
||||
if [ -n "$API_KEY" ]; then
|
||||
if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then
|
||||
echo "Admin API key: $API_KEY"
|
||||
echo "API_KEY=$API_KEY" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Failed to retrieve the API key for the admin user."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting 1 minute before sending the health API request..."
|
||||
sleep 60
|
||||
|
||||
echo "Checking health API..."
|
||||
HEALTH_RESPONSE=$(curl -s 'http://localhost:3001/api/v1/health?force=true' \
|
||||
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \
|
||||
-H 'Accept-Language: en-US,en;q=0.9' \
|
||||
-H 'Connection: keep-alive' \
|
||||
-H 'Sec-Fetch-Dest: document' \
|
||||
-H 'Sec-Fetch-Mode: navigate' \
|
||||
-H 'Sec-Fetch-Site: none' \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H 'Sec-Fetch-User: ?1' \
|
||||
-H 'Upgrade-Insecure-Requests: 1' \
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \
|
||||
-H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \
|
||||
-H 'sec-ch-ua-mobile: ?0' \
|
||||
-H 'sec-ch-ua-platform: "macOS"')
|
||||
-H "Authorization: Bearer $API_KEY")
|
||||
|
||||
echo "Health API Response: $HEALTH_RESPONSE"
|
||||
|
||||
WORKFLOWS_CREATE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.create')
|
||||
WORKFLOWS_RUN=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run')
|
||||
@@ -132,9 +137,35 @@ jobs:
|
||||
WORKFLOWS_RUN_STATUS=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run_status')
|
||||
WORKFLOWS_DELETE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.delete')
|
||||
|
||||
echo "WORKFLOWS_CREATE: $WORKFLOWS_CREATE"
|
||||
echo "WORKFLOWS_RUN: $WORKFLOWS_RUN"
|
||||
echo "WORKFLOWS_RUN_FINISHED: $WORKFLOWS_RUN_FINISHED"
|
||||
echo "WORKFLOWS_RUN_STATUS: $WORKFLOWS_RUN_STATUS"
|
||||
echo "WORKFLOWS_DELETE: $WORKFLOWS_DELETE"
|
||||
|
||||
if [ "$WORKFLOWS_CREATE" = "true" ] && [ "$WORKFLOWS_RUN" = "true" ] && [ "$WORKFLOWS_RUN_FINISHED" = "true" ] && [ "$WORKFLOWS_RUN_STATUS" = "FINISHED" ] && [ "$WORKFLOWS_DELETE" = "true" ]; then
|
||||
echo "Health endpoint check was successful."
|
||||
exit 0
|
||||
else
|
||||
echo "Health endpoint check failed. Response did not meet expected criteria."
|
||||
echo "Health check failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
notify:
|
||||
needs: build
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Twilio Alert
|
||||
run: |
|
||||
WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
MESSAGE="🚨 Shuffle Workflow Failed!
|
||||
Repository: ${{ github.repository }}
|
||||
Branch: ${{ github.ref_name }}
|
||||
Workflow URL: $WORKFLOW_URL"
|
||||
|
||||
curl -s -X POST https://api.twilio.com/2010-04-01/Accounts/${{ secrets.TWILIO_ACCOUNT_SID }}/Messages.json \
|
||||
--data-urlencode "To=${{ secrets.TWILIO_TO_NUMBER }}" \
|
||||
--data-urlencode "From=${{ secrets.TWILIO_FROM_NUMBER }}" \
|
||||
--data-urlencode "Body=$MESSAGE" \
|
||||
-u "${{ secrets.TWILIO_ACCOUNT_SID }}:${{ secrets.TWILIO_AUTH_TOKEN }}" > /dev/null
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
SERVER_URL="http://localhost:3000"
|
||||
|
||||
declare -a Routes=($(grep -oP '(?<=path=")[^"]*' src/App.jsx | grep -E '^[a-zA-Z0-9/_:-]+$' | grep -vE '^/$'))
|
||||
|
||||
for i in "${!Routes[@]}"; do
|
||||
Routes[$i]="${Routes[$i]#/}"
|
||||
done
|
||||
|
||||
# # Add all tab routes and unique routes here
|
||||
Routes+=(
|
||||
'workflows?tab=org_workflows'
|
||||
'workflows?tab=my_workflows'
|
||||
'workflows?tab=all_workflows'
|
||||
'apps/gmail'
|
||||
'apis/gmail'
|
||||
'apps?tab=my_apps'
|
||||
'apps?tab=all_apps'
|
||||
'search?tab=org_apps'
|
||||
'search?tab=my_apps'
|
||||
'search?tab=workflows'
|
||||
'search?tab=docs'
|
||||
'search?tab=creators'
|
||||
'search?tab=discord'
|
||||
"admin?tab=organization"
|
||||
"admin?tab=users"
|
||||
"admin?tab=app_auth"
|
||||
"admin?tab=datastore"
|
||||
"admin?tab=files"
|
||||
"admin?tab=triggers"
|
||||
"admin?tab=locations"
|
||||
"admin?tab=tenants"
|
||||
"admin?admin_tab=org_config"
|
||||
"admin?admin_tab=sso"
|
||||
"admin?admin_tab=notifications"
|
||||
"admin?admin_tab=billingstats"
|
||||
"admin?admin_tab=branding(beta)"
|
||||
)
|
||||
|
||||
# Stop frontend container so it test unpushed changes
|
||||
docker stop shuffle-frontend
|
||||
|
||||
# # Install all frontend dependencies
|
||||
yarn add selenium-webdriver --dev && yarn install
|
||||
echo "Starting frontend..."
|
||||
|
||||
BROWSER=none yarn start &
|
||||
SERVER_PID=$!
|
||||
echo "Frontend started with PID: $SERVER_PID"
|
||||
|
||||
echo "Waiting for 1 minute to ensure the server is fully up..."
|
||||
sleep 60
|
||||
|
||||
echo "Server is up! Starting Selenium tests..."
|
||||
|
||||
echo "Starting frontend tests..."
|
||||
node selenium-test.js "$SERVER_URL" "${Routes[@]}"
|
||||
|
||||
TEST_EXIT_CODE=$?
|
||||
|
||||
if [[ $TEST_EXIT_CODE -ne 0 ]]; then
|
||||
echo "Selenium tests failed. Exiting..."
|
||||
kill $SERVER_PID
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kill $SERVER_PID
|
||||
echo "Testing complete. See above logs for errors if any."
|
||||
|
||||
# Starting shuffle-frontend container
|
||||
echo "Starting shuffle-frontend container..."
|
||||
docker start shuffle-frontend
|
||||
echo "shuffle-frontend started successfully."
|
||||
exit 0
|
||||
@@ -128,6 +128,7 @@
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"postcss": "^8.4.38",
|
||||
"promise-window": "^1.2.1",
|
||||
"selenium-webdriver": "^4.29.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
const { Builder, By, until } = require('selenium-webdriver');
|
||||
const chrome = require('selenium-webdriver/chrome');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('Starting Selenium script for testing pages...');
|
||||
|
||||
(async () => {
|
||||
const userDataDir = path.join(__dirname, 'chrome-user-data', `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`);
|
||||
fs.mkdirSync(userDataDir, { recursive: true });
|
||||
|
||||
let options = new chrome.Options();
|
||||
options.addArguments('--no-sandbox');
|
||||
options.addArguments('--headless');
|
||||
|
||||
let driver = await new Builder()
|
||||
.forBrowser('chrome')
|
||||
.setChromeOptions(options)
|
||||
.build();
|
||||
|
||||
const SuccessfullyLoadedPath = [];
|
||||
const FailedToLoadPath = [];
|
||||
|
||||
try {
|
||||
await driver.manage().window().setRect({ width: 1600, height: 1200 });
|
||||
await driver.manage().window().maximize()
|
||||
await driver.manage().setTimeouts({ implicit: 15000 });
|
||||
|
||||
const frontendURL = process.argv[2];
|
||||
const routes = process.argv.slice(3);
|
||||
|
||||
console.log('Frontend URL:', frontendURL);
|
||||
const isCloud = frontendURL === 'http://localhost:3002' || frontendURL === 'https://sandbox.shuffler.io' || frontendURL === 'https://shuffler.io';
|
||||
|
||||
if (isCloud) {
|
||||
// Login Credentials
|
||||
const LOGIN_URL = `${frontendURL}/login`;
|
||||
// put your login credentials here
|
||||
const USERNAME = '';
|
||||
const PASSWORD = '';
|
||||
|
||||
console.log('Logging in...');
|
||||
await driver.get(LOGIN_URL);
|
||||
|
||||
try {
|
||||
await driver.wait(until.elementLocated(By.css('#emailfield')), 10000);
|
||||
await driver.findElement(By.css('#emailfield')).sendKeys(USERNAME);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#outlined-password-input')), 10000);
|
||||
await driver.findElement(By.css('#outlined-password-input')).sendKeys(PASSWORD);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#loginButton')), 10000);
|
||||
await driver.findElement(By.css('#loginButton')).click();
|
||||
|
||||
// Ensure login success by checking URL change
|
||||
await driver.wait(async () => {
|
||||
const url = await driver.getCurrentUrl();
|
||||
return url.includes('welcome');
|
||||
}, 20000);
|
||||
|
||||
console.log('Successfully logged in!');
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error.message);
|
||||
FailedToLoadPath.push(LOGIN_URL);
|
||||
await driver.quit();
|
||||
process.exit(1);
|
||||
}
|
||||
}else {
|
||||
// Steps for onprem testing
|
||||
// 1. Login
|
||||
// 2. Create new workflow
|
||||
|
||||
// Write your own login credentials here if you are testing onprem locally
|
||||
const USERNAME = 'demo@demo.io';
|
||||
const PASSWORD = 'supercoolpassword';
|
||||
const WORKFLOW_URL = `${frontendURL}/workflows`;
|
||||
|
||||
try {
|
||||
// Login
|
||||
console.log('Logging in...');
|
||||
await driver.get(`${frontendURL}/login`);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#emailfield')), 10000);
|
||||
await driver.findElement(By.css('#emailfield')).sendKeys(USERNAME);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#outlined-password-input')), 10000);
|
||||
await driver.findElement(By.css('#outlined-password-input')).sendKeys(PASSWORD);
|
||||
|
||||
const loginButton = await driver.findElement(By.css('#loginButton'));
|
||||
await driver.executeScript("arguments[0].scrollIntoView(true);", loginButton);
|
||||
await loginButton.click();
|
||||
|
||||
|
||||
|
||||
// Ensure signup success by checking URL change
|
||||
await driver.wait(async () => {
|
||||
const url = await driver.getCurrentUrl();
|
||||
return url.includes('welcome') || url.includes('workflows');
|
||||
}, 20000);
|
||||
|
||||
|
||||
const isParentPresent = await driver.wait(async () => {
|
||||
return await driver.executeScript(
|
||||
"return document.querySelector('.parent-component') !== null;"
|
||||
);
|
||||
}, 5000).catch(() => false);
|
||||
|
||||
if (!isParentPresent) {
|
||||
const logs = await driver.manage().logs().get('browser');
|
||||
const severeErrors = logs.filter(log => log.level.name === 'SEVERE');
|
||||
if (severeErrors.length > 0) {
|
||||
const crashCausingErrors = severeErrors.filter(err => {
|
||||
return !err.message.includes('Warning:') &&
|
||||
!err.message.includes('MUI:')
|
||||
});
|
||||
|
||||
console.error(`Page (${frontendURL}/login) did not load correctly:`, crashCausingErrors);
|
||||
FailedToLoadPath.push(`${frontendURL}/login`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Successfully logged in!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to login:', error.message);
|
||||
FailedToLoadPath.push(WORKFLOW_URL);
|
||||
await driver.quit();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new workflow
|
||||
console.log('Creating new workflow...');
|
||||
await driver.get(WORKFLOW_URL);
|
||||
|
||||
await driver.sleep(1000);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#create_workflow_button')), 10000);
|
||||
const create_workflow_button = await driver.findElement(By.css('#create_workflow_button'));
|
||||
|
||||
await driver.sleep(1000);
|
||||
await create_workflow_button.click();
|
||||
|
||||
await driver.sleep(1500);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#Enter-Workflow-Name')), 10000);
|
||||
const enter_workflow_name_field = await driver.findElement(By.css('#Enter-Workflow-Name'));
|
||||
|
||||
await driver.sleep(1000);
|
||||
await enter_workflow_name_field.sendKeys("Test Workflow");
|
||||
|
||||
// Wait before saving
|
||||
await driver.sleep(1500);
|
||||
|
||||
await driver.wait(until.elementLocated(By.css('#save_workflow_button')), 10000);
|
||||
const save_workflow_button = await driver.findElement(By.css('#save_workflow_button'));
|
||||
|
||||
await driver.sleep(1000); // Delay before clicking
|
||||
await save_workflow_button.click();
|
||||
|
||||
// Wait for the parent component to appear
|
||||
await driver.sleep(2000);
|
||||
|
||||
const isParentPresent = await driver.wait(async () => {
|
||||
return await driver.executeScript(
|
||||
"return document.querySelector('.parent-component') !== null;"
|
||||
);
|
||||
}, 5000).catch(() => false);
|
||||
|
||||
if (!isParentPresent) {
|
||||
const logs = await driver.manage().logs().get('browser');
|
||||
const severeErrors = logs.filter(log => log.level.name === 'SEVERE');
|
||||
if (severeErrors.length > 0) {
|
||||
const crashCausingErrors = severeErrors.filter(err => {
|
||||
return !err.message.includes('Warning:') &&
|
||||
!err.message.includes('MUI:');
|
||||
});
|
||||
|
||||
console.error(`Page (${WORKFLOW_URL}) did not load correctly:`, crashCausingErrors);
|
||||
FailedToLoadPath.push(`${WORKFLOW_URL}`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to create new workflow:', error.message);
|
||||
FailedToLoadPath.push(WORKFLOW_URL);
|
||||
await driver.quit();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get routes from command line arguments
|
||||
if (routes.length === 0) {
|
||||
console.error('No routes found to test.');
|
||||
await driver.quit();
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Found ${routes.length} routes to test.`);
|
||||
|
||||
for (const route of routes) {
|
||||
const url = `${frontendURL}/${route}`;
|
||||
console.log(`Testing route: ${url}`);
|
||||
|
||||
try {
|
||||
await driver.get(url);
|
||||
await driver.sleep(3000);
|
||||
|
||||
// Wait for document readiness
|
||||
await driver.wait(async () => {
|
||||
return await driver.executeScript('return document.readyState') === 'complete';
|
||||
}, 10000);
|
||||
|
||||
const isParentPresent = await driver.wait(async () => {
|
||||
return await driver.executeScript(
|
||||
"return document.querySelector('.parent-component') !== null;"
|
||||
);
|
||||
}, 5000).catch(() => false);
|
||||
|
||||
if (!isParentPresent) {
|
||||
const logs = await driver.manage().logs().get('browser');
|
||||
const severeErrors = logs.filter(log => log.level.name === 'SEVERE');
|
||||
if (severeErrors.length > 0) {
|
||||
const crashCausingErrors = severeErrors.filter(err => {
|
||||
return !err.message.includes('Warning:') &&
|
||||
!err.message.includes('MUI:')
|
||||
});
|
||||
|
||||
console.error(`Page (${url}) did not load correctly:`, crashCausingErrors);
|
||||
FailedToLoadPath.push(url);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Successfully loaded: ${url}`);
|
||||
SuccessfullyLoadedPath.push(url);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load ${url}:`, error.message);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
console.log("Total pages tested: ", SuccessfullyLoadedPath.length + FailedToLoadPath.length);
|
||||
console.log("Successfully loaded pages: ", SuccessfullyLoadedPath.length);
|
||||
if (FailedToLoadPath.length > 0) {
|
||||
console.log("Failed to load pages: ", FailedToLoadPath.length);
|
||||
console.log("Failed to load pages paths: ", FailedToLoadPath);
|
||||
process.exit(1);
|
||||
}else {
|
||||
console.log("No pages failed to load. Congrats!");
|
||||
}
|
||||
await driver.quit();
|
||||
fs.rmSync(userDataDir, { recursive: true, force: true });
|
||||
}
|
||||
})();
|
||||
@@ -189,6 +189,7 @@ const App = (message, props) => {
|
||||
color: "rgba(255, 255, 255, 0.65)",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
className='parent-component'
|
||||
>
|
||||
<ScrollToTop
|
||||
getUserNotifications={getUserNotifications}
|
||||
@@ -371,6 +372,7 @@ const App = (message, props) => {
|
||||
element={
|
||||
<Admin
|
||||
isLoggedIn={isLoggedIn}
|
||||
userdata={userdata}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
register={true}
|
||||
isLoaded={isLoaded}
|
||||
|
||||
@@ -302,6 +302,7 @@ const EditWorkflow = (props) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
style={{}}
|
||||
id="save_workflow_button"
|
||||
disabled={name.length === 0 || submitLoading === true}
|
||||
onClick={() => {
|
||||
setSubmitLoading(true)
|
||||
@@ -406,6 +407,7 @@ const EditWorkflow = (props) => {
|
||||
label="Name"
|
||||
autoFocus
|
||||
fullWidth
|
||||
id="Enter-Workflow-Name"
|
||||
/>
|
||||
|
||||
<div style={{ display: "flex", marginTop: 10, }}>
|
||||
|
||||
@@ -461,7 +461,6 @@ const LoginPage = props => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const onSubmit = (e) => {
|
||||
//toast("Testing from login page")
|
||||
|
||||
|
||||
@@ -4616,6 +4616,7 @@ const Workflows2 = (props) => {
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreateWorkflow}
|
||||
id="create_workflow_button"
|
||||
style={{
|
||||
borderRadius: 4,
|
||||
flex: 0.8,
|
||||
|
||||
Reference in New Issue
Block a user