Skip to content

Commit 28a42b3

Browse files
Merge pull request certinia#628 from lukecotter/chore-sample-app
chore: add sample app
2 parents c9a0eb8 + f079712 commit 28a42b3

26 files changed

Lines changed: 877 additions & 35 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.sf
2+
.sfdx
3+
4+
.DS_Store
5+
6+
node_modules

eslint.config.mjs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,55 @@
1-
import typescriptEslint from "@typescript-eslint/eslint-plugin";
2-
import tsParser from "@typescript-eslint/parser";
3-
import path from "node:path";
4-
import { fileURLToPath } from "node:url";
5-
import js from "@eslint/js";
6-
import { FlatCompat } from "@eslint/eslintrc";
1+
import { FlatCompat } from '@eslint/eslintrc';
2+
import js from '@eslint/js';
3+
import typescriptEslint from '@typescript-eslint/eslint-plugin';
4+
import tsParser from '@typescript-eslint/parser';
5+
import path from 'node:path';
6+
import { fileURLToPath } from 'node:url';
77

88
const __filename = fileURLToPath(import.meta.url);
99
const __dirname = path.dirname(__filename);
1010
const compat = new FlatCompat({
11-
baseDirectory: __dirname,
12-
recommendedConfig: js.configs.recommended,
13-
allConfig: js.configs.all
11+
baseDirectory: __dirname,
12+
recommendedConfig: js.configs.recommended,
13+
allConfig: js.configs.all,
1414
});
1515

16-
export default [{
17-
ignores: ["**/node_modules"],
18-
}, ...compat.extends(
19-
"eslint:recommended",
20-
"plugin:@typescript-eslint/eslint-recommended",
21-
"plugin:@typescript-eslint/recommended",
22-
"prettier",
23-
), {
16+
export default [
17+
{
18+
ignores: ['**/node_modules', '**/.sf', '**/.sfdx'],
19+
},
20+
...compat.extends(
21+
'eslint:recommended',
22+
'plugin:@typescript-eslint/eslint-recommended',
23+
'plugin:@typescript-eslint/recommended',
24+
'prettier',
25+
),
26+
{
2427
plugins: {
25-
"@typescript-eslint": typescriptEslint,
28+
'@typescript-eslint': typescriptEslint,
2629
},
2730

2831
languageOptions: {
29-
parser: tsParser,
30-
ecmaVersion: "latest",
31-
sourceType: "module",
32+
parser: tsParser,
33+
ecmaVersion: 'latest',
34+
sourceType: 'module',
3235
},
3336

3437
rules: {
35-
"no-console": "warn",
36-
"@typescript-eslint/naming-convention": "warn",
37-
semi: "warn",
38+
'no-console': 'warn',
39+
'@typescript-eslint/naming-convention': 'warn',
40+
semi: 'warn',
3841

39-
"@typescript-eslint/no-unused-vars": ["error", {
40-
argsIgnorePattern: "^_",
41-
varsIgnorePattern: "^_",
42-
}],
42+
'@typescript-eslint/no-unused-vars': [
43+
'error',
44+
{
45+
argsIgnorePattern: '^_',
46+
varsIgnorePattern: '^_',
47+
},
48+
],
4349

44-
"@typescript-eslint/no-explicit-any": "warn",
45-
curly: "warn",
46-
eqeqeq: "warn",
50+
'@typescript-eslint/no-explicit-any': 'warn',
51+
curly: 'warn',
52+
eqeqeq: 'warn',
4753
},
48-
}];
54+
},
55+
];

log-viewer/modules/components/calltree-view/CalltreeView.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
// todo: improve scroll rows performance
66
//
77
//todo: ** future **
8-
//todo: show total and self as percentage of total? + do the same on the analysis view?
9-
//todo: add class to locate current tree for current log
10-
//todo: add filter on line type
118
//todo: add filter on log level (fine, finer etc)
129
import {
1310
provideVSCodeDesignSystem,

sample-app/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Salesforce cache
2+
.sf/
3+
.sfdx/

sample-app/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Sample Salesforce Apex Project
2+
3+
This project is a sample Salesforce Apex used to generate Apex Debug Logs for testing and gif creation. The project includes custom triggers, mock Apex logic and workflows.
4+
5+
Can be used to test log analyzer features including the go to code functionality.
6+
Click on a class/method link in the call tree and you will be taken top the file in `force-app/main/default/classes`
7+
8+
## Setup Instructions
9+
10+
The sample log contains out from some managed packages that are not included in the install steps.
11+
In future we should probably create mocks managed packages to test these cases.
12+
13+
1. Create an org: `sf org create scratch -f config/project-scratch-def.json -d -a sample -y 3 --async`
14+
2. Deploy: `sf project deploy start -c -o sample`
15+
3. Setup desired log levels e.g `APEX_CODE,FINE; APEX_PROFILING,FINE; CALLOUT,INFO; DB,FINEST; NBA,INFO; SYSTEM,DEBUG; VALIDATION,INFO; VISUALFORCE,FINE; WAVE,INFO; WORKFLOW,FINE`
16+
4. In Dev console: `AccountService.createAccountsAndContacts();`
17+
5. Download the log
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"orgName": "lana",
3+
"edition": "Developer",
4+
"features": ["EnableSetPasswordInApi", "MultiCurrency"],
5+
"settings": {
6+
"lightningExperienceSettings": {
7+
"enableS1DesktopEnabled": true
8+
},
9+
"mobileSettings": {
10+
"enableS1EncryptedStoragePref2": false
11+
}
12+
}
13+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright (c) 2025 Certinia Inc. All rights reserved.
3+
*/
4+
5+
public with sharing class AccountService {
6+
7+
static Date KNOWN_MONDAY = Date.newInstance(2025, 6, 30);
8+
9+
public static void createAccountsAndContacts() {
10+
11+
Decimal revenue = getRevenue();
12+
13+
List<Account> accountsToInsert = new List<Account>();
14+
for (Integer i = 0; i < 300; i++) {
15+
Account acc = new Account();
16+
acc.Name = 'Account ' + i;
17+
acc.Description = 'This is account number ' + i;
18+
acc.AnnualRevenue = 15000;
19+
accountsToInsert.add(acc);
20+
}
21+
22+
System.debug('Accounts to insert: ' + accountsToInsert);
23+
24+
// Validate Accounts
25+
for (Account acc : accountsToInsert) {
26+
if (String.isBlank(acc.Name)) {
27+
throw new IllegalArgumentException('Account Name cannot be blank.');
28+
}
29+
if (acc.AnnualRevenue != null && acc.AnnualRevenue < 0) {
30+
throw new IllegalArgumentException('Annual Revenue cannot be negative.');
31+
}
32+
}
33+
34+
// Insert Accounts
35+
try {
36+
insert accountsToInsert;
37+
System.debug('Accounts inserted successfully.');
38+
} catch (DmlException e) {
39+
System.debug('Error inserting accounts: ' + e.getMessage());
40+
throw e;
41+
}
42+
43+
// Map Account Names to IDs for linking Contacts
44+
Map<String, Id> accountNameToIdMap = new Map<String, Id>();
45+
for (Account acc : accountsToInsert) {
46+
accountNameToIdMap.put(acc.Name, acc.Id);
47+
}
48+
49+
List<Contact> contacts = new List<Contact>();
50+
for (Account acc : accountsToInsert) {
51+
Contact contact = new Contact(
52+
FirstName = 'Linked',
53+
LastName = 'Contact ' + acc.Name,
54+
AccountId = acc.Id
55+
);
56+
contacts.add(contact);
57+
}
58+
59+
System.debug('Contacts to insert: ' + contacts);
60+
61+
// Validate and link Contacts
62+
for (Contact con : contacts) {
63+
if (String.isBlank(con.LastName)) {
64+
throw new IllegalArgumentException('Contact Last Name cannot be blank.');
65+
}
66+
67+
if (String.isBlank(con.AccountId)){
68+
throw new IllegalArgumentException('Contact must be linked to a valid Account.');
69+
}
70+
}
71+
72+
// Query Accounts for additional validation
73+
Set<Id> accountIds = new Set<Id>();
74+
for (Contact con : contacts) {
75+
accountIds.add(con.AccountId);
76+
}
77+
accountIds.remove(null);
78+
79+
Map<Id, Account> accountById = new Map<Id, Account>([SELECT Id, Name, AnnualRevenue FROM Account WHERE Id IN :accountIds]);
80+
System.debug('Queried Accounts: ' + accountById);
81+
82+
// Perform additional validation based on queried Accounts
83+
for (Contact con : contacts) {
84+
Account relatedAccount = getAccount(con.AccountId, accountById);
85+
if (relatedAccount != null && relatedAccount.AnnualRevenue < 10000) {
86+
throw new IllegalArgumentException('Contact cannot be linked to an Account with Annual Revenue less than 10,000.');
87+
}
88+
}
89+
90+
new RecursiveSearcher().search(15, 15, 1100);
91+
92+
// Insert Contacts
93+
try {
94+
insert contacts;
95+
System.debug('Contacts inserted successfully.');
96+
} catch (DmlException e) {
97+
System.debug('Error inserting contacts: ' + e.getMessage());
98+
throw e;
99+
}
100+
101+
System.debug('Account and Contact creation completed successfully.');
102+
new RecursiveSearcher().search(4, 8, 500);
103+
// LogUtil.burnLogStatements(7000);
104+
// LogUtil.burnCPU(500);
105+
}
106+
107+
private static Account getAccount(Id accountId, Map<Id, Account> accountById){
108+
return accountById.get(accountId);
109+
// NOTE: Purposly using a loop and not a map to create a more dense debug log
110+
// for(Account a : accounts){
111+
// if(a.Id == accountId) {
112+
// return a;
113+
// }
114+
// }
115+
// return null;
116+
}
117+
118+
private static Decimal getRevenue() {
119+
120+
return getDayValue(Date.today())* 10;
121+
// Date tdy = Date.today();
122+
// Date startOfYear = Date.newInstance(tdy.year(), 1, 1);
123+
// Date endOfYear = Date.newInstance(tdy.year(), 12, 31);
124+
125+
126+
// Decimal revenue = 0 ;
127+
// while(startOfYear < tdy){
128+
// revenue += getDayValue(startOfYear) * 2;
129+
// startOfYear.addDays(1);
130+
// }
131+
132+
// while(endOfYear > tdy){
133+
// revenue += getDayValue(endOfYear);
134+
// endOfYear.addDays(-1);
135+
// }
136+
137+
// return revenue;
138+
139+
}
140+
141+
private static Decimal getDayValue(Date day) {
142+
Integer dayDiff = KNOWN_MONDAY.daysBetween(day);
143+
Integer dayIndex = Math.mod(dayDiff, 7);
144+
switch on dayIndex {
145+
when 0, 1, 2, 3, 4 {
146+
return 2000;
147+
}
148+
when 5 {
149+
return 1000;
150+
}
151+
when else {
152+
return 0;
153+
}
154+
}
155+
}
156+
157+
158+
159+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>64.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2025 Certinia Inc. All rights reserved.
3+
*/
4+
5+
public with sharing class AccountTriggerHandler {
6+
public void handleBeforeInsert(List<Account> newAccounts) {
7+
for (Account acc : newAccounts) {
8+
// Sample business logic: Ensure Account Name is not blank
9+
if (String.isBlank(acc.Name)) {
10+
acc.Name = 'Default Account Name';
11+
}
12+
}
13+
14+
new AccountValidator().validate(newAccounts);
15+
}
16+
17+
public void handleBeforeUpdate(List<Account> newAccounts, Map<Id, Account> oldAccountMap) {
18+
for (Integer i = 0; i < newAccounts.size(); i++) {
19+
Account newAcc = newAccounts[i];
20+
Account oldAcc = oldAccountMap.get(newAcc.Id);
21+
22+
// Sample business logic: Log changes to Account Name
23+
if (oldAcc.Name != newAcc.Name) {
24+
System.debug('Account Name changed from ' + oldAcc.Name + ' to ' + newAcc.Name);
25+
}
26+
}
27+
}
28+
29+
public void handleAfterInsert(List<Account> newAccounts) {
30+
for (Account acc : newAccounts) {
31+
BusinessLogic.validateAccountData(acc);
32+
}
33+
34+
new RecursiveSearcher().search(4, 9, 600);
35+
}
36+
37+
public void handleAfterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccountMap) {
38+
39+
}
40+
41+
private class AccountValidator {
42+
43+
public void validate(List<Account> accounts){
44+
45+
}
46+
47+
}
48+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>64.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>

0 commit comments

Comments
 (0)