atr/templates/voting-selected-revision.html (174 lines of code) (raw):
{% extends "layouts/base.html" %}
{% block title %}
Start release vote ~ ATR
{% endblock title %}
{% block description %}
Initiate a vote for a release candidate.
{% endblock description %}
{% block content %}
<p class="d-flex justify-content-between align-items-center">
<a href="{{ as_url(routes.compose.selected, project_name=release.project.name, version_name=release.version) }}"
class="atr-back-link">← Back to Compose {{ release.short_display_name }}</a>
<span>
<strong class="atr-phase-one atr-phase-symbol">①</strong>
<span class="atr-phase-one atr-phase-label">COMPOSE</span>
<span class="atr-phase-arrow">→</span>
<span class="atr-phase-symbol-other">②</span>
<span class="atr-phase-arrow">→</span>
<span class="atr-phase-symbol-other">③</span>
</span>
</p>
<h1 class="mb-4">
Start voting on <strong>{{ release.project.short_display_name }}</strong> <em>{{ release.version }}</em>
</h1>
<div class="px-3 py-4 mb-4 bg-light border rounded">
<!-- TODO: Specify the draft revision too? -->
<p class="mb-0">
Starting a vote for this draft release will cause an email to be sent to the appropriate mailing list, and advance the draft to the "waiting for vote result" phase, unless you send a test message to yourself.
</p>
</div>
<div class="p-3 mb-4 bg-warning-subtle border border-warning rounded">
<strong>Note:</strong> This feature is currently in development. The form below only sends email to <a href="https://lists.apache.org/list.html?user-tests@tooling.apache.org">a test mailing list</a> or yourself.
</div>
<form method="post"
id="vote-initiate-form"
class="atr-canary py-4 px-5"
action="{{ as_url(routes.voting.selected_revision, project_name=release.project.name, version_name=release.version, revision=release.revision) }}">
{{ form.hidden_tag() if form.hidden_tag }}
{{ form.release_name }}
<div class="mb-4">
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-3 text-md-end fw-medium">{{ form.mailing_list.label }}</div>
<div class="col-md-9">
<div class="d-flex gap-4 mb-2">
{% for subfield in form.mailing_list %}
<div class="form-check">
{{ subfield(class_='form-check-input') }}
{{ subfield.label(class_='form-check-label') }}
</div>
{% endfor %}
</div>
{% if form.mailing_list.errors %}
<div class="invalid-feedback d-block">
{% for error in form.mailing_list.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
<small class="form-text text-muted">NOTE: The limited options above are provided for testing purposes. In the finished version of ATR, you will be able to send to your own specified mailing lists.</small>
</div>
</div>
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-3 text-md-end fw-medium pt-2">{{ form.vote_duration.label }}</div>
<div class="col-md-9">
{{ form.vote_duration(class_='form-select w-75') }}
{% if form.vote_duration.errors %}
<div class="invalid-feedback d-block">
{% for error in form.vote_duration.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-3 text-md-end fw-medium pt-2">{{ form.subject.label }}</div>
<div class="col-md-9">
{{ form.subject(class_='form-control w-75') }}
{% if form.subject.errors %}
<div class="invalid-feedback d-block">
{% for error in form.subject.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3 pb-3">
<div class="col-md-3 text-md-end fw-medium pt-2">{{ form.body.label }}</div>
<div class="col-md-9">
{{ form.body(class_='form-control font-monospace', rows='20') }}
{% if form.body.errors %}
<div class="invalid-feedback d-block">
{% for error in form.body.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
<small class="form-text text-muted">Edit the vote email content as needed. Placeholders like [KEY_FINGERPRINT], [DURATION], [REVIEW_URL], and [YOUR_ASF_ID] will be filled in automatically when the email is sent.</small>
</div>
</div>
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-9 offset-md-3">
<details>
<summary class="text-muted">Show live preview</summary>
<pre id="email-body-preview"
data-preview-url="{{ as_url(routes.draft.vote_preview, project_name=release.project.name, version_name=release.version) }}"
data-asf-uid="{{ current_user.uid }}"
class="mt-2 p-3 bg-white border rounded font-monospace overflow-auto"></pre>
</details>
</div>
</div>
</div>
<div class="mt-4 col-md-9 offset-md-3">
{{ form.submit(class_='btn btn-primary') }}
<a href="{{ as_url(routes.compose.selected, project_name=release.project.name, version_name=release.version) }}"
class="btn btn-link text-secondary">Cancel</a>
</div>
</form>
{% endblock content %}
{% block javascripts %}
{{ super() }}
<script>
document.addEventListener("DOMContentLoaded", () => {
let debounceTimeout;
const debounceDelay = 500;
const bodyTextarea = document.getElementById("body");
const voteDurationInput = document.getElementById("vote_duration");
const previewPre = document.getElementById("email-body-preview");
const voteForm = document.getElementById("vote-initiate-form");
if (!bodyTextarea || !previewPre || !voteDurationInput || !voteForm) {
console.error("Required elements for preview not found.");
return;
}
const previewUrl = previewPre.dataset.previewUrl;
const csrfTokenInput = voteForm.querySelector('input[name="csrf_token"]');
if (!previewUrl || !csrfTokenInput) {
console.error("Required data attributes or CSRF token not found.");
return
}
const csrfToken = csrfTokenInput.value;
function fetchAndUpdatePreview() {
const bodyContent = bodyTextarea.value;
const voteDuration = voteDurationInput.value;
fetch(previewUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-CSRFToken": csrfToken
},
body: new URLSearchParams({
"body": bodyContent,
"vote_duration": voteDuration,
"csrf_token": csrfToken
})
})
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error(`HTTP error ${response.status}: ${text}`)
});
}
return response.text();
})
.then(previewText => {
previewPre.textContent = previewText;
})
.catch(error => {
console.error("Error fetching email preview:", error);
previewPre.textContent = `Error loading preview:\n${error.message}`;
});
}
bodyTextarea.addEventListener("input", () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(fetchAndUpdatePreview, debounceDelay);
});
voteDurationInput.addEventListener("change", () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(fetchAndUpdatePreview, debounceDelay);
});
fetchAndUpdatePreview();
});
</script>
{% endblock javascripts %}