Build a Power BI Security Dashboard
Build a Power BI dashboard for security incident and SLA performance.
Introduction
30 Second Summary
Incident lists grow quickly during a busy month. A manager needs the urgent patterns to stand out before the next response decision.
In this project, you will build an interactive security incident operations report in Power BI Desktop from a structured Microsoft Excel workbook. You will turn a misleading SLA score into a defensible DAX metric for a browser-viewable GitHub case study.
What You'll Build
Picture selecting Phishing in your finished report and watching every headline metric and chart update to show that category's workload, severity, trend, and response performance.
By the end of this project, you'll have:
- An interactive dashboard where category or region filters immediately reshape the headline KPIs, severity mix, and incident trend.
- A defensible SLA compliance score of 75 across the 12 eligible closed incidents, with the misleading 50-point result documented as a denominator lesson.
- A browser-viewable GitHub case study where a recruiter can inspect the source workbook, exported PDF, DAX measure reference, findings, and recommendations without Power BI Desktop.
- Secret Mission: Add an exception-focused action queue that surfaces the two open critical incidents requiring immediate attention.
Are there any prerequisites?
You need a Windows computer with Microsoft Excel and Power BI Desktop installed, plus access to GitHub. Basic familiarity with tables, charts, and analytical questions is enough. No prior DAX experience is required.
Before We Start
A focused dashboard starts with a clear audience and decision. This checkpoint locks in who needs the report and what operational action it should support before any hands-on work begins.
Prepare and Load the Incident Data
Your security operations manager needs evidence they can trust when prioritizing incidents. An unstructured source can distort every later metric.
You'll build an auditable incident table in Microsoft Excel. Power Query inside Power BI Desktop then assigns the data types the report needs.
In this step, get ready to:
- Create the 18-row incident source in Microsoft Excel.
- Store the source as an Excel table named Incidents.
- Load the table through Power Query with verified data types.
Confirm the desktop workspace
The source begins as a small synthetic dataset that you can inspect row by row. First, confirm that both desktop applications are ready.
- Press the Windows key to open Windows search.
- Type Microsoft Excel into the search field.
- Press Enter to launch Microsoft Excel.
- Create a blank worksheet from the Excel start screen.
You'll see an empty worksheet with cell A1 ready for data.
- Press the Windows key again.
- Type Power BI Desktop into the search field.
- Press Enter to launch Power BI Desktop.
You'll see both applications running on the Windows taskbar. This confirms that your reporting workspace is ready.
- Switch back to Microsoft Excel from the Windows taskbar.
- Select cell A1.
- Fill the worksheet by copying the tab-separated data below into A1:
Incident_ID Opened_Date Closed_Date Severity Category Status SLA_Met Team Region
INC-001 2026-07-01 2026-07-01 Critical Malware Closed Yes SecOps East
INC-002 2026-07-02 2026-07-04 High Phishing Closed No SecOps West
INC-003 2026-07-03 2026-07-03 Medium Access Closed Yes IAM Central
INC-004 2026-07-05 Critical Malware Open SecOps East
INC-005 2026-07-06 2026-07-06 Low Policy Closed Yes GRC South
INC-006 2026-07-07 2026-07-08 High Access Closed Yes IAM West
INC-007 2026-07-09 2026-07-11 Critical Ransomware Closed No SecOps Central
INC-008 2026-07-10 2026-07-10 Medium Phishing Closed Yes SecOps East
INC-009 2026-07-12 High Phishing Open SecOps South
INC-010 2026-07-13 2026-07-13 Low Access Closed Yes IAM Central
INC-011 2026-07-15 2026-07-16 Medium Malware Closed Yes SecOps West
INC-012 2026-07-17 2026-07-20 High Ransomware Closed No SecOps East
INC-013 2026-07-20 Medium Policy Open GRC South
INC-014 2026-07-22 2026-07-22 Low Phishing Closed Yes SecOps Central
INC-015 2026-07-23 2026-07-24 High Access Closed Yes IAM East
INC-016 2026-07-25 Critical Malware Open SecOps West
INC-017 2026-07-27 High Access Open IAM South
INC-018 2026-07-29 Medium Phishing Open SecOps Central
What is this dataset?
These 18 records are synthetic security incidents. You can use them without exposing production security data.
Open incidents have blank Closed_Date cells. Their SLA_Met cells are blank too.
- Select File in Excel.
- Select Save As.
- Choose Documents as the save location.
- Create a folder named security-incident-dashboard.
- Open the security-incident-dashboard folder.
- Enter security_incidents.xlsx as the file name.
- Choose Excel Workbook under Save as type.
- Select Save.
You'll see security_incidents.xlsx in the Excel title bar. The worksheet contains a header row followed by incidents INC-001 through INC-018.
Did the data paste into one column?
- Confirm that you copied the data directly from the tab-separated block.
- Select A1 before pasting again.
- Remove the single-column copy before repeating the paste.
Still stuck? Why did my tab-separated incident data paste into one Excel column?
Structure the incident workbook
An Excel table preserves the source as a named structure. Power BI can then import the intended range without relying on loose worksheet cells.
- Select any populated cell in the incident range.
- Press Ctrl+T.
- Confirm that My table has headers is selected.
- Select OK.
You'll see filter controls in each column header. That visible change confirms Excel converted the range into a table.
- Select a cell inside the new table.
- Open Table Design in the Excel ribbon.
- Enter Incidents in Table Name.
- Press Enter to apply the name.
- Select File.
- Select Save.
Good work. Your source is now a saved table named Incidents with 18 incident records.
Is the table name missing?
- Select a cell inside the formatted table so Table Design becomes available.
- Check that Incidents appears in the Table Name field.
- Press Enter after typing the table name.
Need help? Why is my Excel table name not changing to Incidents?
✔️ Awesome, I've got everything!
Your saved workbook contains an Excel table named Incidents. It contains all 18 synthetic incident records.
ⓧ I'd like to double check the full code
Compare your Excel table with this complete source dataset.
Incident_ID Opened_Date Closed_Date Severity Category Status SLA_Met Team Region
INC-001 2026-07-01 2026-07-01 Critical Malware Closed Yes SecOps East
INC-002 2026-07-02 2026-07-04 High Phishing Closed No SecOps West
INC-003 2026-07-03 2026-07-03 Medium Access Closed Yes IAM Central
INC-004 2026-07-05 Critical Malware Open SecOps East
INC-005 2026-07-06 2026-07-06 Low Policy Closed Yes GRC South
INC-006 2026-07-07 2026-07-08 High Access Closed Yes IAM West
INC-007 2026-07-09 2026-07-11 Critical Ransomware Closed No SecOps Central
INC-008 2026-07-10 2026-07-10 Medium Phishing Closed Yes SecOps East
INC-009 2026-07-12 High Phishing Open SecOps South
INC-010 2026-07-13 2026-07-13 Low Access Closed Yes IAM Central
INC-011 2026-07-15 2026-07-16 Medium Malware Closed Yes SecOps West
INC-012 2026-07-17 2026-07-20 High Ransomware Closed No SecOps East
INC-013 2026-07-20 Medium Policy Open GRC South
INC-014 2026-07-22 2026-07-22 Low Phishing Closed Yes SecOps Central
INC-015 2026-07-23 2026-07-24 High Access Closed Yes IAM East
INC-016 2026-07-25 Critical Malware Open SecOps West
INC-017 2026-07-27 High Access Open IAM South
INC-018 2026-07-29 Medium Phishing Open SecOps Central
How to use this reference
The first line contains the nine column headers. Each remaining line represents one incident record.
- Confirm that the table name is Incidents.
- Confirm that the first incident is INC-001.
- Confirm that the final incident is INC-018.
- Confirm that open incidents have blank Closed_Date values.
- Confirm that open incidents have blank SLA_Met values.
Load the table through Power Query
Power Query records the intended data type for every column. Correct date typing makes the source ready for time-based analysis.
- Switch back to Power BI Desktop from the Windows taskbar.
- Select Home in the ribbon.
- Select Get Data.
- Select Excel as the data source.
- Browse to Documents/security-incident-dashboard.
- Select security_incidents.xlsx.
- Select Open.
The Navigator shows the named objects available in the workbook. You'll see the Incidents table from Excel.
- Select the Incidents table in Navigator.
- Select Transform Data.
Power Query Editor opens with the 18 incident records. The type icon beside each header shows how Power BI currently interprets that column.
- Select the Opened_Date column.
- Hold Ctrl while selecting the Closed_Date column.
- Choose Date from Data Type in the Transform group.
Both date columns now display the Date type icon. Power BI can treat their values as calendar dates.
- Select the Incident_ID column.
- Hold Shift while selecting the Region column.
- Hold Ctrl while selecting Opened_Date to remove it from the selection.
- Hold Ctrl while selecting Closed_Date to remove it from the selection.
- Choose Text from Data Type.
The seven remaining columns now use the Text type. This preserves identifiers such as INC-001 without treating them as numbers.
Before you apply the query, do you expect the row count to stay at 18 after changing only the data types?
- Select Close & Apply.
- Select Table view in the left navigation.
- Select Incidents in the Fields pane.
You'll see 18 records in the Incidents table. The IDs run from INC-001 through INC-018.
The Opened_Date field uses the Date type. The Closed_Date field also uses the Date type.
Each remaining field uses the Text type. Your Power BI model now matches the structured Excel source.
Seeing the wrong row count or data types?
- Confirm that you selected the named Incidents table in Navigator.
- Return to Power Query Editor through Transform Data if either date field uses another type.
- Check the Excel source for all records from INC-001 through INC-018.
Still stuck? Why is my Power BI import showing the wrong data types or fewer than 18 rows?
Your incident source is structured, saved, and loaded with the correct data types. Next, you'll turn these 18 rows into the first working dashboard view.
Build the First Incident View
Your incident table now has 18 typed rows in Power BI Desktop. That gives the report a reliable source.
A blank report gives no feedback. You need an early visual check before building more complex KPI logic.
This step turns the loaded table into an interactive incident view. You will see the totals recalculate when you filter a category.
In this step, get ready to:
- Create a reusable incident count with DAX.
- Show the severity distribution in a bar chart.
- Prove that category filtering updates the report.
Create the incident total
A DAX measure stores a calculation that can respond to the report's filter context. One reusable measure keeps every incident count consistent.
- In the Fields pane, right-click Incidents.
- Select New measure.
- Define the reusable incident count by entering this measure:
Total Incidents = COUNTROWS(Incidents)
What does this measure do?
- The COUNTROWS(Incidents) expression counts the rows available from the Incidents table.
- The measure inherits the active filter context. That behavior lets the same measure show every incident or only the selected category.
- Commit the measure by pressing Enter.
- Select a blank area of the report canvas.
- In the Visualizations pane, select Card.
- Drag Total Incidents from the Fields pane onto the card.
Good work. Your first report card now shows all 18 incidents.
✔️ Awesome, I've got everything!
Great. Your Power BI Desktop model contains the full measure created in this step.
ⓧ I'd like to double check the full code
Total Incidents = COUNTROWS(Incidents)
Card Not Showing 18?
- Confirm you created the measure under Incidents from the Fields pane.
- Confirm the card contains Total Incidents as its only field.
Still stuck? Get help diagnosing a blank Total Incidents card.
Chart the severity mix
A total shows the size of the workload. A severity distribution reveals how much of that workload carries higher risk.
- Select a blank area of the report canvas.
- In the Visualizations pane, select Clustered bar chart.
- Drag Severity into the category field well.
- Drag Total Incidents into the value field well.
What Should I See?
Critical shows 4. High shows 6.
Medium shows 5. Low shows 3.
Those four bars account for all 18 incidents. Your chart now turns the headline total into an operational risk mix.
Severity Counts Look Wrong?
- Confirm the chart uses Severity as its category.
- Confirm the chart uses Total Incidents as its value.
- Clear any active selections on the report page.
Need another pair of eyes? Get help checking the severity chart fields.
Filter by incident category
A slicer applies one category choice to the connected visuals. This is where filter context becomes visible.
- Select a blank area of the report canvas.
- In the Visualizations pane, select Slicer.
- Drag Category from the Fields pane onto the slicer.
Before you select Phishing, consider whether the card will stay at 18 or recalculate.
- Select Phishing in the Category slicer.
You should see Total Incidents change from 18 to 5. The severity chart redraws for Phishing incidents.
- Clear the Category slicer selection.
That interaction is working. Your card returns to 18 when the filter is cleared.
Slicer Not Updating the Visuals?
- Confirm the slicer contains the Category field.
- Clear other report selections before choosing Phishing.
- Confirm the card still contains the Total Incidents measure.
Still seeing 18 after selecting Phishing? Get help checking the slicer's visual interaction.
Your first incident view is working. Next, you'll use it to reveal how a plausible SLA score can measure the wrong population.
Expose the SLA Denominator Problem
The interactive Power BI Desktop report now turns 18 incident records into a working view. The Category slicer proves that the metrics respond to a manager's focus.
A convincing SLA percentage can still answer the wrong question when its denominator uses the wrong population. In this step, you'll create the counts that expose this risk with evidence.
In this step, get ready to:
- Count closed incidents and incidents that met SLA.
- Create a naive SLA percentage using every incident.
- Compare the eligible count with the naive score to reveal the denominator problem.
Count eligible and successful incidents
An SLA ratio needs a numerator for successful outcomes. It also needs a denominator for eligible incidents.
How will DAX create these counts?
A DAX measure calculates a result from the current report filters. The CALCULATE function changes the filter context so each count includes only the records that match its condition.
- Right-click the Incidents table in the Fields pane.
- Select New measure.
- Create the closed-incident count by pasting this definition:
Closed Incidents =
CALCULATE(
[Total Incidents],
Incidents[Status] = "Closed"
)
What Does This Measure Count?
- The Closed Incidents measure starts with the existing Total Incidents count.
- The status condition keeps records where Incidents[Status] equals Closed.
- Press Enter to validate the measure.
- Confirm that Closed Incidents appears under the Incidents table in the Fields pane.
Closed Incidents Not Appearing?
- Check that the table name is exactly Incidents.
- Check that the status value is exactly Closed.
- Press Enter after pasting the definition.
Still stuck? Help me diagnose why my Closed Incidents measure is not appearing in Power BI Desktop.
- Right-click the Incidents table again.
- Select New measure.
- Create the successful-incident count by pasting this definition:
SLA Met Incidents =
CALCULATE(
[Total Incidents],
Incidents[SLA_Met] = "Yes"
)
How Does This Measure Isolate Success?
- The SLA Met Incidents measure counts records that have Yes in Incidents[SLA_Met].
- Open incidents have blank SLA values. Those records do not contribute to this success count.
- Press Enter to validate the measure.
- Confirm that SLA Met Incidents appears under the Incidents table.
SLA Met Incidents Not Appearing?
- Check that the column name is exactly SLA_Met.
- Check that the successful value is exactly Yes.
- Confirm that every opening parenthesis has a closing parenthesis.
Still stuck? Help me find the problem in my SLA Met Incidents measure.
Build the naive SLA score
A tempting shortcut divides successful incidents by every incident in the dataset. You will run that shortcut exactly as written before judging whether it represents SLA performance fairly.
- Right-click the Incidents table.
- Select New measure.
- Create the naive SLA percentage by pasting this definition:
SLA Compliance Naive =
DIVIDE([SLA Met Incidents], [Total Incidents], 0) * 100
What Does the Naive Score Calculate?
- The DIVIDE function uses SLA Met Incidents as the numerator.
- The measure uses Total Incidents as the denominator.
- The fallback value 0 handles an empty denominator.
- The multiplication converts the result into a percentage value.
- Press Enter to validate the measure.
- Confirm that SLA Compliance Naive appears under the Incidents table.
Naive Measure Not Validating?
- Confirm that Closed Incidents appears in the model before checking this formula.
- Confirm that SLA Met Incidents appears in the model before checking this formula.
- Check that both measure names remain inside square brackets.
Still stuck? Help me diagnose why my SLA Compliance Naive measure is not validating.
The measures.txt reference keeps your model logic readable outside Power BI Desktop. Update it now so the file matches the measures in your report.
- Enter Documents/security-incident-dashboard in the File Explorer address bar.
You'll see the project files from the earlier data preparation work.
- Open measures.txt.
- Replace its contents with this cumulative measure reference:
Total Incidents = COUNTROWS(Incidents)
Closed Incidents =
CALCULATE(
[Total Incidents],
Incidents[Status] = "Closed"
)
SLA Met Incidents =
CALCULATE(
[Total Incidents],
Incidents[SLA_Met] = "Yes"
)
SLA Compliance Naive =
DIVIDE([SLA Met Incidents], [Total Incidents], 0) * 100
What Does the Reference File Capture?
The file records the four measures currently present in the model. This gives reviewers a readable copy of the analytical logic without requiring the report file.
- Save measures.txt.
- Confirm that the file contains Total Incidents, Closed Incidents, SLA Met Incidents, and SLA Compliance Naive.
Measure Reference Missing a Definition?
- Check that you replaced the previous contents of measures.txt.
- Confirm that each measure begins on its own line.
- Save the file after adding all four definitions.
Still stuck? Help me compare my measures.txt file with the measures in Power BI Desktop.
✔️ Awesome, I've got everything!
Your Power BI model now contains all four measures. Your saved measures.txt file contains the same definitions.
ⓧ I'd like to double check the full code
- Compare your saved measures.txt file with this complete reference:
Total Incidents = COUNTROWS(Incidents)
Closed Incidents =
CALCULATE(
[Total Incidents],
Incidents[Status] = "Closed"
)
SLA Met Incidents =
CALCULATE(
[Total Incidents],
Incidents[SLA_Met] = "Yes"
)
SLA Compliance Naive =
DIVIDE([SLA Met Incidents], [Total Incidents], 0) * 100
Reveal the denominator problem
The measures become meaningful when you place them beside the existing total. Clear the report filters first so every card evaluates the same full dataset.
- Clear any selection in the Category slicer with its clear control.
The existing Total Incidents card returns to 18.
- Select an empty area of the report canvas.
- Select the Card visual in the Visualizations pane.
- Add Closed Incidents to the new card.
You'll see 12 on the Closed Incidents card. This is the population with a completed outcome.
Before you add the score card, what result do you expect when nine SLA successes are divided by all 18 incidents?
- Select another empty area of the report canvas.
- Select the Card visual in the Visualizations pane.
- Add SLA Compliance Naive to the new card.
You'll see 50 on the naive SLA card. That result is the intended shortfall.
Why Is 50 Misleading?
The numerator contains nine incidents that met SLA. The denominator contains all 18 incidents.
Only 12 incidents are closed. The six open incidents remain in the denominator even though they have no completed SLA outcome.
Those unresolved incidents depress the score as if they were SLA failures. The card looks plausible while measuring the wrong eligible population.
Cards Not Showing 12 and 50?
- Clear every selection in the Category slicer.
- Confirm that the first new card uses Closed Incidents.
- Confirm that the second new card uses SLA Compliance Naive.
Still stuck? Help me check why my Power BI cards do not show 12 closed incidents and a naive SLA score of 50.
You found the KPI trap. Your dashboard now proves that unresolved incidents can distort a plausible-looking percentage.
Next, you'll rebuild the metric around its eligible population. You'll also complete the operational view with response-time and trend context.
Correct the KPI and Complete the Dashboard
Your earlier test in Power BI Desktop exposed a believable score with a broken denominator. The naive card shows 50 because all 18 incidents sit in its eligible population.
That 50-point score treats unresolved work as SLA failure. A manager could act on the wrong signal.
You'll repair the denominator with a new DAX measure. Then you'll add response-time context before completing the report page.
In this step, get ready to:
- Calculate SLA compliance using only closed incidents.
- Measure the average time required to resolve completed incidents.
- Complete the report with a date trend plus regional filtering.
Correct the SLA denominator
An SLA percentage needs a denominator made from incidents that can be assessed. The existing Closed Incidents measure already defines that eligible population.
- Switch back to Power BI Desktop.
- In the Fields pane, right-click the Incidents table.
- Select New measure.
- Create the corrected SLA measure by entering this expression:
SLA Compliance Corrected =
DIVIDE([SLA Met Incidents], [Closed Incidents], 0) * 100
What does this measure do?
- The DIVIDE function compares successful incidents with the closed incident population.
- The Closed Incidents denominator excludes open records from the calculation.
- The * 100 operation converts the result into a percentage score.
- Press Enter to commit the measure.
You'll see SLA Compliance Corrected listed under the Incidents table.
- Select the card that currently shows 50.
- Replace SLA Compliance Naive in the card's value field with SLA Compliance Corrected.
You've fixed the core KPI. With the slicers cleared, the card now shows 75.
Still seeing 50?
- Confirm the card uses SLA Compliance Corrected instead of SLA Compliance Naive.
- Confirm the corrected measure uses [Closed Incidents] as its denominator.
- Clear the current selection from the Category slicer.
Still stuck? Help me check why my corrected SLA card is not showing 75.
Add response-time context
Response time needs its own eligible population. Open incidents have no Closed_Date value.
The next measure filters to completed work before averaging elapsed hours. This gives the manager a comparable view of resolution speed.
- In the Fields pane, right-click the Incidents table.
- Select New measure.
- Create the resolution-time measure by entering this expression:
Average Resolution Hours =
AVERAGEX(
FILTER(Incidents, Incidents[Status] = "Closed"),
DATEDIFF(Incidents[Opened_Date], Incidents[Closed_Date], HOUR)
)
How is resolution time calculated?
- The FILTER function keeps incidents whose status is Closed.
- The DATEDIFF function measures the elapsed time between Opened_Date and Closed_Date in hours.
- The AVERAGEX function averages those durations within the current filter context.
- Press Enter to commit the measure.
You'll see Average Resolution Hours listed under the Incidents table.
- Select the card that currently shows 12.
- Replace Closed Incidents in the card's value field with Average Resolution Hours.
The card should show 20. Your headline row now includes response speed.
Resolution card not showing 20?
- Confirm Opened_Date still uses the Date data type.
- Confirm Closed_Date still uses the Date data type.
- Check that the filter compares Incidents[Status] with Closed.
Still stuck? Help me find why my Average Resolution Hours card is not showing 20.
✔️ Awesome, I've got everything!
Great work. Both new measures are saved in Power BI Desktop.
ⓧ I'd like to double check the full code
Total Incidents = COUNTROWS(Incidents)
Closed Incidents =
CALCULATE(
[Total Incidents],
Incidents[Status] = "Closed"
)
SLA Met Incidents =
CALCULATE(
[Total Incidents],
Incidents[SLA_Met] = "Yes"
)
SLA Compliance Naive =
DIVIDE([SLA Met Incidents], [Total Incidents], 0) * 100
SLA Compliance Corrected =
DIVIDE([SLA Met Incidents], [Closed Incidents], 0) * 100
Average Resolution Hours =
AVERAGEX(
FILTER(Incidents, Incidents[Status] = "Closed"),
DATEDIFF(Incidents[Opened_Date], Incidents[Closed_Date], HOUR)
)
Complete the report page
The headline cards explain workload plus performance. A date trend reveals when that workload arrived.
A region filter lets a manager focus the whole page on one operating area. Together, these visuals turn the corrected measures into an interactive report.
- Select a blank area on the report page.
- Choose Line chart in the Visualizations pane.
You'll see an empty line-chart frame on the report page.
- Drag Opened_Date from the Fields pane to the chart's date axis.
- Drag Total Incidents to the chart's value area.
You'll see incident volume plotted across the July 2026 dates.
Line chart looks incomplete?
- Confirm the chart uses Opened_Date for its date axis.
- Confirm the chart uses Total Incidents for its values.
- Clear the Category slicer if the trend shows only one incident category.
Still stuck? Help me check why my incident trend is missing dates or values.
- Select another blank area on the report page.
- Choose Slicer in the Visualizations pane.
You'll see an empty slicer frame on the report page.
- Drag Region from the Fields pane into the slicer.
You'll see region choices for Central, East, South, and West.
- Position the three headline cards across the top of the report page.
- Place the severity chart below the headline cards.
Your workload summary now has a clear visual hierarchy.
- Place the date trend beside the severity chart.
- Place the Category slicer near the charts.
The report now connects incident mix with incident timing.
- Place the Region slicer beside the Category slicer.
- Resize the visuals so every title plus value remains readable.
Before you clear the filters, what values should the three headline cards return for the full dataset?
- Clear the current selection from the Category slicer.
- Clear the current selection from the Region slicer.
You should see 18 on the Total Incidents card. The SLA Compliance Corrected card should show 75.
The Average Resolution Hours card should show 20. That's the dashboard complete.
Your dashboard now tells a defensible operational story. Next up, you'll package that story into a browser-viewable case study.
Publish the Portfolio Case Study
Your corrected dashboard now gives a security operations manager defensible KPIs. It still lives inside Power BI Desktop, which limits who can review it.
This step packages the report as a PBIX file plus a browser-viewable PDF. You will publish the source workbook with its DAX reference in a public GitHub case study.
In this step, get ready to:
- Save the completed report as a PBIX file.
- Export the completed report as a browser-viewable PDF.
- Publish a public case study containing the source workbook plus the dashboard documentation.
Save the report and export the PDF
The PBIX file preserves your interactive report for future editing. The PDF gives reviewers a copy they can open without Power BI Desktop.
- In Power BI Desktop from earlier, select File.
- Select Save.
You will see a save dialog where you can name the report and choose its location.
- Choose the Documents/security-incident-dashboard folder.
- Enter security-incident-operations-dashboard.pbix as the file name.
The save dialog should now point to the same folder as your source workbook.
- Select Save.
The Power BI Desktop title bar should now show security-incident-operations-dashboard.pbix.
- Select File.
- Select Export.
The export menu includes the PDF option for the visible report page.
- Select Export to PDF.
Power BI Desktop prepares the visible report page and opens a save dialog.
- Choose the Documents/security-incident-dashboard folder.
- Enter security-incident-operations-dashboard.pdf as the file name.
The PDF path should match the folder containing your PBIX file plus security_incidents.xlsx.
- Select Save.
Your default PDF viewer opens the exported dashboard. You should see the headline cards plus the severity chart, trend chart, Category slicer, and Region slicer.
You now have an editable PBIX plus a browser-viewable PDF. That pairing makes the report useful to builders as well as reviewers.
PDF missing from the project folder?
- Check the save location for security-incident-operations-dashboard.pdf.
- Repeat the export if the PDF was saved outside Documents/security-incident-dashboard.
Still stuck? Help me find a PDF exported from Power BI Desktop on Windows.
Create the public GitHub repository
A public repository gives the case study one browser-accessible home. Reviewers can inspect your evidence without downloading the editable PBIX file.
Why publish the case study on GitHub?
The repository combines your findings with the files that support them. The PDF shows the finished report while the workbook makes the synthetic source auditable.
Keeping the PBIX local protects your editable working copy. The public case study stays focused on files a browser reviewer can use.
- Visit GitHub in your browser.
- Sign in to your account.
You should see your signed-in GitHub home page.
- Select New repository from the create menu.
- Choose your personal account from the Owner list.
The repository form should now show your account as its owner.
- Enter security-incident-operations-dashboard in the Repository name field.
- Select Public as the visibility.
The repository name is ready to become the public address for your case study.
- Select Add README.
- Select Create repository.
Good progress. Your public repository now exists with README.md ready for the case-study narrative.
- Record the repository URL here: https://github.com/your-username/security-incident-operations-dashboard.
Repository creation blocked?
- Confirm that the repository owner is your signed-in GitHub account.
- Check that the repository name is exactly security-incident-operations-dashboard.
Need a hand? Help me create the public GitHub repository for this dashboard.
Build and verify the case study
A portfolio case study needs to explain the decision behind the dashboard. It also needs to show how the evidence supports your recommendation.
What should the README cover?
- The business question asks how a security operations manager can monitor workload, risk, and response performance.
- The workflow explains how Power Query loads and types the synthetic Excel source. DAX defines the measures used by the report.
- The KPI correction explains why only closed incidents belong in the SLA denominator.
- The volume findings report 18 total incidents plus 12 closed incidents.
- The SLA finding reports 75 SLA compliance among closed incidents.
- The response finding reports 20 average resolution hours.
- The exception findings report two open critical incidents. They also report two ransomware incidents that both missed SLA.
- The recommendations prioritize the open critical incidents. They also call for a review of ransomware handling.
- Select README.md from the repository file list.
- Select the pencil icon in the upper-right corner of the file view.
You will see the README in GitHub's file editor.
- Replace the starter text with a case study that follows the blueprint above.
- Select Preview.
The preview should present the business question, workflow, corrected KPI, findings, and recommendations as a readable case study.
- Select Commit changes.
The commit dialog asks for a short message describing the README update.
- Enter a short commit message describing the case-study update.
- Select Commit changes.
Your repository home page now displays the case-study narrative from README.md.
The README explains the result. The measure reference lets another analyst inspect the exact KPI logic.
- Return to the repository file list.
- Select Add file.
The menu should show an option for creating a file in the repository.
- Select Create new file.
- Enter measures.txt in the file name field.
The editor is ready for the complete measure reference.
- Create the DAX reference by pasting the following content into measures.txt:
Total Incidents = COUNTROWS(Incidents)
Closed Incidents =
CALCULATE(
[Total Incidents],
Incidents[Status] = "Closed"
)
SLA Met Incidents =
CALCULATE(
[Total Incidents],
Incidents[SLA_Met] = "Yes"
)
SLA Compliance Naive =
DIVIDE([SLA Met Incidents], [Total Incidents], 0) * 100
SLA Compliance Corrected =
DIVIDE([SLA Met Incidents], [Closed Incidents], 0) * 100
Average Resolution Hours =
AVERAGEX(
FILTER(Incidents, Incidents[Status] = "Closed"),
DATEDIFF(Incidents[Opened_Date], Incidents[Closed_Date], HOUR)
)
What does this reference document?
- The Total Incidents, Closed Incidents, and SLA Met Incidents measures establish the populations behind the report.
- The SLA Compliance Naive measure preserves the denominator mistake you discovered. The SLA Compliance Corrected measure records the defensible replacement.
- The Average Resolution Hours measure documents how the dashboard summarizes completed incident durations.
- Select Preview.
- Select Commit changes.
The commit dialog is ready to save measures.txt.
- Enter a short commit message describing the new measure reference.
- Select Commit changes.
You should now see measures.txt in the repository file list.
✔️ Awesome, I've got everything!
Great. Confirm that measures.txt is committed and visible from the repository home page.
ⓧ I'd like to double check the full code
Compare your repository file with this complete DAX reference:
Total Incidents = COUNTROWS(Incidents)
Closed Incidents =
CALCULATE(
[Total Incidents],
Incidents[Status] = "Closed"
)
SLA Met Incidents =
CALCULATE(
[Total Incidents],
Incidents[SLA_Met] = "Yes"
)
SLA Compliance Naive =
DIVIDE([SLA Met Incidents], [Total Incidents], 0) * 100
SLA Compliance Corrected =
DIVIDE([SLA Met Incidents], [Closed Incidents], 0) * 100
Average Resolution Hours =
AVERAGEX(
FILTER(Incidents, Incidents[Status] = "Closed"),
DATEDIFF(Incidents[Opened_Date], Incidents[Closed_Date], HOUR)
)
This full file preserves every measure from the completed Power BI model.
Measure reference looks incomplete?
- Confirm that the file name is exactly measures.txt.
- Compare the final line with the complete-code tab to catch missing parentheses.
- Check that all six measure names appear in the file.
Need help comparing the files? Help me check my DAX measure reference for missing lines.
The narrative and measure logic are now online. The final upload adds the workbook plus the PDF evidence behind them.
- Return to the repository file list.
- Select Add file.
The file menu should now show the upload option.
- Select Upload files.
You will see an upload area for files from your computer.
- Drag security_incidents.xlsx plus security-incident-operations-dashboard.pdf from Documents/security-incident-dashboard into the upload area.
Both file names should appear in the upload queue.
- Enter a short commit message describing the dashboard files.
- Select Commit changes.
Your repository file list should now contain README.md, measures.txt, security_incidents.xlsx, and security-incident-operations-dashboard.pdf.
Upload not completing?
- Confirm that the PDF opens locally before uploading it again.
- Check that you selected the exported PDF instead of the local PBIX file.
- Repeat the upload with one file at a time if the browser loses the selection.
Still blocked? Help me upload the dashboard workbook and PDF to my GitHub repository.
Before you test the link, do you expect a signed-out reviewer to see every portfolio file?
- Open a private browser window.
- Paste https://github.com/your-username/security-incident-operations-dashboard into the address bar.
- Press Enter.
You should see the public README plus measures.txt, security_incidents.xlsx, and security-incident-operations-dashboard.pdf without signing in.
- Select security-incident-operations-dashboard.pdf from the public file list.
You should see the completed one-page dashboard in the browser. This confirms that a reviewer can inspect the finished report without Power BI Desktop.
That completes the publishing loop. Your analysis now has a public narrative plus reproducible evidence.
Secret mission
Secret Mission: Add a Critical Incident Action Queue
Headline metrics show the scale of an incident workload. This mission adds an exception-focused queue that surfaces open critical incidents, identifies the responsible team, and turns the dashboard into an immediate action tool.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep your dashboard available, pause your work, or delete the project entirely. The local files and public GitHub repository cost $0 additional to keep.
Resources you used:
- Local folder Documents/security-incident-dashboard, containing security_incidents.xlsx, security-incident-operations-dashboard.pbix, and security-incident-operations-dashboard.pdf.
- Public GitHub repository security-incident-operations-dashboard, containing README.md, measures.txt, security_incidents.xlsx, and security-incident-operations-dashboard.pdf.
Keep everything running
No cleanup is needed. Choose this option if you want the dashboard and public case study ready for review or further refinement.
- Leave the local Documents/security-incident-dashboard folder in place.
- Leave the public GitHub repository security-incident-operations-dashboard available.
Your interactive report remains editable locally. Recruiters can continue viewing the PDF and case-study files in a browser.
Pause - I'll come back to this later
Shut down the active desktop session while preserving every project artifact. Your local files and public repository remain ready for your return.
- Close Microsoft Excel from earlier.
- Close Power BI Desktop from earlier.
The desktop apps stop using system resources. Your report and public case study stay unchanged.
Delete - I don't want to use this again
Deleting the local folder and public repository is permanent. Choose this option only if you no longer need the editable report or browser-viewable case study.
Delete the local project files:
- Close Microsoft Excel from earlier.
- Close Power BI Desktop from earlier.
- Go to Documents in Windows File Explorer.
- Select the security-incident-dashboard folder.
- Delete the selected folder.
Delete the public GitHub repository:
- Return to the public GitHub repository security-incident-operations-dashboard from earlier.
- Select Settings.
- Scroll to Danger Zone.
- Select Delete this repository.
- Select I want to delete this repository.
- Acknowledge the repository deletion warnings.
- Enter security-incident-operations-dashboard in the confirmation field.
- Select Delete this repository to confirm the deletion.
The local workbook, editable report, exported PDF, DAX reference, and public case study are now removed.
Nice Work!
Nice Work!
You did it! You've built an interactive security incident operations dashboard in Power BI Desktop.
Your report now gives a security operations manager an evidence-based view of incident performance.
You've learned how to:
- Prepared an 18-row incident source with Power Query. Built an interactive report whose category and region filters update every incident KPI.
- Used DAX to replace a misleading all-incident denominator with a closed-incident denominator. This corrected SLA compliance from 50 to 75.
- Published a browser-viewable case study on GitHub. Recruiters can review your dashboard findings without Power BI Desktop.
- Completed the optional Secret Mission by adding an open critical incident queue that surfaces INC-004 and INC-016. The queue supports your recommendation that SecOps act first.
Ready to quiz yourself?