mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-12 16:12:44 +00:00
add tests to dashboard
This commit is contained in:
@@ -250,11 +250,13 @@ func (c *Campaign) GetStats(g *gin.Context) {
|
||||
}
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
includeTestCampaigns := g.Query("includeTest") == "true"
|
||||
// get
|
||||
stats, err := c.CampaignService.GetStats(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
companyID,
|
||||
includeTestCampaigns,
|
||||
)
|
||||
// handle responses
|
||||
if ok := c.handleErrors(g, err); !ok {
|
||||
@@ -303,6 +305,7 @@ func (c *Campaign) GetAllWithinDates(g *gin.Context) {
|
||||
}
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
includeTestCampaigns := g.Query("includeTest") == "true"
|
||||
queryArgs, ok := c.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
@@ -330,6 +333,7 @@ func (c *Campaign) GetAllWithinDates(g *gin.Context) {
|
||||
&repository.CampaignOption{
|
||||
QueryArgs: queryArgs,
|
||||
WithCampaignTemplate: true,
|
||||
IncludeTestCampaigns: includeTestCampaigns,
|
||||
},
|
||||
)
|
||||
// handle responses
|
||||
@@ -349,6 +353,7 @@ func (c *Campaign) GetAllActive(g *gin.Context) {
|
||||
}
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
includeTestCampaigns := g.Query("includeTest") == "true"
|
||||
queryArgs, ok := c.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
@@ -367,6 +372,7 @@ func (c *Campaign) GetAllActive(g *gin.Context) {
|
||||
QueryArgs: queryArgs,
|
||||
WithCompany: true,
|
||||
WithCampaignTemplate: true,
|
||||
IncludeTestCampaigns: includeTestCampaigns,
|
||||
},
|
||||
)
|
||||
// handle responses
|
||||
@@ -385,6 +391,7 @@ func (c *Campaign) GetAllUpcoming(g *gin.Context) {
|
||||
}
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
includeTestCampaigns := g.Query("includeTest") == "true"
|
||||
queryArgs, ok := c.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
@@ -403,6 +410,7 @@ func (c *Campaign) GetAllUpcoming(g *gin.Context) {
|
||||
QueryArgs: queryArgs,
|
||||
WithCompany: true,
|
||||
WithCampaignTemplate: true,
|
||||
IncludeTestCampaigns: includeTestCampaigns,
|
||||
},
|
||||
)
|
||||
// handle responses
|
||||
@@ -421,6 +429,7 @@ func (c *Campaign) GetAllFinished(g *gin.Context) {
|
||||
}
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
includeTestCampaigns := g.Query("includeTest") == "true"
|
||||
queryArgs, ok := c.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
@@ -439,6 +448,7 @@ func (c *Campaign) GetAllFinished(g *gin.Context) {
|
||||
QueryArgs: queryArgs,
|
||||
WithCompany: true,
|
||||
WithCampaignTemplate: true,
|
||||
IncludeTestCampaigns: includeTestCampaigns,
|
||||
},
|
||||
)
|
||||
// handle responses
|
||||
|
||||
@@ -59,6 +59,7 @@ type CampaignOption struct {
|
||||
WithRecipientGroupCount bool
|
||||
WithAllowDeny bool
|
||||
WithDenyPage bool
|
||||
IncludeTestCampaigns bool
|
||||
}
|
||||
|
||||
// CampaignEventOption is options for preloading
|
||||
@@ -74,6 +75,14 @@ type Campaign struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// applyTestCampaignFilter conditionally applies the is_test filter based on options
|
||||
func (r *Campaign) applyTestCampaignFilter(db *gorm.DB, options *CampaignOption) *gorm.DB {
|
||||
if !options.IncludeTestCampaigns {
|
||||
db = db.Where("is_test = false")
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// load preloads the campaign repository
|
||||
func (r *Campaign) load(db *gorm.DB, options *CampaignOption) *gorm.DB {
|
||||
if options.WithCompany {
|
||||
@@ -435,8 +444,8 @@ func (r *Campaign) GetAllActive(
|
||||
if err != nil {
|
||||
return result, errs.Wrap(err)
|
||||
}
|
||||
// Filter out test campaigns
|
||||
db = db.Where("is_test = false")
|
||||
// Apply test campaign filter based on options
|
||||
db = r.applyTestCampaignFilter(db, options)
|
||||
|
||||
var dbCampaigns []database.Campaign
|
||||
res := db.
|
||||
@@ -486,8 +495,8 @@ func (r *Campaign) GetAllUpcoming(
|
||||
if err != nil {
|
||||
return result, errs.Wrap(err)
|
||||
}
|
||||
// Filter out test campaigns
|
||||
db = db.Where("is_test = false")
|
||||
// Apply test campaign filter based on options
|
||||
db = r.applyTestCampaignFilter(db, options)
|
||||
|
||||
var dbCampaigns []database.Campaign
|
||||
res := db.
|
||||
@@ -537,8 +546,8 @@ func (r *Campaign) GetAllFinished(
|
||||
if err != nil {
|
||||
return result, errs.Wrap(err)
|
||||
}
|
||||
// Filter out test campaigns
|
||||
db = db.Where("is_test = false")
|
||||
// Apply test campaign filter based on options
|
||||
db = r.applyTestCampaignFilter(db, options)
|
||||
|
||||
var dbCampaigns []database.Campaign
|
||||
res := db.
|
||||
@@ -860,8 +869,8 @@ func (r *Campaign) GetAllCampaignWithinDates(
|
||||
|
||||
var dbCampaigns []database.Campaign
|
||||
|
||||
// Filter out test campaigns
|
||||
db = db.Where("is_test = false")
|
||||
// Apply test campaign filter based on options
|
||||
db = r.applyTestCampaignFilter(db, options)
|
||||
|
||||
// Query campaigns that:
|
||||
// 1. Are self-managed (no send_start_at)
|
||||
@@ -1304,18 +1313,21 @@ func (r *Campaign) AnonymizeCampaignEventsByRecipientID(
|
||||
|
||||
// GetActiveCount get the number running campaigns
|
||||
// if no company ID is selected it gets the global count including all companies
|
||||
func (r *Campaign) GetActiveCount(ctx context.Context, companyID *uuid.UUID) (int64, error) {
|
||||
func (r *Campaign) GetActiveCount(ctx context.Context, companyID *uuid.UUID, includeTestCampaigns bool) (int64, error) {
|
||||
var c int64
|
||||
db := r.DB
|
||||
if companyID != nil {
|
||||
db = whereCompany(db, database.CAMPAIGN_TABLE, companyID)
|
||||
}
|
||||
|
||||
whereClause := "((send_start_at <= ? OR send_start_at IS NULL) AND closed_at IS NULL)"
|
||||
if !includeTestCampaigns {
|
||||
whereClause += " AND is_test = false"
|
||||
}
|
||||
|
||||
res := db.
|
||||
Model(&database.Campaign{}).
|
||||
Where(
|
||||
"((send_start_at <= ? OR send_start_at IS NULL) AND closed_at IS NULL AND is_test IS false)",
|
||||
utils.NowRFC3339UTC(),
|
||||
).
|
||||
Where(whereClause, utils.NowRFC3339UTC()).
|
||||
Count(&c)
|
||||
|
||||
return c, res.Error
|
||||
@@ -1323,17 +1335,21 @@ func (r *Campaign) GetActiveCount(ctx context.Context, companyID *uuid.UUID) (in
|
||||
|
||||
// GetUpcomingCount get the upcoming campaign count
|
||||
// if no company ID is selected it gets the global count including all companies
|
||||
func (r *Campaign) GetUpcomingCount(ctx context.Context, companyID *uuid.UUID) (int64, error) {
|
||||
func (r *Campaign) GetUpcomingCount(ctx context.Context, companyID *uuid.UUID, includeTestCampaigns bool) (int64, error) {
|
||||
var c int64
|
||||
db := r.DB
|
||||
if companyID != nil {
|
||||
db = whereCompany(db, database.CAMPAIGN_TABLE, companyID)
|
||||
}
|
||||
|
||||
whereClause := "((send_start_at > ?) AND closed_at IS NULL)"
|
||||
if !includeTestCampaigns {
|
||||
whereClause += " AND is_test = false"
|
||||
}
|
||||
|
||||
res := db.
|
||||
Model(&database.Campaign{}).
|
||||
Where(
|
||||
"((send_start_at > ?) AND closed_at IS NULL AND is_test IS false)", utils.NowRFC3339UTC(),
|
||||
).
|
||||
Where(whereClause, utils.NowRFC3339UTC()).
|
||||
Count(&c)
|
||||
|
||||
return c, res.Error
|
||||
@@ -1341,15 +1357,21 @@ func (r *Campaign) GetUpcomingCount(ctx context.Context, companyID *uuid.UUID) (
|
||||
|
||||
// GetFinishedCount get the finished campaign count
|
||||
// if no company ID is selected it gets the global count including all companies
|
||||
func (r *Campaign) GetFinishedCount(ctx context.Context, companyID *uuid.UUID) (int64, error) {
|
||||
func (r *Campaign) GetFinishedCount(ctx context.Context, companyID *uuid.UUID, includeTestCampaigns bool) (int64, error) {
|
||||
var c int64
|
||||
db := r.DB
|
||||
if companyID != nil {
|
||||
db = whereCompany(db, database.CAMPAIGN_TABLE, companyID)
|
||||
}
|
||||
|
||||
whereClause := "closed_at IS NOT NULL"
|
||||
if !includeTestCampaigns {
|
||||
whereClause += " AND is_test = false"
|
||||
}
|
||||
|
||||
res := db.
|
||||
Model(&database.Campaign{}).
|
||||
Where("closed_at IS NOT NULL AND is_test IS false").
|
||||
Where(whereClause).
|
||||
Count(&c)
|
||||
|
||||
return c, res.Error
|
||||
|
||||
@@ -736,6 +736,7 @@ func (c *Campaign) GetStats(
|
||||
ctx context.Context,
|
||||
session *model.Session,
|
||||
companyID *uuid.UUID,
|
||||
includeTestCampaigns bool,
|
||||
) (*model.CampaignsStatView, error) {
|
||||
ae := NewAuditEvent("Campaign.GetStats", session)
|
||||
if companyID != nil {
|
||||
@@ -752,15 +753,15 @@ func (c *Campaign) GetStats(
|
||||
return nil, errs.ErrAuthorizationFailed
|
||||
}
|
||||
// get stats
|
||||
active, err := c.CampaignRepository.GetActiveCount(ctx, companyID)
|
||||
active, err := c.CampaignRepository.GetActiveCount(ctx, companyID, includeTestCampaigns)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
upcoming, err := c.CampaignRepository.GetUpcomingCount(ctx, companyID)
|
||||
upcoming, err := c.CampaignRepository.GetUpcomingCount(ctx, companyID, includeTestCampaigns)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
finished, err := c.CampaignRepository.GetFinishedCount(ctx, companyID)
|
||||
finished, err := c.CampaignRepository.GetFinishedCount(ctx, companyID, includeTestCampaigns)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -74,6 +74,15 @@ const appendQuery = (query) => {
|
||||
if (query.search) {
|
||||
urlQuery += `&search=${query.search}`;
|
||||
}
|
||||
|
||||
// Handle additional parameters
|
||||
const knownParams = ['currentPage', 'perPage', 'page', 'sortBy', 'sortOrder', 'search'];
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (!knownParams.includes(key) && value !== undefined && value !== null) {
|
||||
urlQuery += `&${key}=${encodeURIComponent(value)}`;
|
||||
}
|
||||
}
|
||||
|
||||
return urlQuery;
|
||||
};
|
||||
/**
|
||||
@@ -648,8 +657,12 @@ export class API {
|
||||
* Get campaigns stats
|
||||
* if no company ID is provided it retrieves the global stats including all companies
|
||||
*/
|
||||
getStats: async (companyID = null) => {
|
||||
return await getJSON(this.getPath(`/campaign/statistics?${this.companyQuery(companyID)}`));
|
||||
getStats: async (companyID = null, options = {}) => {
|
||||
return await getJSON(
|
||||
this.getPath(
|
||||
`/campaign/statistics?${appendQuery(options)}${this.appendCompanyQuery(companyID)}`
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import CampaignCalender from '$lib/components/CampaignCalendar.svelte';
|
||||
import CampaignTrendChart from '$lib/components/CampaignTrendChart.svelte';
|
||||
import { fetchAllRows } from '$lib/utils/api-utils';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
// services
|
||||
const appStateService = AppStateService.instance;
|
||||
@@ -67,6 +68,17 @@
|
||||
let calendarStartDate = null;
|
||||
let calendarEndDate = null;
|
||||
|
||||
// Toggle for including test campaigns
|
||||
let includeTestCampaigns = false;
|
||||
|
||||
// Handler for when toggle changes
|
||||
const handleToggleChange = async () => {
|
||||
// Wait for binding to update
|
||||
await tick();
|
||||
// Refresh all data with new toggle state
|
||||
await refresh(false);
|
||||
};
|
||||
|
||||
// hooks
|
||||
onMount(() => {
|
||||
const context = appStateService.getContext();
|
||||
@@ -91,7 +103,9 @@
|
||||
if (showLoading) {
|
||||
showIsLoading();
|
||||
}
|
||||
let res = await api.campaign.getStats(contextCompanyID);
|
||||
let res = await api.campaign.getStats(contextCompanyID, {
|
||||
includeTest: includeTestCampaigns
|
||||
});
|
||||
if (!res.success) {
|
||||
throw res.error;
|
||||
}
|
||||
@@ -124,7 +138,7 @@
|
||||
const a = api.campaign.getWithinDates(
|
||||
calendarStartDate.toISOString(),
|
||||
calendarEndDate.toISOString(),
|
||||
options,
|
||||
{ ...options, includeTest: includeTestCampaigns },
|
||||
contextCompanyID
|
||||
);
|
||||
return a;
|
||||
@@ -142,7 +156,15 @@
|
||||
isActiveCampaignsLoading = true;
|
||||
}
|
||||
try {
|
||||
const res = await api.campaign.getAllActive(activeTableURLParams, contextCompanyID);
|
||||
const options = {
|
||||
page: activeTableURLParams.currentPage,
|
||||
perPage: activeTableURLParams.perPage,
|
||||
sortBy: activeTableURLParams.sortBy,
|
||||
sortOrder: activeTableURLParams.sortOrder,
|
||||
search: activeTableURLParams.search,
|
||||
includeTest: includeTestCampaigns
|
||||
};
|
||||
const res = await api.campaign.getAllActive(options, contextCompanyID);
|
||||
if (!res.success) {
|
||||
throw res.error;
|
||||
}
|
||||
@@ -162,7 +184,15 @@
|
||||
isUpcomingCampaignsLoading = true;
|
||||
}
|
||||
try {
|
||||
const res = await api.campaign.getAllUpcoming(scheduledTableURLParams, contextCompanyID);
|
||||
const options = {
|
||||
page: scheduledTableURLParams.currentPage,
|
||||
perPage: scheduledTableURLParams.perPage,
|
||||
sortBy: scheduledTableURLParams.sortBy,
|
||||
sortOrder: scheduledTableURLParams.sortOrder,
|
||||
search: scheduledTableURLParams.search,
|
||||
includeTest: includeTestCampaigns
|
||||
};
|
||||
const res = await api.campaign.getAllUpcoming(options, contextCompanyID);
|
||||
if (!res.success) {
|
||||
throw res.error;
|
||||
}
|
||||
@@ -182,7 +212,15 @@
|
||||
isFinishedCampaignsLoading = true;
|
||||
}
|
||||
try {
|
||||
const res = await api.campaign.getAllFinished(completedTableURLParams, contextCompanyID);
|
||||
const options = {
|
||||
page: completedTableURLParams.currentPage,
|
||||
perPage: completedTableURLParams.perPage,
|
||||
sortBy: completedTableURLParams.sortBy,
|
||||
sortOrder: completedTableURLParams.sortOrder,
|
||||
search: completedTableURLParams.search,
|
||||
includeTest: includeTestCampaigns
|
||||
};
|
||||
const res = await api.campaign.getAllFinished(options, contextCompanyID);
|
||||
if (!res.success) {
|
||||
throw res.error;
|
||||
}
|
||||
@@ -220,7 +258,15 @@
|
||||
sortOrder: 'desc',
|
||||
perPage: 10
|
||||
});
|
||||
const res = await api.campaign.getAllCampaignStats(statsParams, contextCompanyID);
|
||||
const options = {
|
||||
page: statsParams.currentPage,
|
||||
perPage: statsParams.perPage,
|
||||
sortBy: statsParams.sortBy,
|
||||
sortOrder: statsParams.sortOrder,
|
||||
search: statsParams.search,
|
||||
includeTest: includeTestCampaigns
|
||||
};
|
||||
const res = await api.campaign.getAllCampaignStats(options, contextCompanyID);
|
||||
if (!res.success) {
|
||||
throw res.error;
|
||||
}
|
||||
@@ -245,17 +291,50 @@
|
||||
<main>
|
||||
<div class="flex justify-between">
|
||||
<Headline>Dashboard</Headline>
|
||||
<AutoRefresh
|
||||
isLoading={false}
|
||||
onRefresh={async () => {
|
||||
await refresh(false);
|
||||
}}
|
||||
/>
|
||||
<div class="flex gap-4 items-center">
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={includeTestCampaigns}
|
||||
on:change={handleToggleChange}
|
||||
class="rounded"
|
||||
/>
|
||||
Include test campaigns
|
||||
</label>
|
||||
<AutoRefresh
|
||||
isLoading={false}
|
||||
onRefresh={async () => {
|
||||
await refresh(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if contextCompanyName}
|
||||
<SubHeadline>{contextCompanyName}</SubHeadline>
|
||||
{/if}
|
||||
|
||||
{#if includeTestCampaigns}
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-yellow-800">
|
||||
<strong>Test campaigns included:</strong> The dashboard is currently showing both production
|
||||
and test campaigns.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 mt-4">
|
||||
<a href="/campaign">
|
||||
<StatsCard
|
||||
|
||||
Reference in New Issue
Block a user