Set up CI/CD for Hex data analytics integrations: run unit tests with mocked project run and connection responses on every PR, trigger live Hex project runs and validate outputs on merge to main. Hex provides collaborative data notebooks with scheduled runs and API-triggered execution, so CI pipelines verify data transform logic, trigger post-deploy dashboard refreshes, and monitor run status.
name: Hex CI
on:
pull_request:
paths: ['src/hex/**', 'tests/**']
push:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm test -- --reporter=verbose
trigger-hex-refresh:
if: github.ref == 'refs/heads/main'
needs: unit-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run test:integration
env:
HEX_API_TOKEN: ${{ secrets.HEX_API_TOKEN }}
HEX_PROJECT_ID: ${{ vars.HEX_PROJECT_ID }}
import { describe, it, expect, vi } from 'vitest';
import { triggerProjectRun, getRunStatus } from '../src/hex-service';
vi.mock('../src/hex-client', () => ({
HexClient: vi.fn().mockImplementation(() => ({
runProject: vi.fn().mockResolvedValue({
runId: 'run_abc123',
projectId: 'proj_xyz',
status: 'running',
startedAt: '2026-04-01T10:00:00Z',
}),
getRunStatus: vi.fn().mockResolvedValue({
runId: 'run_abc123',
status: 'completed',
elapsedMs: 4500,
outputs: { row_count: 1250, last_updated: '2026-04-01T10:00:04Z' },
}),
listProjects: vi.fn().mockResolvedValue({
projects: [{ id: 'proj_xyz', title: 'Revenue Dashboard' }],
}),
})),
}));
describe('Hex Service', () => {
it('triggers a project run and returns run ID', async () => {
const result = await triggerProjectRun('proj_xyz', { triggered_by: 'ci' });
expect(result.runId).toBe('run_abc123');
expect(result.status).toBe('running');
});
it('polls run status until complete', async () => {
const status = await getRunStatus('run_abc123');
expect(status.status).toBe('completed');
expect(status.outputs.row_count).toBe(1250);
});
});
import { describe, it, expect } from 'vitest';
const hasToken = !!process.env.HEX_API_TOKEN;
describe.skipIf(!hasToken)('Hex Live API', () => {
it('triggers a project run via API', async () => {
const res = await fetch(
`https://app.hex.tech/api/v1/project/${process.env.HEX_PROJECT_ID}/run`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HEX_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ inputParams: { triggered_by: 'ci' }, updateCacheResult: true }),
},
);
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toHaveProperty('runId');
});
});