Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HOSTSD-237 Export to Excel #119

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 63 additions & 4 deletions src/api/Areas/Dashboard/Controllers/ServerItemController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,6 @@ public IActionResult FindHistory()
}
}

// TODO: Complete functionality
// TODO: Limit based on role and tenant.
/// <summary>
/// Export the server items to Excel.
/// </summary>
Expand All @@ -228,11 +226,72 @@ public IActionResult FindHistory()
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[SwaggerOperation(Tags = new[] { "Server Item" })]
public IActionResult Export(string format, string name = "service-now")
public IActionResult Export(string format = "excel", string name = "service-now")
{
var uri = new Uri(this.Request.GetDisplayUrl());
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
var filter = new HSB.Models.Filters.ServerItemFilter(query);

if (format == "excel")
{
var items = _serverItemService.Find(a => true);
IEnumerable<Entities.ServerItem> items;
var isHSB = this.User.HasClientRole(ClientRole.HSB);
if (isHSB)
{
items = _serverItemService.Find(filter, true);
}
else
{
var user = _authorization.GetUser();
if (user == null) return Forbid();
items = _serverItemService.FindForUser(user.Id, filter, true);
}

var workbook = _exporter.GenerateExcel(name, items);

using var stream = new MemoryStream();
workbook.Write(stream);
var bytes = stream.ToArray();

return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}

throw new NotImplementedException("Format 'csv' not implemented yet");
}

/// <summary>
/// Export the server history items to Excel.
/// </summary>
/// <param name="format"></param>
/// <param name="name"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
[HttpGet("history/export")]
[Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[SwaggerOperation(Tags = new[] { "Server Item" })]
public IActionResult ExportHistory(string format = "excel", string name = "service-now")
{
var uri = new Uri(this.Request.GetDisplayUrl());
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
var filter = new HSB.Models.Filters.ServerHistoryItemFilter(query);

if (format == "excel")
{
IEnumerable<Entities.ServerHistoryItem> items;
var isHSB = this.User.HasClientRole(ClientRole.HSB);
if (isHSB)
{
items = _serverHistoryItemService.FindHistoryByMonth(filter.StartDate ?? DateTime.UtcNow, filter.EndDate, filter.TenantId, filter.OrganizationId, filter.OperatingSystemItemId, filter.ServiceNowKey, true);
}
else
{
var user = _authorization.GetUser();
if (user == null) return Forbid();
items = _serverHistoryItemService.FindHistoryByMonthForUser(user.Id, filter.StartDate ?? DateTime.UtcNow, filter.EndDate, filter.TenantId, filter.OrganizationId, filter.OperatingSystemItemId, filter.ServiceNowKey, true);
}

var workbook = _exporter.GenerateExcel(name, items);

using var stream = new MemoryStream();
Expand Down
8 changes: 8 additions & 0 deletions src/api/Helpers/IXlsExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,13 @@ public interface IXlsExporter
/// <param name="items"></param>
/// <returns></returns>
XSSFWorkbook GenerateExcel(string sheetName, IEnumerable<Entities.ServerItem> items);

/// <summary>
///
/// </summary>
/// <param name="sheetName"></param>
/// <param name="items"></param>
/// <returns></returns>
XSSFWorkbook GenerateExcel(string sheetName, IEnumerable<Entities.ServerHistoryItem> items);
#endregion
}
109 changes: 109 additions & 0 deletions src/api/Helpers/XlsExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,118 @@ public XSSFWorkbook GenerateExcel(string sheetName, IEnumerable<Entities.ServerI

var workbook = new XSSFWorkbook();
var sheet = (XSSFSheet)workbook.CreateSheet(sheetName);
var rowIndex = 0;

// Header
AddHeaders(sheet.CreateRow(rowIndex++), new[] { "Tenant", "Org Code", "Organization", "ServiceNowKey", "Name", "OS", "Capacity (bytes)", "Available Space (bytes)" });

// Content
foreach (var item in items)
{
AddContent(workbook, sheet.CreateRow(rowIndex++), item);
}

return workbook;
}

/// <summary>
///
/// </summary>
/// <param name="sheetName"></param>
/// <param name="items"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public XSSFWorkbook GenerateExcel(string sheetName, IEnumerable<Entities.ServerHistoryItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));

var workbook = new XSSFWorkbook();
var sheet = (XSSFSheet)workbook.CreateSheet(sheetName);
var rowIndex = 0;

// Header
AddHeaders(sheet.CreateRow(rowIndex++), new[] { "Date", "Tenant", "Org Code", "Organization", "ServiceNowKey", "Name", "OS", "Capacity (bytes)", "Available Space (bytes)" });

// Content
foreach (var item in items)
{
AddContent(workbook, sheet.CreateRow(rowIndex++), item);
}

return workbook;
}

private static void AddHeaders(IRow row, string[] labels)
{
for (var i = 0; i < labels.Length; i++)
{
var cell = row.CreateCell(i);
cell.SetCellValue(labels[i]);
}
}

private static void AddContent(IWorkbook workbook, IRow row, Entities.ServerItem item)
{
var numberStyle = workbook.CreateCellStyle();
var numberFormat = workbook.CreateDataFormat().GetFormat("#,#0");
numberStyle.DataFormat = numberFormat;

var cell0 = row.CreateCell(0);
cell0.SetCellValue(item.Tenant?.Code);
var cell1 = row.CreateCell(1);
cell1.SetCellValue(item.Organization?.Code);
var cell2 = row.CreateCell(2);
cell2.SetCellValue(item.Organization?.Name);
var cell3 = row.CreateCell(3);
cell3.SetCellValue(item.ServiceNowKey);
var cell4 = row.CreateCell(4);
cell4.SetCellValue(item.Name);
var cell5 = row.CreateCell(5);
cell5.SetCellValue(item.OperatingSystemItem?.Name);
var cell6 = row.CreateCell(6);
cell6.SetCellType(CellType.Numeric);
cell6.CellStyle = numberStyle;
cell6.SetCellValue(item.Capacity ?? 0);
var cell7 = row.CreateCell(7);
cell7.SetCellType(CellType.Numeric);
cell7.CellStyle = numberStyle;
cell7.SetCellValue(item.AvailableSpace ?? 0);
}

private static void AddContent(IWorkbook workbook, IRow row, Entities.ServerHistoryItem item)
{
var numberStyle = workbook.CreateCellStyle();
var numberFormat = workbook.CreateDataFormat().GetFormat("#,#0");
numberStyle.DataFormat = numberFormat;

var dateStyle = workbook.CreateCellStyle();
var dateFormat = workbook.CreateDataFormat().GetFormat("yyyy/MM/dd");
dateStyle.DataFormat = dateFormat;

var cell0 = row.CreateCell(0);
cell0.SetCellValue(item.CreatedOn.Date);
cell0.CellStyle = dateStyle;
var cell1 = row.CreateCell(1);
cell1.SetCellValue(item.Tenant?.Code);
var cell2 = row.CreateCell(2);
cell2.SetCellValue(item.Organization?.Code);
var cell3 = row.CreateCell(3);
cell3.SetCellValue(item.Organization?.Name);
var cell4 = row.CreateCell(4);
cell4.SetCellValue(item.ServiceNowKey);
var cell5 = row.CreateCell(5);
cell5.SetCellValue(item.Name);
var cell6 = row.CreateCell(6);
cell6.SetCellValue(item.OperatingSystemItem?.Name);
var cell7 = row.CreateCell(7);
cell7.SetCellType(CellType.Numeric);
cell7.CellStyle = numberStyle;
cell7.SetCellValue(item.Capacity ?? 0);
var cell8 = row.CreateCell(8);
cell8.SetCellType(CellType.Numeric);
cell8.CellStyle = numberStyle;
cell8.SetCellValue(item.AvailableSpace ?? 0);
}
#endregion

#region Helpers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { dispatch } from '@/app/api/utils';

export async function GET(req: Request, context: { params: any }) {
const url = new URL(req.url);
return await dispatch(`/v1/dashboard/server-items/history/export${url.search}`);
}
16 changes: 15 additions & 1 deletion src/dashboard/src/app/client/servers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { AllocationTable, useDashboardFilter } from '@/components';
import { LoadingAnimation } from '@/components/loadingAnimation';
import { useAuth } from '@/hooks';
import { useApiServerItems, useAuth } from '@/hooks';
import {
useOperatingSystemItems,
useOrganizations,
Expand All @@ -11,6 +11,7 @@ import {
} from '@/hooks/lists';
import { useFilteredStore } from '@/store';
import { redirect, useRouter } from 'next/navigation';
import { toast } from 'react-toastify';

export default function Page() {
const router = useRouter();
Expand All @@ -27,6 +28,7 @@ export default function Page() {
const setFilteredValues = useFilteredStore((state) => state.setValues);
const setFilteredOrganizations = useFilteredStore((state) => state.setOrganizations);
const setFilteredServerItems = useFilteredStore((state) => state.setServerItems);
const { download } = useApiServerItems();

const updateDashboard = useDashboardFilter();

Expand Down Expand Up @@ -74,6 +76,18 @@ export default function Page() {
router.push(`/client/dashboard?serverItem=${serverItem?.serviceNowKey}`);
}
}}
showExport
onExport={async (search) => {
try {
await download({
search: search ? search : undefined,
});
} catch (ex) {
const error = ex as Error;
toast.error('Failed to download data. ' + error.message);
console.error(error);
}
}}
/>
);
}
16 changes: 15 additions & 1 deletion src/dashboard/src/app/hsb/servers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { AllocationTable, useDashboardFilter } from '@/components';
import { LoadingAnimation } from '@/components/loadingAnimation';
import { useAuth } from '@/hooks';
import { useApiServerItems, useAuth } from '@/hooks';
import {
useOperatingSystemItems,
useOrganizations,
Expand All @@ -11,6 +11,7 @@ import {
} from '@/hooks/lists';
import { useFilteredStore } from '@/store';
import { redirect, useRouter } from 'next/navigation';
import { toast } from 'react-toastify';

export default function Page() {
const router = useRouter();
Expand All @@ -27,6 +28,7 @@ export default function Page() {
const setFilteredValues = useFilteredStore((state) => state.setValues);
const setFilteredOrganizations = useFilteredStore((state) => state.setOrganizations);
const setFilteredServerItems = useFilteredStore((state) => state.setServerItems);
const { download } = useApiServerItems();

const updateDashboard = useDashboardFilter();

Expand Down Expand Up @@ -74,6 +76,18 @@ export default function Page() {
router.push(`/hsb/dashboard?serverItem=${serverItem?.serviceNowKey}`);
}
}}
showExport
onExport={async (search) => {
try {
await download({
search: search ? search : undefined,
});
} catch (ex) {
const error = ex as Error;
toast.error('Failed to download data. ' + error.message);
console.error(error);
}
}}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,23 @@ export interface IAllocationTableProps {
operatingSystemId?: number;
serverItems: IServerItemListModel[];
loading?: boolean;
onClick?: (serverItem?: IServerItemListModel) => void;
margin?: number;
showExport?: boolean;
exportDisabled?: boolean;
onExport?: (search: string) => void;
onClick?: (serverItem?: IServerItemListModel) => void;
}

export const AllocationTable = ({
osClassName,
operatingSystemId,
serverItems,
loading,
onClick,
margin,
showExport,
exportDisabled,
onExport,
onClick,
}: IAllocationTableProps) => {
const getServerItems = useAllocationByOS(osClassName, operatingSystemId);

Expand Down Expand Up @@ -191,9 +197,16 @@ export const AllocationTable = ({
))}
</div>
</div>
<Button variant="secondary" iconPath="/images/download-icon.png" disabled>
Export to Excel
</Button>
{showExport && (
<Button
variant="secondary"
iconPath="/images/download-icon.png"
disabled={exportDisabled}
onClick={() => onExport?.(filter)}
>
Export to Excel
</Button>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,28 @@ export interface IAllocationByOSProps {
serverItems: IServerItemListModel[];
operatingSystemItems: IOperatingSystemItemListModel[];
loading?: boolean;
showExport?: boolean;
exportDisabled?: boolean;
onExport?: () => void;
onClick?: (operatingSystemItem?: IOperatingSystemItemListModel) => void;
}

export const AllocationByOS = ({
serverItems,
operatingSystemItems,
loading,
showExport,
exportDisabled,
onExport,
onClick,
}: IAllocationByOSProps) => {
return (
<SmallBarChart
title="Allocation by OS"
data={{ ...defaultData, datasets: groupByOS(serverItems, operatingSystemItems) }}
exportDisabled={true}
onExport={() => {}}
showExport={showExport}
exportDisabled={exportDisabled}
onExport={onExport}
loading={loading}
>
{(data) => {
Expand Down
Loading
Loading