Skip to content

[Senior Software Engineer] Airbnb - Brazil #5

[Senior Software Engineer] Airbnb - Brazil

[Senior Software Engineer] Airbnb - Brazil #5

Workflow file for this run

name: Validar e Classificar Vaga
on:
issues:
types: ["opened", "edited"]
jobs:
validar-vaga:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Verificar campos obrigatórios
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const body = issue.body || '';
const title = issue.title || '';
// Lista de campos obrigatórios do template
const camposObrigatorios = [
{ nome: 'Cargo', regex: /###\s*🎯\s*Cargo\s*\n\n(.+)/i },
{ nome: 'Empresa', regex: /###\s*🏢\s*Empresa\s*\n\n(.+)/i },
{ nome: 'Nível', regex: /###\s*📊\s*Nível de Experiência\s*\n\n(.+)/i },
{ nome: 'Modalidade', regex: /###\s*🏠\s*Modalidade de Trabalho\s*\n\n(.+)/i },
{ nome: 'Localização', regex: /###\s*📍\s*Localização\s*\n\n(.+)/i },
{ nome: 'Contratação', regex: /###\s*📝\s*Tipo de Contratação\s*\n\n(.+)/i },
{ nome: 'Descrição', regex: /###\s*📋\s*Descrição da Vaga\s*\n\n([\s\S]+?)(?=###|$)/i },
{ nome: 'Requisitos', regex: /###\s*✅\s*Requisitos Técnicos\s*\n\n([\s\S]+?)(?=###|$)/i },
{ nome: 'Como Candidatar', regex: /###\s*📧\s*Como se Candidatar\s*\n\n([\s\S]+?)(?=###|$)/i }
];
const camposFaltando = [];
const camposPreenchidos = {};
for (const campo of camposObrigatorios) {
const match = body.match(campo.regex);
if (!match || !match[1] || match[1].trim() === '' || match[1].trim() === '_No response_') {
camposFaltando.push(campo.nome);
} else {
camposPreenchidos[campo.nome] = match[1].trim();
}
}
// Marcador para identificar comentários do bot
const marcadorBot = '<!-- bot-validacao -->';
// Busca comentário existente do bot
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number
});
const comentarioBot = comments.data.find(c => c.body.includes(marcadorBot));
// Se faltam campos, cria ou atualiza comentário e label
if (camposFaltando.length > 0) {
const mensagem = `${marcadorBot}
## ⚠️ Informações Pendentes
Olá! Obrigado por publicar sua vaga no **DevOps Brasil Vagas**! 🚀
Para que sua vaga seja melhor indexada e encontrada pelos candidatos, por favor complete os seguintes campos:
${camposFaltando.map(c => `- ❌ **${c}**`).join('\n')}
Por favor, edite a issue e preencha os campos faltantes usando o [template de vagas](https://github.com/DevOps-Brasil/Vagas/issues/new/choose).
---
*Esta é uma mensagem automática do bot de validação.*`;
if (comentarioBot) {
// Atualiza comentário existente
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comentarioBot.id,
body: mensagem
});
} else {
// Cria novo comentário
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: mensagem
});
}
// Adiciona label de pendente
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['Pendente de informações']
});
return;
}
// Tudo OK — remove comentário do bot se existir
if (comentarioBot) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comentarioBot.id
});
}
// Remove label de pendente se existir
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: 'Pendente de informações'
});
} catch (e) {
// Label não existe, ignorar
}
console.log('✅ Vaga validada com sucesso!');
console.log('Campos preenchidos:', camposPreenchidos);
classificar-nivel:
runs-on: ubuntu-latest
needs: validar-vaga
permissions:
issues: write
steps:
- name: Adicionar label de nível
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const body = issue.body || '';
const title = issue.title.toLowerCase();
// Mapeamento de níveis
const niveis = {
'Estágio': 'Estágio',
'Júnior': 'Júnior',
'Pleno': 'Pleno',
'Sênior': 'Sênior',
'Especialista/Staff': 'Especialista',
'Tech Lead': 'Tech Lead',
'Coordenação/Gerência': 'Gestão'
};
const todasLabelsNivel = new Set(Object.values(niveis));
// Busca nível no body (template)
const nivelMatch = body.match(/###\s*📊\s*Nível de Experiência\s*\n\n(.+)/i);
let labelNivel = null;
if (nivelMatch && nivelMatch[1]) {
const nivelSelecionado = nivelMatch[1].trim();
labelNivel = niveis[nivelSelecionado];
}
// Fallback: busca no título
if (!labelNivel) {
if (title.includes('junior') || title.includes('júnior') || title.includes('jr')) {
labelNivel = 'Júnior';
} else if (title.includes('pleno') || title.includes('mid')) {
labelNivel = 'Pleno';
} else if (title.includes('senior') || title.includes('sênior') || title.includes('sr')) {
labelNivel = 'Sênior';
} else if (title.includes('lead') || title.includes('líder')) {
labelNivel = 'Tech Lead';
} else if (title.includes('estag') || title.includes('intern')) {
labelNivel = 'Estágio';
}
}
// Remove labels de nível antigas
const labelsAtuais = issue.labels.map(l => l.name);
for (const label of labelsAtuais) {
if (todasLabelsNivel.has(label) && label !== labelNivel) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: label
});
} catch (e) { /* label não existe */ }
}
}
if (labelNivel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: [labelNivel]
});
console.log(`✅ Label de nível adicionada: ${labelNivel}`);
}
classificar-contratacao:
runs-on: ubuntu-latest
needs: validar-vaga
permissions:
issues: write
steps:
- name: Adicionar label de contratação
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const body = issue.body || '';
const title = issue.title.toLowerCase();
// Mapeamento de contratação
const contratacoes = {
'CLT': 'CLT',
'PJ': 'PJ',
'CLT ou PJ': ['CLT', 'PJ'],
'Cooperado': 'Cooperado',
'Freelancer': 'Freelancer',
'Estágio': 'Estágio'
};
const todasLabelsContratacao = new Set(['CLT', 'PJ', 'Cooperado', 'Freelancer', 'Estágio']);
// Busca contratação no body (template)
const contratacaoMatch = body.match(/###\s*📝\s*Tipo de Contratação\s*\n\n(.+)/i);
let labels = [];
if (contratacaoMatch && contratacaoMatch[1]) {
const contratacaoSelecionada = contratacaoMatch[1].trim();
const label = contratacoes[contratacaoSelecionada];
if (Array.isArray(label)) {
labels = label;
} else if (label) {
labels = [label];
}
}
// Fallback: busca no título/body
if (labels.length === 0) {
const texto = (title + ' ' + body).toLowerCase();
if (texto.includes('clt')) labels.push('CLT');
if (texto.includes('pj') || texto.includes('pessoa jurídica')) labels.push('PJ');
}
// Remove labels de contratação antigas
const labelsNovas = new Set(labels);
const labelsAtuais = issue.labels.map(l => l.name);
for (const label of labelsAtuais) {
if (todasLabelsContratacao.has(label) && !labelsNovas.has(label)) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: label
});
} catch (e) { /* label não existe */ }
}
}
if (labels.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: labels
});
console.log(`✅ Labels de contratação adicionadas: ${labels.join(', ')}`);
}
classificar-modalidade:
runs-on: ubuntu-latest
needs: validar-vaga
permissions:
issues: write
steps:
- name: Adicionar label de modalidade
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const body = issue.body || '';
const title = issue.title.toLowerCase();
// Mapeamento de modalidade
const modalidades = {
'Remoto': 'Remoto',
'Híbrido': 'Híbrido',
'Presencial': 'Presencial'
};
const todasLabelsModalidade = new Set(Object.values(modalidades));
// Busca modalidade no body (template)
const modalidadeMatch = body.match(/###\s*🏠\s*Modalidade de Trabalho\s*\n\n(.+)/i);
let labelModalidade = null;
if (modalidadeMatch && modalidadeMatch[1]) {
const modalidadeSelecionada = modalidadeMatch[1].trim();
labelModalidade = modalidades[modalidadeSelecionada];
}
// Fallback: busca no título
if (!labelModalidade) {
if (title.includes('remoto') || title.includes('remote')) {
labelModalidade = 'Remoto';
} else if (title.includes('híbrido') || title.includes('hibrido') || title.includes('hybrid')) {
labelModalidade = 'Híbrido';
} else if (title.includes('presencial') || title.includes('on-site') || title.includes('onsite')) {
labelModalidade = 'Presencial';
}
}
// Remove labels de modalidade antigas
const labelsAtuais = issue.labels.map(l => l.name);
for (const label of labelsAtuais) {
if (todasLabelsModalidade.has(label) && label !== labelModalidade) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: label
});
} catch (e) { /* label não existe */ }
}
}
if (labelModalidade) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: [labelModalidade]
});
console.log(`✅ Label de modalidade adicionada: ${labelModalidade}`);
}