diff --git a/client/src/components/History/CurrentHistory/HistoryCounter.vue b/client/src/components/History/CurrentHistory/HistoryCounter.vue
index c70ec3a91e6b..b5fddac8008a 100644
--- a/client/src/components/History/CurrentHistory/HistoryCounter.vue
+++ b/client/src/components/History/CurrentHistory/HistoryCounter.vue
@@ -54,8 +54,8 @@ const historyPreferredObjectStoreId = ref(props.history.preferred_object_store_i
const niceHistorySize = computed(() => prettyBytes(historySize.value));
-function onDashboard() {
- router.push({ name: "HistoryOverviewInAnalysis", params: { historyId: props.history.id } });
+function onClickStatistics() {
+ router.push({ name: "HistoryStatistics", params: { historyId: props.history.id } });
}
function setFilter(filter: string) {
@@ -121,13 +121,13 @@ onMounted(() => {
+ data-description="statistics dashboard button"
+ @click="onClickStatistics">
{{ niceHistorySize }}
diff --git a/client/src/components/History/HistoryView.test.js b/client/src/components/History/HistoryView.test.js
index 37fa9c79d495..68091c96aeed 100644
--- a/client/src/components/History/HistoryView.test.js
+++ b/client/src/components/History/HistoryView.test.js
@@ -94,7 +94,7 @@ describe("History center panel View", () => {
expect(tags.text()).toContain("tag_1");
expect(tags.text()).toContain("tag_2");
// HistoryCounter
- expect(wrapper.find("[data-description='storage dashboard button']").attributes("disabled")).toBeTruthy();
+ expect(wrapper.find("[data-description='statistics dashboard button']").attributes("disabled")).toBeTruthy();
expect(wrapper.find("[data-description='show active items button']").text()).toEqual("8");
expect(wrapper.find("[data-description='include deleted items button']").text()).toEqual("1");
expect(wrapper.find("[data-description='include hidden items button']").text()).toEqual("2");
diff --git a/client/src/components/History/HistoryView.vue b/client/src/components/History/HistoryView.vue
index a6dbee74fd1c..0dea4190db85 100644
--- a/client/src/components/History/HistoryView.vue
+++ b/client/src/components/History/HistoryView.vue
@@ -33,6 +33,7 @@
:selected-collections.sync="selectedCollections"
:show-controls="false"
@view-collection="onViewCollection" />
+
diff --git a/client/src/components/History/Statistics/Charts/BarChart.test.ts b/client/src/components/History/Statistics/Charts/BarChart.test.ts
new file mode 100644
index 000000000000..0e86b15ba4b2
--- /dev/null
+++ b/client/src/components/History/Statistics/Charts/BarChart.test.ts
@@ -0,0 +1,229 @@
+import { mount } from "@vue/test-utils";
+
+import type { DataValuePoint } from ".";
+
+import BarChart from "./BarChart.vue";
+
+// Duplicated interface from BarChart.vue because of https://github.com/vuejs/core/issues/4294
+interface BarChartProps {
+ title: string;
+ data: DataValuePoint[];
+ description?: string;
+ width?: number;
+ height?: number;
+ enableTooltips?: boolean;
+ enableSelection?: boolean;
+ labelFormatter?: (dataPoint?: DataValuePoint | null) => string;
+}
+
+const TEST_DATA = [
+ { id: "id1", label: "foo", value: 1 },
+ { id: "id2", label: "bar", value: 2 },
+];
+
+function mountBarChartWrapper(props: BarChartProps) {
+ return mount(BarChart, {
+ propsData: props,
+ });
+}
+
+describe("BarChart.vue", () => {
+ describe("Chart Rendering", () => {
+ it("should render a bar chart when there is data", () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ });
+ expect(wrapper.find("svg").exists()).toBe(true);
+ });
+
+ it("should not render a bar chart when there is no data", () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: [],
+ });
+ expect(wrapper.find("svg").exists()).toBe(false);
+ });
+
+ it("should render a bar chart with the correct title", () => {
+ const title = "Test Bar Chart";
+ const wrapper = mountBarChartWrapper({
+ title,
+ data: TEST_DATA,
+ });
+ expect(wrapper.find("h3").text()).toBe(title);
+ });
+
+ it("should render a bar chart with the correct description", () => {
+ const description = "Test description";
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ description,
+ });
+ expect(wrapper.find(".chart-description").text()).toBe(description);
+ });
+
+ it("should render a bar chart with the correct width and height", () => {
+ const width = 500;
+ const height = 500;
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ width,
+ height,
+ });
+ expect(wrapper.find("svg").attributes("width")).toBe(width.toString());
+ expect(wrapper.find("svg").attributes("height")).toBe(height.toString());
+ });
+
+ it("should render a bar chart with the correct number of bars", () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ });
+ expect(wrapper.findAll(".bar").length).toBe(TEST_DATA.length);
+ });
+
+ it("should render a bar chart with the correct number of legend items", () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ });
+ expect(wrapper.findAll(".legend-item").length).toBe(TEST_DATA.length);
+ });
+
+ it("should render a bar chart with the correct legend labels", () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ });
+ TEST_DATA.forEach((dataPoint, index) => {
+ expect(wrapper.findAll(".legend-item").at(index).text()).toContain(dataPoint.label);
+ });
+ });
+
+ it("should refresh the bar chart and legend when the data prop changes", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ });
+ const newTestData = [
+ ...TEST_DATA,
+ { id: "id3", label: "baz", value: 3 },
+ { id: "id4", label: "qux", value: 4 },
+ ];
+ await wrapper.setProps({
+ data: newTestData,
+ });
+ expect(wrapper.findAll(".bar").length).toBe(newTestData.length);
+ expect(wrapper.findAll(".legend-item").length).toBe(newTestData.length);
+ });
+
+ it("should refresh the bar chart and legend when the labelFormatter prop changes", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ });
+ const newLabelFormatter = (dataPoint?: DataValuePoint | null) => {
+ return dataPoint?.label.toUpperCase() || "";
+ };
+ await wrapper.setProps({
+ labelFormatter: newLabelFormatter,
+ });
+ TEST_DATA.forEach((dataPoint, index) => {
+ expect(wrapper.findAll(".legend-item").at(index).text()).toContain(newLabelFormatter(dataPoint));
+ });
+ });
+ });
+
+ describe("Chart Options", () => {
+ describe("Chart Tooltips", () => {
+ it("should display chart-tooltip when a bar is hovered and enableTooltips is true", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableTooltips: true,
+ });
+ expect(wrapper.find(".chart-tooltip").isVisible()).toBe(false);
+ await wrapper.find(".bar").trigger("mouseenter");
+ expect(wrapper.find(".chart-tooltip").isVisible()).toBe(true);
+ });
+
+ it("should not display chart-tooltip when a bar is hovered and enableTooltips is false", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableTooltips: false,
+ });
+ expect(wrapper.find(".chart-tooltip").isVisible()).toBe(false);
+ await wrapper.find(".bar").trigger("mouseenter");
+ expect(wrapper.find(".chart-tooltip").isVisible()).toBe(false);
+ });
+
+ it("should display the correct label in the chart-tooltip when a bar is hovered and enableTooltips is true", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableTooltips: true,
+ });
+ await wrapper.find(".bar").trigger("mouseenter");
+ expect(wrapper.find(".chart-tooltip").text()).toContain(TEST_DATA.at(0)?.label);
+ });
+ });
+
+ describe("Chart Selection", () => {
+ it("should display the selection-info when a bar is clicked and enableSelection is true", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableSelection: true,
+ });
+ await wrapper.find(".bar").trigger("click");
+ expect(wrapper.find(".selection-info").exists()).toBe(true);
+ });
+
+ it("should not display the selection-info when a bar is clicked and enableSelection is false", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableSelection: false,
+ });
+ await wrapper.find(".bar").trigger("click");
+ expect(wrapper.find(".selection-info").exists()).toBe(false);
+ });
+
+ it("should emit selection-changed event when a bar is clicked and enableSelection is true", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableSelection: true,
+ });
+ expect(wrapper.emitted("selection-changed")).toBeFalsy();
+ await wrapper.find(".bar").trigger("click");
+ expect(wrapper.emitted("selection-changed")).toBeTruthy();
+ });
+
+ it("should not emit selection-changed event when a bar is clicked and enableSelection is false", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableSelection: false,
+ });
+ expect(wrapper.emitted("selection-changed")).toBeFalsy();
+ await wrapper.find(".bar").trigger("click");
+ expect(wrapper.emitted("selection-changed")).toBeFalsy();
+ });
+
+ it("should display the correct selection info when a bar is clicked and enableSelection is true", async () => {
+ const wrapper = mountBarChartWrapper({
+ title: "Test Bar Chart",
+ data: TEST_DATA,
+ enableSelection: true,
+ });
+ await wrapper.find(".bar").trigger("click");
+ expect(wrapper.find(".selection-info").text()).toContain(TEST_DATA.at(0)?.label);
+ });
+ });
+ });
+});
diff --git a/client/src/components/History/Statistics/Charts/BarChart.vue b/client/src/components/History/Statistics/Charts/BarChart.vue
new file mode 100644
index 000000000000..c45717d6f1f4
--- /dev/null
+++ b/client/src/components/History/Statistics/Charts/BarChart.vue
@@ -0,0 +1,408 @@
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
{{ description }}
+
+
+
+
+
+ Selected: {{ selectedDataPoint.label }}
+
+
+
+
+
+
No data to display. Populate some data and come back.
+
+
+
+
+
+
diff --git a/client/src/components/History/Statistics/Charts/formatters.ts b/client/src/components/History/Statistics/Charts/formatters.ts
new file mode 100644
index 000000000000..565922a65b0a
--- /dev/null
+++ b/client/src/components/History/Statistics/Charts/formatters.ts
@@ -0,0 +1,11 @@
+import { bytesToString } from "@/utils/utils";
+
+import type { DataValuePoint } from ".";
+
+export function bytesLabelFormatter(dataPoint?: DataValuePoint | null): string {
+ return dataPoint ? `${dataPoint.label}: ${bytesToString(dataPoint.value)}` : "No data";
+}
+
+export function bytesValueFormatter(value: number): string {
+ return bytesToString(value);
+}
diff --git a/client/src/components/History/Statistics/Charts/index.ts b/client/src/components/History/Statistics/Charts/index.ts
new file mode 100644
index 000000000000..40d9066a55d7
--- /dev/null
+++ b/client/src/components/History/Statistics/Charts/index.ts
@@ -0,0 +1,5 @@
+export interface DataValuePoint {
+ id: string;
+ label: string;
+ value: number;
+}
diff --git a/client/src/components/History/Statistics/HistoriesStorageOverview.vue b/client/src/components/History/Statistics/HistoriesStorageOverview.vue
new file mode 100644
index 000000000000..237e2a56459b
--- /dev/null
+++ b/client/src/components/History/Statistics/HistoriesStorageOverview.vue
@@ -0,0 +1,241 @@
+
+
+
+
+
{{ localize("Go to Storage Dashboard") }}
+
+
+ Here you can find various graphs displaying the storage size taken by all your histories.
+
+
+
+ Note: these graphs include deleted histories. Remember that, even if you delete histories, they still
+ take up storage space. However, you can free up the storage space by permanently deleting them from the
+ Discarded Items section of the
+ Storage Manager page or by selecting them
+ individually in the graph and clicking the Permanently Delete button.
+
+
+
+
+
+
+
+
+ {{ localize(`Top ${numberOfHistoriesToDisplay} Histories by Size`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/History/Statistics/HistoryCarbonEmissions.vue b/client/src/components/History/Statistics/HistoryCarbonEmissions.vue
new file mode 100644
index 000000000000..7de4dee4c706
--- /dev/null
+++ b/client/src/components/History/Statistics/HistoryCarbonEmissions.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+ Here is an estimated summary of the total carbon footprint of all datasets and jobs in the current
+ history. These estimates can help you get a better sense of your history's carbon footprint which
+ may ultimately serve as a motivation to use computing resources more responsibly.
+
+
+
+
+
+ Click here to learn more about how we calculate your carbon emissions data.
+
+
+
+
+
+
+
+
+
+ 1. Based off of the global carbon intensity value of
+ {{ worldwideCarbonIntensity }}.
+
+
+ 1. based off of this galaxy instance's configured location of
+ {{ geographicalServerLocationName }}, which has a carbon intensity value of {{ carbonIntensity }} gCO2/kWh.
+
+
+
+
+
+ 2. Using the global default power usage effectiveness value of
+ {{ worldwidePowerUsageEffectiveness }}.
+
+
+
+
+
+
diff --git a/client/src/components/History/Statistics/HistoryStatistics.vue b/client/src/components/History/Statistics/HistoryStatistics.vue
new file mode 100644
index 000000000000..9fc4f44cb2a4
--- /dev/null
+++ b/client/src/components/History/Statistics/HistoryStatistics.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+ History Statistics
+
+
+ Here is a summary of the statistics of the history
+ {{ getHistoryNameById(props.historyId) }}.
+
+
+
+ As you continue to work on your current history, we collect statistics such as your history's storage
+ metrics or the aggregated energy usage of all jobs in the history.
+
+
+
+
+
+
+
+
diff --git a/client/src/components/History/Statistics/HistoryStorageOverview.vue b/client/src/components/History/Statistics/HistoryStorageOverview.vue
new file mode 100644
index 000000000000..7db826afd274
--- /dev/null
+++ b/client/src/components/History/Statistics/HistoryStorageOverview.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
{{ localize("Back to Dashboard") }}
+
History Storage Overview
+
+ Here you will find some Graphs displaying the storage taken by datasets in your history:
+ {{ getHistoryNameById(props.historyId) }}. You can use these graphs to identify the datasets that take the most space in your history. You can also
+ go to the
+ Histories Storage Overview page to see
+ the storage taken by all your histories.
+
+
+ Note: these graphs include deleted datasets. Remember that, even if you delete datasets, they still
+ take up storage space. However, you can free up the storage space by permanently deleting them from the
+ Discarded Items section of the
+ Storage Manager page or by selecting them
+ individually in the graph and clicking the Permanently Delete button.
+
+
+
+
+
+
+
+
+ {{ localize(`Top ${numberOfDatasetsToDisplay} Datasets by Size`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
TODO:
+
+
diff --git a/client/src/components/History/Statistics/RecoverableItemSizeTooltip.vue b/client/src/components/History/Statistics/RecoverableItemSizeTooltip.vue
new file mode 100644
index 000000000000..2806f31c1679
--- /dev/null
+++ b/client/src/components/History/Statistics/RecoverableItemSizeTooltip.vue
@@ -0,0 +1,33 @@
+
+
+
+
{{ label }}
+
{{ prettySize }}
+
This item is archived
+
Recoverable storage space
+
+
diff --git a/client/src/components/History/Statistics/SelectedItemActions.vue b/client/src/components/History/Statistics/SelectedItemActions.vue
new file mode 100644
index 000000000000..04b5b02604d3
--- /dev/null
+++ b/client/src/components/History/Statistics/SelectedItemActions.vue
@@ -0,0 +1,120 @@
+
+
+
+
+ {{ label }}
+
+
+ This {{ itemType }} is archived.
+ Total storage space taken: {{ prettySize }}.
+
+ This {{ itemType }} was deleted. You can undelete it or permanently delete it to free up
+ its storage space.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/History/Statistics/service.ts b/client/src/components/History/Statistics/service.ts
new file mode 100644
index 000000000000..2a83827c8cd8
--- /dev/null
+++ b/client/src/components/History/Statistics/service.ts
@@ -0,0 +1,66 @@
+import { datasetsFetcher, purgeHistoryDataset, undeleteHistoryDataset } from "@/api/datasets";
+import { archivedHistoriesFetcher, deleteHistory, historiesFetcher, undeleteHistory } from "@/api/histories";
+
+export interface ItemSizeSummary {
+ id: string;
+ name: string;
+ size: number;
+ deleted: boolean;
+ archived: boolean;
+}
+
+interface PurgeableItemSizeSummary extends ItemSizeSummary {
+ purged: boolean;
+}
+
+const itemSizeSummaryFields = "id,name,size,deleted,archived";
+
+export async function fetchAllHistoriesSizeSummary(): Promise
{
+ const nonPurgedHistoriesResponse = await historiesFetcher({
+ keys: itemSizeSummaryFields,
+ q: ["deleted", "purged"],
+ qv: ["None", "false"],
+ });
+ const nonPurgedArchivedHistories = await archivedHistoriesFetcher({
+ keys: itemSizeSummaryFields,
+ q: ["purged"],
+ qv: ["false"],
+ });
+ const allHistoriesTakingStorageResponse = [
+ ...(nonPurgedHistoriesResponse.data as ItemSizeSummary[]),
+ ...(nonPurgedArchivedHistories.data as ItemSizeSummary[]),
+ ];
+ return allHistoriesTakingStorageResponse;
+}
+
+export async function fetchHistoryContentsSizeSummary(historyId: string, limit = 5000) {
+ const response = await datasetsFetcher({
+ history_id: historyId,
+ keys: itemSizeSummaryFields,
+ limit,
+ order: "size-dsc",
+ q: ["purged", "history_content_type"],
+ qv: ["false", "dataset"],
+ });
+ return response.data as unknown as ItemSizeSummary[];
+}
+
+export async function undeleteHistoryById(historyId: string): Promise {
+ const response = await undeleteHistory({ history_id: historyId });
+ return response.data as unknown as ItemSizeSummary;
+}
+
+export async function purgeHistoryById(historyId: string): Promise {
+ const response = await deleteHistory({ history_id: historyId, purge: true });
+ return response.data as unknown as PurgeableItemSizeSummary;
+}
+
+export async function undeleteDatasetById(historyId: string, datasetId: string): Promise {
+ const data = await undeleteHistoryDataset(historyId, datasetId);
+ return data as unknown as ItemSizeSummary;
+}
+
+export async function purgeDatasetById(historyId: string, datasetId: string): Promise {
+ const data = await purgeHistoryDataset(historyId, datasetId);
+ return data as unknown as PurgeableItemSizeSummary;
+}
diff --git a/client/src/components/JobMetrics/AwsEstimate.vue b/client/src/components/JobMetrics/AwsEstimate.vue
index da194e56a6d4..2f69a523ec01 100644
--- a/client/src/components/JobMetrics/AwsEstimate.vue
+++ b/client/src/components/JobMetrics/AwsEstimate.vue
@@ -1,26 +1,34 @@
+
+
+
+
+
+ Carbon Footprint
+
+
+
+
+
+ 1. Based off of the global carbon intensity value of
+ {{ worldwideCarbonIntensity }}.
+
+
+ 1. based off of this galaxy instance's configured location of
+ {{ geographicalServerLocationName }}, which has a carbon intensity value of {{ carbonIntensity }} gCO2/kWh.
+
+
+
+
+
+ 2. Using the global default power usage effectiveness value of
+ {{ worldwidePowerUsageEffectiveness }}.
+
+
+
+
+
+
+ Learn more about how we calculate your carbon emissions data.
+
+
+
+
+
+
+
+
diff --git a/client/src/components/JobMetrics/JobMetrics.test.js b/client/src/components/JobMetrics/JobMetrics.test.ts
similarity index 78%
rename from client/src/components/JobMetrics/JobMetrics.test.js
rename to client/src/components/JobMetrics/JobMetrics.test.ts
index b8e934489e9d..c6984abc0db1 100644
--- a/client/src/components/JobMetrics/JobMetrics.test.js
+++ b/client/src/components/JobMetrics/JobMetrics.test.ts
@@ -4,9 +4,8 @@ import flushPromises from "flush-promises";
import { setActivePinia } from "pinia";
import { getLocalVue } from "tests/jest/helpers";
-import JobMetrics from "./JobMetrics";
+import JobMetrics from "./JobMetrics.vue";
-// Ignore all axios calls, data is mocked locally -- just say "OKAY!"
jest.mock("axios", () => ({
get: async () => {
return { response: { status: 200 } };
@@ -17,12 +16,23 @@ const localVue = getLocalVue();
describe("JobMetrics/JobMetrics.vue", () => {
it("should not render a div if no plugins found in store", async () => {
- const wrapper = mount(JobMetrics, {
- pinia: createTestingPinia(),
+ const pinia = createTestingPinia({
+ initialState: {
+ jobMetricsStore: {
+ jobMetricsByHdaId: {},
+ jobMetricsByJobId: {},
+ jobMetricsByLddaId: {},
+ },
+ },
+ });
+ setActivePinia(pinia);
+
+ const wrapper = mount(JobMetrics as any, {
propsData: {
- jobId: "9000",
+ jobId: "some-job-id",
},
localVue,
+ pinia,
});
await wrapper.vm.$nextTick();
@@ -30,7 +40,7 @@ describe("JobMetrics/JobMetrics.vue", () => {
});
it("should group plugins by type", async () => {
- const JOB_ID = "9000";
+ const JOB_ID = "some-job-id";
const mockMetricsResponse = [
{ plugin: "core", title: "runtime", value: 145 },
{ plugin: "core", title: "memory", value: 146 },
@@ -50,12 +60,12 @@ describe("JobMetrics/JobMetrics.vue", () => {
});
setActivePinia(pinia);
- const wrapper = mount(JobMetrics, {
- localVue,
- pinia,
+ const wrapper = mount(JobMetrics as any, {
propsData: {
jobId: JOB_ID,
},
+ localVue,
+ pinia,
});
// Wait for axios and rendering.
diff --git a/client/src/components/JobMetrics/JobMetrics.vue b/client/src/components/JobMetrics/JobMetrics.vue
index accbc853642e..1d46aae440cb 100644
--- a/client/src/components/JobMetrics/JobMetrics.vue
+++ b/client/src/components/JobMetrics/JobMetrics.vue
@@ -1,12 +1,11 @@
-
+
Job Metrics
@@ -201,21 +151,11 @@ const estimatedServerInstance = computed(() => {
-
+
diff --git a/client/src/components/Markdown/Elements/JobMetrics.vue b/client/src/components/Markdown/Elements/JobMetrics.vue
index c656b6d1402f..b17ca9529f60 100644
--- a/client/src/components/Markdown/Elements/JobMetrics.vue
+++ b/client/src/components/Markdown/Elements/JobMetrics.vue
@@ -69,13 +69,7 @@ watch(
:job-id="jobId"
:implicit-collection-jobs-id="implicitCollectionJobsId"
:select-job-options="selectJobOptions">
-
+
{{ footer }}
diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.test.js b/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.test.js
index eafa5a84b1a0..e0d2a17503a5 100644
--- a/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.test.js
+++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.test.js
@@ -1,11 +1,23 @@
+import { createTestingPinia } from "@pinia/testing";
import { shallowMount } from "@vue/test-utils";
import { getLocalVue } from "tests/jest/helpers";
+import { mockFetcher } from "@/api/schema/__mocks__";
+
import invocationData from "../Workflow/test/json/invocation.json";
import WorkflowInvocationSummary from "./WorkflowInvocationSummary";
const localVue = getLocalVue();
+jest.mock("@/api/schema");
+mockFetcher.path("/api/invocations/{invocation_id}/energy_usage").method("get").mock({
+ total_energy_needed_cpu_kwh: 0,
+ total_energy_needed_memory_kwh: 0,
+ total_energy_needed_kwh: 0,
+});
+
+const pinia = createTestingPinia();
+
describe("WorkflowInvocationSummary.vue with terminal invocation", () => {
let wrapper;
let propsData;
@@ -18,6 +30,7 @@ describe("WorkflowInvocationSummary.vue with terminal invocation", () => {
};
wrapper = shallowMount(WorkflowInvocationSummary, {
propsData,
+ pinia,
localVue,
});
});
@@ -45,6 +58,7 @@ describe("WorkflowInvocationSummary.vue with invocation scheduling running", ()
};
wrapper = shallowMount(WorkflowInvocationSummary, {
store,
+ pinia,
propsData,
localVue,
});
diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue
index df0d600d9797..aff9c1af1e4c 100644
--- a/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue
+++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue
@@ -69,23 +69,88 @@
:error-count="errorCount"
:loading="!invocationAndJobTerminal"
class="jobs-progress" />
+
+
+
Carbon Emissions:
+
+
+
+
+ Here is an estimated summary of the total carbon footprint of this workflow invocation.
+
+
+
+ Click here to learn more about how we calculate your carbon emissions data.
+
+
+
+
+
+
+
+
+
+ 1. Based off of the global carbon intensity value of
+ {{ worldwideCarbonIntensity }}.
+
+
+ 1. based off of this galaxy instance's configured location of
+ {{ geographicalServerLocationName }}, which has a carbon intensity value of {{ carbonIntensity }} gCO2/kWh.
+
+
+
+
+
+ 2. Using the global default power usage effectiveness value of
+ {{ worldwidePowerUsageEffectiveness }}.
+
+
+
+
+