mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 09:34:46 +08:00
Merge remote-tracking branch 'origin/main' into fix/1900
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: adicionado
|
||||
titulo: A tela de atualização diz onde está o detalhe da disputa de banco
|
||||
---
|
||||
|
||||
Quando a atualização pelo botão da tela encontra o banco em uso por outro processo e precisa de mais de uma passada, o resumo dessa disputa já aparecia no fim da atualização. Faltava dizer onde procurar o detalhe.
|
||||
|
||||
Agora, logo abaixo do resumo, a tela aponta o arquivo `.update.log`, na pasta do projeto no servidor, onde fica o que cada passada não aplicou. A linha aparece tanto quando a atualização termina bem quanto quando ela volta para a versão anterior. Se a rodada não teve disputa, ou se ela não foi medida, a linha não aparece. Nada muda para quem atualiza pela linha de comando. Não exige ação.
|
||||
|
||||
Contribuição de @webtecnica (#1040).
|
||||
@@ -309,6 +309,65 @@ describe("GET /api/v1/system/version", () => {
|
||||
expect(body.data.run.superseded).toBe(false);
|
||||
});
|
||||
|
||||
/**
|
||||
* O ESTADO PERSISTIDO DA RODADA CHEGANDO À TELA — o coração do #1040.
|
||||
*
|
||||
* O kit grava disputa/retentativas/passada nas três colunas do run; quem
|
||||
* conta isso para quem clicou é ESTA rota. Se ela devolvesse só
|
||||
* sucesso/falha (o `status`), a tela continuaria contando uma história mais
|
||||
* simples que a acontecida — o aviso morreria no `.update.log`, no disco da
|
||||
* VPS, exatamente como o issue descreve.
|
||||
*/
|
||||
it("⭐ devolve a rodada do banco do run — disputa, retentativas e passada", async () => {
|
||||
versionRow.current_version = "1.0.0";
|
||||
runRow = {
|
||||
id: "55555555-5555-4555-8555-555555555555",
|
||||
status: "success",
|
||||
last_step: "app",
|
||||
dispatched_at: new Date().toISOString(),
|
||||
from_version: "1.0.0",
|
||||
to_version: "1.1.0",
|
||||
log_tail: "deadlock detected … 2ª passada fechou",
|
||||
disputa_de_banco: true,
|
||||
retentativas_do_banco: 1,
|
||||
passada_do_banco: 2,
|
||||
};
|
||||
vi.mocked(loadAuthUser).mockResolvedValue(OWNER as never);
|
||||
const { GET } = await import("../version/route");
|
||||
const body = await (await GET(get())).json();
|
||||
|
||||
expect(body.data.run.status).toBe("success");
|
||||
expect(body.data.run.rodada_do_banco).toEqual({
|
||||
disputa: true,
|
||||
retentativas: 1,
|
||||
passada: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("coluna nula é 'não medido': a rota devolve null, não zero", async () => {
|
||||
// Rodada que não passou pelo banco (atualização só de código) deixa as três
|
||||
// colunas nulas. `null` é o que mantém a tela calada; `0` viraria a frase
|
||||
// "não houve disputa" para uma disputa que ninguém mediu.
|
||||
versionRow.current_version = "1.0.0";
|
||||
runRow = {
|
||||
id: "55555555-5555-4555-8555-555555555555",
|
||||
status: "success",
|
||||
last_step: "codigo",
|
||||
dispatched_at: new Date().toISOString(),
|
||||
from_version: "1.0.0",
|
||||
to_version: "1.1.0",
|
||||
log_tail: "",
|
||||
disputa_de_banco: null,
|
||||
retentativas_do_banco: null,
|
||||
passada_do_banco: null,
|
||||
};
|
||||
vi.mocked(loadAuthUser).mockResolvedValue(OWNER as never);
|
||||
const { GET } = await import("../version/route");
|
||||
const body = await (await GET(get())).json();
|
||||
|
||||
expect(body.data.run.rodada_do_banco).toBeNull();
|
||||
});
|
||||
|
||||
it("depois de um rollback, quem não é dono também vê a versão que está no ar", async () => {
|
||||
versionRow.current_version = "1.1.0";
|
||||
runRow = {
|
||||
|
||||
@@ -93,3 +93,93 @@ describe("tela de atualização — o botão não fica atrás do changelog", ()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* O #1040: a rodada de atualização sabe que houve disputa de banco, e a tela
|
||||
* CONTA isso — em português de gente — e diz onde está o detalhe. Até aqui o
|
||||
* aviso morria só no `.update.log`, no disco da VPS, e quem apertou o botão
|
||||
* via "sucesso" sem saber que a base estava ocupada.
|
||||
*/
|
||||
function rodada(overrides: Partial<NonNullable<SystemVersion["run"]>> = {}): SystemVersion {
|
||||
return {
|
||||
current_version: "1.0.0",
|
||||
is_owner: true,
|
||||
latest_version: "1.1.0",
|
||||
update_available: false,
|
||||
agent_online: true,
|
||||
just_updated: true,
|
||||
notes: null,
|
||||
run: {
|
||||
id: "55555555-5555-4555-8555-555555555555",
|
||||
status: "success",
|
||||
last_step: "app",
|
||||
from_version: "1.0.0",
|
||||
to_version: "1.1.0",
|
||||
log_tail: "",
|
||||
rodada_do_banco: { disputa: true, retentativas: 1, passada: 2 },
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("tela de atualização — a rodada conta a disputa do banco", () => {
|
||||
it("terminou com disputa: a tela mostra o resumo em português de gente", () => {
|
||||
renderTela(rodada());
|
||||
|
||||
// Não é log cru nem código: é frase. Os números vêm do estado persistido.
|
||||
expect(
|
||||
screen.getByText(
|
||||
/O banco estava em disputa com o sistema no ar: foram duas passadas e uma retentativa/,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("e diz ONDE está o detalhe — o .update.log, que até aqui era o único lugar que sabia", () => {
|
||||
renderTela(rodada());
|
||||
|
||||
const ponteiro = screen.getByText(/\.update\.log/);
|
||||
expect(ponteiro.textContent).toContain("na pasta do projeto no servidor");
|
||||
});
|
||||
|
||||
it("o resumo e o ponteiro vêm JUNTOS — apontar o log sem contar o resumo seria jogar o problema pra trás da tela", () => {
|
||||
renderTela(rodada());
|
||||
|
||||
const resumo = screen.getByText(/duas passadas/);
|
||||
const ponteiro = screen.getByText(/\.update\.log/);
|
||||
expect(
|
||||
Boolean(resumo.compareDocumentPosition(ponteiro) & Node.DOCUMENT_POSITION_FOLLOWING),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rodada medida SEM disputa: conta que foi de uma vez e não aponta log nenhum", () => {
|
||||
// O controle do issue: aviso sem motivo vira ruído, e quem para de ler o
|
||||
// ruído para de ler o aviso. Endereço de log é linha só quando há o que ler.
|
||||
renderTela(
|
||||
rodada({ rodada_do_banco: { disputa: false, retentativas: 0, passada: 1 } }),
|
||||
);
|
||||
|
||||
expect(screen.getByText(/atualizou de uma vez, na primeira passada/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/\.update\.log/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("ninguém mediu a rodada: a tela fica calada — nem resumo, nem ponteiro", () => {
|
||||
renderTela(rodada({ rodada_do_banco: null }));
|
||||
|
||||
expect(screen.queryByText(/disputa com o sistema/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/\.update\.log/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("no desfecho em que o servidor voltou atrás também: quem clicou tem o direito de saber", () => {
|
||||
renderTela({
|
||||
...rodada({
|
||||
status: "failed_rolled_back",
|
||||
rodada_do_banco: { disputa: true, retentativas: 2, passada: 3 },
|
||||
}),
|
||||
just_updated: false,
|
||||
update_available: true,
|
||||
});
|
||||
|
||||
expect(screen.getByText(/foram 3 passadas e 2 retentativas/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/\.update\.log/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -164,6 +164,11 @@ export function UpdatePanel() {
|
||||
// retentativas custou e em qual passada fechou. Sem registro na rodada isto é
|
||||
// `null`, e a tela fica calada em vez de afirmar zero.
|
||||
const contaDoBanco = textoDaRodadaDoBanco(data.run?.rodada_do_banco);
|
||||
// O ponteiro para o `.update.log` vem junto do resumo, mas só quando a
|
||||
// rodada REGISTROU disputa — é aí que existe passada por passada para ler.
|
||||
// Rodada medida e limpa fica com o resumo só: endereço de log que ninguém
|
||||
// precisa abrir é linha a mais para ignorar, e quem ignora uma ignora a próxima.
|
||||
const detalheNoLog = data.run?.rodada_do_banco?.disputa === true;
|
||||
|
||||
if (data.just_updated) {
|
||||
const pedida = semV(data.run?.to_version);
|
||||
@@ -183,6 +188,18 @@ export function UpdatePanel() {
|
||||
{contaDoBanco ? (
|
||||
<p className="mt-3 text-sm text-muted-foreground">{t(contaDoBanco)}</p>
|
||||
) : null}
|
||||
{/* O resumo é a ponta; quem quer ver passada por passada precisa saber
|
||||
ONDE. Esse endereço até aqui só existia no log do servidor — quem
|
||||
apertou o botão não tinha como chegar lá sem SSH. Só quando HÁ o que
|
||||
procurar lá (disputa registrada): ponteiro em rodada limpa é ruído, e
|
||||
quem para de ler o ruído para de ler o aviso. */}
|
||||
{detalheNoLog ? (
|
||||
<p className="mt-3 text-sm text-muted-foreground">
|
||||
{t(
|
||||
"O detalhe de cada passada está no arquivo .update.log, na pasta do projeto no servidor.",
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -212,6 +229,13 @@ export function UpdatePanel() {
|
||||
{contaDoBanco ? (
|
||||
<p className="mt-3 text-sm text-muted-foreground">{t(contaDoBanco)}</p>
|
||||
) : null}
|
||||
{detalheNoLog ? (
|
||||
<p className="mt-3 text-sm text-muted-foreground">
|
||||
{t(
|
||||
"O detalhe de cada passada está no arquivo .update.log, na pasta do projeto no servidor.",
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
<DetalhesTecnicos texto={data.run.log_tail} />
|
||||
<Saida
|
||||
botao={false}
|
||||
|
||||
@@ -5908,6 +5908,9 @@ export const DICIONARIO: Traducoes = {
|
||||
"Detalhes técnicos (útil se for pedir ajuda)": {
|
||||
es: "Detalles técnicos (útil si vas a pedir ayuda)",
|
||||
},
|
||||
"O detalhe de cada passada está no arquivo .update.log, na pasta do projeto no servidor.": {
|
||||
es: "El detalle de cada pasada está en el archivo .update.log, en la carpeta del proyecto en el servidor.",
|
||||
},
|
||||
"Atualização do sistema": { es: "Actualización del sistema" },
|
||||
"Guardando uma cópia de segurança dos seus dados": {
|
||||
es: "Guardando una copia de seguridad de tus datos",
|
||||
|
||||
Reference in New Issue
Block a user