Skip to content

Commit 79970e0

Browse files
Merge pull request #12 from microting/master
Master changes
2 parents a06fae3 + fa59147 commit 79970e0

File tree

93 files changed

+1159
-801
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+1159
-801
lines changed

.github/copilot-instructions.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
# GitHub Copilot Instructions for eForm Angular Frontend
22

3+
> **Note**: These instructions follow the [GitHub Copilot coding agent best practices](https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-coding-agent). For more information about using Copilot effectively in this repository, refer to the official documentation.
4+
5+
## Quick Start for Copilot
6+
7+
**Critical requirements before starting:**
8+
- ✅ .NET 9.x SDK installed
9+
- ✅ Node.js 22.19.x+ and yarn 1.22.19+ installed
10+
- ✅ Repository structure: `eFormAPI/` (backend) + `eform-client/` (frontend)
11+
12+
**Essential commands for validation:**
13+
```bash
14+
# Backend: Restore & Build
15+
cd eFormAPI && dotnet restore && dotnet build
16+
17+
# Frontend: Install & Build
18+
cd eform-client && yarn install && yarn build
19+
20+
# Run tests
21+
cd eFormAPI && dotnet test
22+
cd eform-client && yarn test:unit
23+
24+
# Lint code
25+
cd eform-client && yarn lint
26+
cd eFormAPI && dotnet format --verify-no-changes
27+
```
28+
29+
**Remember:**
30+
- Always run build/test commands with adequate timeouts (see Build Process section)
31+
- Never cancel long-running operations early
32+
- Make minimal, surgical changes to existing code
33+
- Test both frontend and backend if changes affect both
34+
335
## Repository Overview
436

537
This is **eForm Angular Frontend**, a multi-component application consisting of:
@@ -383,6 +415,22 @@ When making changes, always:
383415
- Sensitive configuration via environment variables
384416
- Database connections use secure connection strings
385417

418+
## When to Ask for Help
419+
420+
Copilot should request human assistance when encountering:
421+
422+
- **Ambiguous Requirements**: Issue description lacks clarity or contains conflicting requirements
423+
- **Missing Information**: Cannot determine which files to modify or what the expected behavior should be
424+
- **Build Failures**: Cannot resolve build errors after reasonable attempts
425+
- **Test Failures**: Existing tests fail and the root cause is unclear or requires business logic knowledge
426+
- **Security Concerns**: Uncertain about security implications of a change
427+
- **Breaking Changes**: Proposed changes would significantly alter existing functionality
428+
- **Architecture Decisions**: Need to make architectural choices that affect multiple components
429+
- **Access Issues**: Cannot access required external services, databases, or dependencies
430+
- **Scope Concerns**: The issue appears larger than initially described and might need to be split
431+
432+
Always provide context about what you've attempted and what's blocking progress when asking for help.
433+
386434
---
387435

388436
**Important**: All build commands have been tested with actual execution times. The timeouts specified are based on real measurements plus buffer time. Never cancel long-running operations early - they are designed to complete successfully within the specified timeframes.

.github/workflows/dotnet-core-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- uses: actions/checkout@v3
1414
- name: Build Docker image
1515
id: build
16-
run: docker build . -t microtingas/frontend-container:latest --build-arg GITVERSION=1.0.0 --build-arg SENTRY_AUTH_TOKEN=${{secrets.SENTRY_AUTH_TOKEN}}
16+
run: docker build . -t microtingas/frontend-container:latest --build-arg GITVERSION=1.0.0 --build-arg SENTRY_AUTH_TOKEN=${{secrets.SENTRY_AUTH_TOKEN}} --build-arg DISABLE_SENTRY=true
1717
shell: bash
1818
- run: docker save microtingas/frontend-container:latest -o container.tar
1919
- uses: actions/upload-artifact@v4

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ FROM node:22-bookworm-slim as node-env
22

33
WORKDIR /app
44
ARG SENTRY_AUTH_TOKEN
5+
ARG DISABLE_SENTRY
56
ENV PATH /app/node_modules/.bin:$PATH
67
COPY eform-client ./
78
RUN apt-get update
89
RUN apt-get -y -q install ca-certificates
910
RUN yarn install
1011
RUN yarn build
11-
RUN if [ -n "$SENTRY_AUTH_TOKEN" ]; then yarn sentrysourcemap; else echo "SENTRY_AUTH_TOKEN is not set"; fi
12+
RUN if [ -n "$SENTRY_AUTH_TOKEN" ] && [ "$DISABLE_SENTRY" != "true" ]; then yarn sentrysourcemap; else echo "Sentry sourcemap upload skipped (DISABLE_SENTRY=$DISABLE_SENTRY)"; fi
1213

1314
FROM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build-env
1415
WORKDIR /app
1516
ARG GITVERSION
17+
ARG DISABLE_SENTRY
1618

1719
# Copy csproj and restore as distinct layers
1820
COPY eFormAPI/eFormAPI.Web ./
@@ -22,6 +24,8 @@ RUN pwd
2224
# Build runtime image
2325
FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble
2426
WORKDIR /app
27+
ARG DISABLE_SENTRY
28+
ENV DISABLE_SENTRY=${DISABLE_SENTRY}
2529
COPY --from=build-env /app/out .
2630
COPY --from=node-env /app/dist/browser wwwroot
2731
RUN rm connection.json; exit 0

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ You need to create an account for Microting API and get your access credentials.
4444

4545
* Call Microting at +45 66 11 10 66 to get started.
4646

47+
## Sentry Configuration
48+
49+
Sentry error tracking is enabled by default in production environments but disabled in development mode to prevent unnecessary test data collection.
50+
51+
### Development Mode
52+
When running locally with `yarn start`, Sentry is automatically disabled via the `environment.ts` configuration file where `enableSentry: false`.
53+
54+
### Production Mode
55+
In production builds, Sentry is enabled through `environment.prod.ts` where `enableSentry: true`.
56+
57+
### Docker Configuration
58+
When building Docker images, you can control Sentry behavior using the `DISABLE_SENTRY` build argument:
59+
60+
```bash
61+
# Disable Sentry for testing/CI environments
62+
docker build --build-arg DISABLE_SENTRY=true -t my-image .
63+
64+
# Enable Sentry for production (default behavior)
65+
docker build -t my-image .
66+
```
67+
68+
The `DISABLE_SENTRY` environment variable can be set to `true` or `1` to disable Sentry in both the Angular frontend and C# backend.
69+
4770
## Testing
4871

4972
### E2E Test Migration

eFormAPI/eFormAPI.Web/Program.cs

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -71,29 +71,36 @@ public class Program
7171

7272
public static void Main(string[] args)
7373
{
74-
SentrySdk.Init(options =>
75-
{
76-
// A Sentry Data Source Name (DSN) is required.
77-
// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/
78-
// You can set it in the SENTRY_DSN environment variable, or you can set it in code here.
79-
options.Dsn = "https://a20910d51f605d94e956163ffbf9dd5a@o4506241219428352.ingest.sentry.io/4506279162019840";
80-
81-
// When debug is enabled, the Sentry client will emit detailed debugging information to the console.
82-
// This might be helpful, or might interfere with the normal operation of your application.
83-
// We enable it here for demonstration purposes when first trying Sentry.
84-
// You shouldn't do this in your applications unless you're troubleshooting issues with Sentry.
85-
options.Debug = false;
86-
87-
// This option is recommended. It enables Sentry's "Release Health" feature.
88-
options.AutoSessionTracking = true;
74+
var disableSentry = Environment.GetEnvironmentVariable("DISABLE_SENTRY");
75+
var sentryDisabled = !string.IsNullOrEmpty(disableSentry) &&
76+
(disableSentry.ToLower() == "true" || disableSentry == "1");
8977

90-
// This option is recommended for client applications only. It ensures all threads use the same global scope.
91-
// If you're writing a background service of any kind, you should remove this.
92-
options.IsGlobalModeEnabled = false;
93-
94-
// This option will enable Sentry's tracing features. You still need to start transactions and spans.
95-
//options.EnableTracing = true;
96-
});
78+
if (!sentryDisabled)
79+
{
80+
SentrySdk.Init(options =>
81+
{
82+
// A Sentry Data Source Name (DSN) is required.
83+
// See https://docs.sentry.io/product/sentry-basics/dsn-explainer/
84+
// You can set it in the SENTRY_DSN environment variable, or you can set it in code here.
85+
options.Dsn = "https://a20910d51f605d94e956163ffbf9dd5a@o4506241219428352.ingest.sentry.io/4506279162019840";
86+
87+
// When debug is enabled, the Sentry client will emit detailed debugging information to the console.
88+
// This might be helpful, or might interfere with the normal operation of your application.
89+
// We enable it here for demonstration purposes when first trying Sentry.
90+
// You shouldn't do this in your applications unless you're troubleshooting issues with Sentry.
91+
options.Debug = false;
92+
93+
// This option is recommended. It enables Sentry's "Release Health" feature.
94+
options.AutoSessionTracking = true;
95+
96+
// This option is recommended for client applications only. It ensures all threads use the same global scope.
97+
// If you're writing a background service of any kind, you should remove this.
98+
options.IsGlobalModeEnabled = false;
99+
100+
// This option will enable Sentry's tracing features. You still need to start transactions and spans.
101+
//options.EnableTracing = true;
102+
});
103+
}
97104

98105
var host = BuildWebHost(args);
99106
InitializeSettings(host, args).Wait();
@@ -518,17 +525,23 @@ private static IWebHost BuildWebHost(string[] args)
518525
{
519526
string numberString = match.Groups[1].Value;
520527
int number = int.Parse(numberString);
521-
SentrySdk.ConfigureScope(scope =>
528+
var disableSentry = Environment.GetEnvironmentVariable("DISABLE_SENTRY");
529+
var sentryDisabled = !string.IsNullOrEmpty(disableSentry) &&
530+
(disableSentry.ToLower() == "true" || disableSentry == "1");
531+
if (!sentryDisabled)
522532
{
523-
scope.SetTag("customerNo", number.ToString());
524-
Console.WriteLine("customerNo: " + number);
525-
scope.SetTag("osVersion", Environment.OSVersion.ToString());
526-
Console.WriteLine("osVersion: " + Environment.OSVersion);
527-
scope.SetTag("osArchitecture", RuntimeInformation.OSArchitecture.ToString());
528-
Console.WriteLine("osArchitecture: " + RuntimeInformation.OSArchitecture);
529-
scope.SetTag("osName", RuntimeInformation.OSDescription);
530-
Console.WriteLine("osName: " + RuntimeInformation.OSDescription);
531-
});
533+
SentrySdk.ConfigureScope(scope =>
534+
{
535+
scope.SetTag("customerNo", number.ToString());
536+
Console.WriteLine("customerNo: " + number);
537+
scope.SetTag("osVersion", Environment.OSVersion.ToString());
538+
Console.WriteLine("osVersion: " + Environment.OSVersion);
539+
scope.SetTag("osArchitecture", RuntimeInformation.OSArchitecture.ToString());
540+
Console.WriteLine("osArchitecture: " + RuntimeInformation.OSArchitecture);
541+
scope.SetTag("osName", RuntimeInformation.OSDescription);
542+
Console.WriteLine("osName: " + RuntimeInformation.OSDescription);
543+
});
544+
}
532545
}
533546

534547
using var dbContext = contextFactory.CreateDbContext([_defaultConnectionString]);

eform-client/cypress/e2e/DeviceUsers.page.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ class DeviceUsersPage extends PageWithNavbarPage{
156156
name = '',
157157
surname = ''
158158
): void {
159-
deviceUser.editBtn().should('be.visible').click();
159+
const index = deviceUser.index - 1;
160+
deviceUser.openRowMenu();
161+
cy.get(`#editDeviceUserBtn${index}`).should('be.visible').click();
160162
// @ts-ignore
161163
cy.get('#firstName').should('be.visible');
162164
if (name !== '') {
@@ -178,6 +180,7 @@ const deviceUsersPage = new DeviceUsersPage();
178180
export default deviceUsersPage;
179181

180182
export class DeviceUsersRowObject {
183+
index: number;
181184
siteId: number;
182185
firstName: string;
183186
lastName: string;
@@ -187,6 +190,7 @@ export class DeviceUsersRowObject {
187190
deleteBtn: Cypress.Chainable<JQuery<HTMLElement>>;
188191

189192
getRow(rowNum: number) {
193+
this.index = rowNum;
190194
// @ts-ignore
191195
if (cy.get('#deviceUserId').eq(rowNum - 1).should('exist')) {
192196
// @ts-ignore
@@ -203,8 +207,16 @@ export class DeviceUsersRowObject {
203207
return this;
204208
}
205209

210+
openRowMenu() {
211+
const index = this.index - 1;
212+
cy.get(`#action-items-${index} #actionMenu`).should('be.visible').click();
213+
cy.wait(200);
214+
}
215+
206216
delete() {
207-
this.deleteBtn.should('be.visible').click();
217+
const index = this.index - 1;
218+
this.openRowMenu();
219+
cy.get(`#deleteDeviceUserBtn${index}`).should('be.visible').click();
208220
deviceUsersPage.saveDeleteBtn().should('be.visible').click();
209221
// @ts-ignore
210222
cy.get('#spinner-animation').should('not.exist', { timeout: 40000 });

eform-client/cypress/e2e/Sites.page.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export default sitesPage;
125125
export class SitesRowObject {
126126
constructor() {}
127127

128+
index: number;
128129
element: Cypress.Chainable<JQuery<HTMLElement>>;
129130
siteId: number;
130131
units: string;
@@ -134,6 +135,7 @@ export class SitesRowObject {
134135
deleteBtn: Cypress.Chainable<JQuery<HTMLElement>>;
135136

136137
getRow(rowNum): Promise<SitesRowObject> {
138+
this.index = rowNum;
137139
this.element = cy.get('tbody > tr').eq(rowNum - 1);
138140
if (this.element) {
139141
this.siteId = +(this.element.find('#siteUUId').invoke('text'));
@@ -146,6 +148,13 @@ export class SitesRowObject {
146148
}
147149
return this;
148150
}
151+
152+
openRowMenu() {
153+
const index = this.index - 1;
154+
cy.get(`#action-items-${index} #actionMenu`).should('be.visible').click();
155+
cy.wait(200);
156+
}
157+
149158
closeEditModal(clickCancel = false) {
150159
if (clickCancel) {
151160
sitesPage.siteEditCancelBtn().click();
@@ -156,22 +165,10 @@ export class SitesRowObject {
156165
sitesPage.sitesManageTagsBtn().should('be.visible', { timeout: 40000 });
157166
}
158167

159-
getRow(rowNum): Promise<SitesRowObject> {
160-
this.element = Cypress.$$('tbody > tr').eq(rowNum - 1);
161-
if (this.element) {
162-
this.siteId = +this.element.find('#siteUUId').text();
163-
this.units = this.element.find('#units').text();
164-
this.siteName = this.element.find('#siteName').text();
165-
const list = this.element.find('mat-chip-list mat-chip > span');
166-
this.tags = Promise.all(list.map((_, element) => Cypress.$(element).text()));
167-
this.editBtn = this.element.find('#editSiteBtn');
168-
this.deleteBtn = this.element.find('#deleteSiteBtn');
169-
}
170-
return this;
171-
}
172-
173168
openEditModal(site?: { name?: string; tags?: string[] }) {
174-
this.editBtn.click();
169+
const index = this.index - 1;
170+
this.openRowMenu();
171+
cy.get(`#editSiteBtn${index}`).should('be.visible').click();
175172
cy.wait(500);
176173
sitesPage.siteEditCancelBtn().should('be.visible', {timeout: 40000});
177174
if (site) {
@@ -196,7 +193,9 @@ export class SitesRowObject {
196193
}
197194

198195
openDeleteModal() {
199-
this.deleteBtn.click();
196+
const index = this.index - 1;
197+
this.openRowMenu();
198+
cy.get(`#deleteSiteBtn${index}`).should('be.visible').click();
200199
cy.wait(500);
201200
sitesPage.siteDeleteCancelBtn().should('be.visible', {timeout: 40000}).click();
202201
}

eform-client/cypress/e2e/UserAdministration.page.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ const userAdministrationPage = new UserAdministrationPage();
170170
export default userAdministrationPage;
171171

172172
export class UserAdministrationRowObject {
173+
index: number;
173174
id: number;
174175
email: string;
175176
fullName: string;
@@ -178,6 +179,7 @@ export class UserAdministrationRowObject {
178179
deleteBtn: Cypress.Chainable<JQuery<HTMLElement>>;
179180

180181
getRow(rowNum: number) {
182+
this.index = rowNum;
181183
const index = rowNum - 1;
182184

183185
cy.get(`#userAdministrationId-${index}`).invoke('text').then(text => {
@@ -199,8 +201,16 @@ export class UserAdministrationRowObject {
199201
return this;
200202
}
201203

204+
openRowMenu() {
205+
const index = this.index - 1;
206+
cy.get(`#action-items-${index} #actionMenu`).should('be.visible').click();
207+
cy.wait(200);
208+
}
209+
202210
edit(user: UserAdministrationObject, clickCancel = false) {
203-
this.editBtn.should('be.visible').click();
211+
const index = this.index - 1;
212+
this.openRowMenu();
213+
cy.get(`#userAdministrationEditBtn-${index}`).should('be.visible').click();
204214
cy.get('#editFirstName').should('be.visible');
205215

206216
if (user.firstName) {
@@ -257,7 +267,9 @@ export class UserAdministrationRowObject {
257267
}
258268

259269
delete(clickCancel = false) {
260-
this.deleteBtn.should('be.visible').click();
270+
const index = this.index - 1;
271+
this.openRowMenu();
272+
cy.get(`#userAdministrationDeleteBtn-${index}`).should('be.visible').click();
261273
cy.get('#userDeleteCancelBtn').should('be.visible');
262274

263275
if (clickCancel) {

0 commit comments

Comments
 (0)