feat(oncall): tag On-Call as Beta in the nav and page headers

This commit is contained in:
ktx-abhay
2026-11-04 11:58:22 +05:30
parent d23e5e56e5
commit f75f6b1e1b
10 changed files with 71 additions and 2 deletions
+9 -2
View File
@@ -868,10 +868,17 @@ describe("GATE_PREDICATES", () => {
expect(GATE_PREDICATES.downtimes(ctx({ isEnterprise: true }))).toBe(false);
});
it("marks Downtimes and Alert Calibration as Beta in the Reliability flyout, and nothing else there", () => {
it("marks Downtimes, Alert Calibration and every On-Call entry as Beta in the Reliability flyout, and nothing else there", () => {
const reliability = NAV_GROUPS.find((g) => g.children.some((c) => c.name === "downtimes"))!;
const beta = reliability.children.filter((c) => c.beta).map((c) => c.name);
expect(beta).toEqual(["downtimes", "alertCalibration"]);
expect(beta).toEqual([
"downtimes",
"alertCalibration",
"onCallResponses",
"onCallTeams",
"onCallRouting",
"onCallMine",
]);
});
it("puts Downtimes after Incidents when the rail carries a downtimes item", () => {
+4
View File
@@ -237,6 +237,7 @@ export const NAV_GROUPS: NavGroupDef[] = [
name: "onCallResponses",
categoryKey: "menu.onCall",
gate: "oncall",
beta: true,
},
{
titleKey: "oncall.teams",
@@ -244,6 +245,7 @@ export const NAV_GROUPS: NavGroupDef[] = [
name: "onCallTeams",
categoryKey: "menu.onCall",
gate: "oncall",
beta: true,
},
{
titleKey: "oncall.routingNav",
@@ -251,6 +253,7 @@ export const NAV_GROUPS: NavGroupDef[] = [
name: "onCallRouting",
categoryKey: "menu.onCall",
gate: "oncall",
beta: true,
},
// Same icon as the page's own header, so the menu and the page read as one place.
{
@@ -259,6 +262,7 @@ export const NAV_GROUPS: NavGroupDef[] = [
name: "onCallMine",
categoryKey: "menu.onCall",
gate: "oncall",
beta: true,
},
// Where external alerts (Grafana, Alertmanager, etc.) feed Incidents.
// Gated on incidentList, not alertList: this only makes sense where
+11
View File
@@ -158,6 +158,17 @@ describe("OnCallMine", () => {
service.resolvedSchedule.mockResolvedValue({ data: [] } as any);
});
/// Mounts the real page layout so the tag is proven to reach the rendered header.
it("tags the page title as Beta", async () => {
const wrapper = mount(OnCallMine, {
global: { plugins: [i18n, store], stubs: { ...stubs, OPageLayout: false } },
});
await flushPromises();
const badge = wrapper.find('[data-test="beta-badge"]');
expect(badge.exists()).toBe(true);
expect(badge.text()).toBe(String(i18n.global.t("common.beta")));
});
it("serves a revisit from cache", async () => {
const first = render();
await flushPromises();
+4
View File
@@ -23,6 +23,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
icon="person"
scroll
>
<template #title-trail>
<BetaBadge class="shrink-0" />
</template>
<template #actions>
<!-- Personal and org-wide, so it heads the page rather than sitting under one team. -->
<!-- Away only takes someone off a team's rotation, so a reader on no team has nothing to mark. -->
@@ -464,6 +467,7 @@ import OnCallReachRow from "@/components/oncall/OnCallReachRow.vue";
import OnCallSetupBanner from "@/components/oncall/OnCallSetupBanner.vue";
import OnCallSlackCard from "@/components/oncall/OnCallSlackCard.vue";
import OTag from "@/lib/core/Badge/OTag.vue";
import BetaBadge from "@/components/common/BetaBadge.vue";
import OButton from "@/lib/core/Button/OButton.vue";
import OContent from "@/lib/core/Content/OContent.vue";
import OEmptyState from "@/lib/core/EmptyState/OEmptyState.vue";
@@ -313,6 +313,17 @@ describe("OnCallResponses", () => {
service.getResponse.mockResolvedValue({ data: { events: [] } } as any);
});
/// Mounts the real page layout so the tag is proven to reach the rendered header.
it("tags the page title as Beta", async () => {
const wrapper = mountTracked(OnCallResponses, {
global: { plugins: [i18n, store], stubs: { ...stubs, OPageLayout: false } },
});
await flushPromises();
const badge = wrapper.find('[data-test="beta-badge"]');
expect(badge.exists()).toBe(true);
expect(badge.text()).toBe(String(i18n.global.t("common.beta")));
});
function page(over: Record<string, unknown> = {}, source = "al_ckt") {
return {
id: "resp_1",
+4
View File
@@ -21,6 +21,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:title="t('oncall.responsesTitle')"
icon="notifications-active"
>
<template #title-trail>
<BetaBadge class="shrink-0" />
</template>
<template #actions>
<!-- Whether the reader is themselves on call, beside the buttons rather
than in the body: it qualifies the whole screen, not one section. -->
@@ -821,6 +824,7 @@ import OnCallShiftBanner from "@/components/oncall/OnCallShiftBanner.vue";
import { useOnCallPermissions } from "@/composables/useOnCallPermissions";
import OText from "@/lib/core/Typography/OText.vue";
import OTruncatedText from "@/lib/core/Typography/OTruncatedText.vue";
import BetaBadge from "@/components/common/BetaBadge.vue";
import OButton from "@/lib/core/Button/OButton.vue";
import useBreakpoint from "@/composables/useBreakpoint";
import { useListViewState } from "@/composables/useListViewState";
@@ -259,6 +259,17 @@ describe("OnCallRouting", () => {
correlation.getDimensionAnalytics.mockResolvedValue({ data: { dimensions: [] } } as any);
});
/// Mounts the real page layout so the tag is proven to reach the rendered header.
it("tags the page title as Beta", async () => {
const wrapper = mount(OnCallRouting, {
global: { plugins: [i18n, store], stubs: { ...stubs, OPageLayout: false } },
});
await flushPromises();
const badge = wrapper.find('[data-test="beta-badge"]');
expect(badge.exists()).toBe(true);
expect(badge.text()).toBe(String(i18n.global.t("common.beta")));
});
/// The whole point of the org screen: no team_id, so shadowing is computed
/// across every team's rules, not one team's slice.
it("asks for the org-wide rules, not one team's", async () => {
+2
View File
@@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
icon="alt-route"
>
<template #title-trail>
<BetaBadge class="shrink-0" />
<OIcon
name="info-outline"
size="sm"
@@ -383,6 +384,7 @@ import type { RuleDraft } from "@/components/oncall/OnCallRuleEditor.vue";
import type { SimulatorQuery } from "@/components/oncall/OnCallRoutingSimulator.vue";
import OnCallUnroutedQueue from "@/components/oncall/OnCallUnroutedQueue.vue";
import type { UnroutedFilters } from "@/components/oncall/OnCallUnroutedQueue.vue";
import BetaBadge from "@/components/common/BetaBadge.vue";
import OButton from "@/lib/core/Button/OButton.vue";
import useBreakpoint from "@/composables/useBreakpoint";
import { useOnCallPermissions } from "@/composables/useOnCallPermissions";
+11
View File
@@ -169,6 +169,17 @@ describe("OnCallTeams", () => {
service.getRoutingConfig.mockResolvedValue({ data: { default_team_id: null } } as any);
});
/// Mounts the real page layout so the tag is proven to reach the rendered header.
it("tags the page title as Beta", async () => {
const wrapper = mount(OnCallTeams, {
global: { plugins: [i18n, store], stubs: { ...stubs, OPageLayout: false } },
});
await flushPromises();
const badge = wrapper.find('[data-test="beta-badge"]');
expect(badge.exists()).toBe(true);
expect(badge.text()).toBe(String(i18n.global.t("common.beta")));
});
/// The setup checklist's first step sends somebody here already meaning to
/// create a team. Landing them on the list to hunt for the button is the same
/// click asked twice.
+4
View File
@@ -21,6 +21,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:title="t('oncall.teamsTitle')"
icon="group-work"
>
<template #title-trail>
<BetaBadge class="shrink-0" />
</template>
<template #actions>
<OButton
v-if="canConfigure"
@@ -309,6 +312,7 @@ import { computed, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import BetaBadge from "@/components/common/BetaBadge.vue";
import OButton from "@/lib/core/Button/OButton.vue";
import OEmptyState from "@/lib/core/EmptyState/OEmptyState.vue";
import type { EmptyStateAction } from "@/lib/core/EmptyState/presets";