Skip to content
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
20 changes: 7 additions & 13 deletions .github/workflows/ci-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@ on:
permissions: write-all

jobs:
# e2e-tests:
# uses: ./.github/workflows/e2e-tests.yml
# backend-tests:
# uses: ./.github/workflows/backend-tests.yml
e2e-tests:
uses: ./.github/workflows/e2e-tests.yml
backend-tests:
uses: ./.github/workflows/backend-tests.yml
build-publish-images:
# needs:
# - e2e-tests
# - backend-tests
uses: ./.github/workflows/docker.yml
deploy-to-kubernetes:
needs:
- build-publish-images
uses: ./.github/workflows/helm.yml
secrets:
KUBECONF: ${{ secrets.KUBECONF }}
- e2e-tests
- backend-tests
uses: ./.github/workflows/docker.yml
40 changes: 27 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
# Mentor Pulse

TODO: short introduction what mentor pulse is
Mentor Pulse is a dynamic platform designed to bridge the gap between mentors and mentees, fostering meaningful connections that drive personal and professional growth. Whether you're a mentor looking to share your expertise or a mentee seeking guidance, Mentor Pulse makes it easy to find the right match. With features like skill-based pairing and category-specific mentorship, the platform ensures tailored, impactful relationships. Empower your journey with Mentor Pulse—where mentorship meets opportunity.

The application has multiple components:

- [User service](./server/userservice/README.md)
- [Mentorships service](./server/mentorshipservice/README.md)
- [Rating service](./server/ratingservice/README.md)
- [GenAI service](./genai/README.md)
- [Client](./client/README.md)

## Local Development Setup

TODO: documentation to add for project setup (e.g. how to start with docker)
1. Stop any remaining containers and delete images

```
docker compose up --build
docker compose down -v
```

2. Start docker compose for local development

```
docker compose down -v
docker compose up --build
```

3. In your browser access the app under `localhost:80`

### Loading Mock Data

TODO: add link to mock data scripts
Then you might want to load mock data, to make the application useable. Especially, loading skills and categories is important.
To load mock data for local dev setup go to [server/scripts README](./server/scripts/README.md) and follow instructions.

## Deployment

TODO: add documentation about deployments
We have two types of deployments Kubernetes and ec2.

### Kubernetes

TODO: add link to kubernetes
Kubernetes deployment can be found [under](./helm/README.md).
For pushes on main we automatically deploy to Kubernetes cluster (see [workflow](.github/workflows/ci-main.yml)).

### EC2

Expand All @@ -46,27 +60,27 @@ You provide:

#### Workflow Steps

a. **Checkout & Secrets**
1. **Checkout & Secrets**
Repo is checked out.
Secrets are masked for safety.
SSH key is decoded and saved as infra/priv.pem.

b. **Tooling Setup**
2. **Tooling Setup**
Installs Terraform (v1.12.1) and Ansible (v11.6.0).

c. **Deploying EC2 (Terraform)**
3. **Deploying EC2 (Terraform)**
Runs make deploy in infra/:
Applies main.tf to create a new EC2 instance (Debian, public IP, SSH open, HTTP/HTTPS open).
Security group allows ports 22, 80, 443 from anywhere.
Waits for the instance to be reachable via SSH.

d. **Inventory Update**
4. **Inventory Update**
Extracts the new EC2 public IP and injects it into the Ansible inventory (inventory.ini).

e. **SSH Test**
5. **SSH Test**
Verifies SSH connectivity to the new instance using the provided key.

f. **Provisioning (Ansible)**
6. **Provisioning (Ansible)**
Runs make ansible in infra/:
Executes playbook.yml against the new EC2 instance.
Installs Docker, Docker Compose, Python, pip, etc.
Expand Down
8 changes: 5 additions & 3 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MentorPulse Client
# Mentor Pulse Client

TODO: give overview of client
The Mentor Pulse client is a React application using Antd, TanstackQuery, TanstackRouter and others.

## Docker

Expand All @@ -23,8 +23,10 @@ Or use the docker compose file in the root directory to build the entire applica
## Testing (E2E/UI tests)

The client is tested using E2E/UI tests. We use playwright as the framework for testing.
Tests are executed automatically for every [pull request](../.github/workflows/ci-pull-request.yml)
and every [push on main](../.github/workflows/ci-main.yml).

### Execute tests
### Execute tests locally

1. Start Services: Start docker with `docker compose up --build`.
2. Configure Node: Execute `nvm use` to use the correct node version.
Expand Down
1 change: 1 addition & 0 deletions client/src/components/organisms/MentorInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ function CreateApplicationModal({
]}
>
<TextArea
data-testid="application-modal-text-area"
rows={15}
placeholder="Write your application to the mentor ..."
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export function MenteeApplicationCard({
Contact Mentor
</Button>
<Button
data-testid={`rate-mentor-button-${mentorUser?.id}`}
type="primary"
onClick={() => {
setRateMentorModalIsOpen(true);
Expand Down Expand Up @@ -138,6 +139,7 @@ function RateMentorModal({
rules={[{ required: true, message: 'Please provide a rating!' }]}
>
<Input
data-testid="rating-input"
type="number"
min={1}
max={5}
Expand All @@ -150,7 +152,10 @@ function RateMentorModal({
required={true}
rules={[{ required: true, message: 'Please provide a review!' }]}
>
<Input.TextArea placeholder="Write your review here..." />
<Input.TextArea
data-testid="review-text-area"
placeholder="Write your review here..."
/>
</Form.Item>
</Form>
</Modal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export function MentorApplicationCard({
application,
}: MentorApplicationCardProps) {
const { data: getUserData } = useGetUser(
{ userId: application.mentorId! },
{ query: { enabled: !!application.mentorId } },
{ userId: application.menteeId! },
{ query: { enabled: !!application.menteeId } },
);
const queryClient = useQueryClient();

Expand Down Expand Up @@ -76,6 +76,7 @@ export function MentorApplicationCard({
Reject Application
</Button>
<Button
data-testid={`accept-application-button-${menteeUser?.id}`}
type="primary"
style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
onClick={() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function ViewProfileButton({ mentor }: ViewProfileButtonProps) {

return (
<Button
data-testid={`view-mentor-profile-${mentor.id}`}
data-testid={`view-mentor-profile-${mentor?.mentorId}`}
type="primary"
onClick={() => {
if (mentor.mentorId !== undefined) {
Expand Down
11 changes: 9 additions & 2 deletions client/src/utils/useCurrentUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import { useCurrentUserId } from '@/utils/useCurrentUserId';
export function useCurrentUser() {
const userId = useCurrentUserId();

const { data: getUserData, isLoading } = useGetUser(
{ userId: userId! },
{
query: {
enabled: !!userId,
},
},
);

if (!userId) {
return { currentUser: undefined, isLoading: false };
}

const { data: getUserData, isLoading } = useGetUser({ userId: userId });

return { currentUser: getUserData?.data.user, isLoading: isLoading };
}
104 changes: 101 additions & 3 deletions client/tests/applications.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,104 @@
// Test ability to send applications
import type { CreateUserRequest } from '@/api/user';
import { expect, test, type Page } from '@playwright/test';
import { users } from 'tests/resources/users';

// login as mentee, send application to mentor, wait for confirm, go to applications and check that it is under pending
async function login(page: Page, user: CreateUserRequest) {
await page.goto('/login');
await page.fill('input[id="userName"]', user.userName!);
await page.fill('input[id="password"]', user.password!);
await page.click('button[type="submit"]');
}

// login as mentor, accept application, logout, login as mentee that the application was from, go to applications, rate mentor
async function logout(page: Page) {
const logoutButton = page.locator('span:has-text("Logout")');
await logoutButton.waitFor({ state: 'visible' });
await logoutButton.click();
}

async function goToApplications(page: Page) {
const applicationsButton = page.locator('span:has-text("Applications")');
await applicationsButton.waitFor({ state: 'visible' });
await applicationsButton.click();
}

test.describe('Applications and Rating', () => {
test('Mentee creates application, mentor accepts application, mentee rates mentor', async ({
page,
}) => {
const mentorUsername = 'lorischwartz';
const menteeUsername = 'kelly14';
const mentor = users.filter((user) => user.userName === mentorUsername)[0]!;
const mentee = users.filter((user) => user.userName === menteeUsername)[0]!;

// Send application as user
await login(page, mentee);
await expect(page).toHaveURL('/search');

const viewProfileButton = page.locator(
`button[data-testid=view-mentor-profile-${mentor.id}]`,
);
await viewProfileButton.scrollIntoViewIfNeeded();
await viewProfileButton.waitFor({ state: 'visible' });
await viewProfileButton.click();

await expect(page).toHaveURL(`/mentor/${mentor.id}`);

const createApplicationButton = page.locator(
`button[data-testid=mentorship-application-button]`,
);
await createApplicationButton.waitFor({ state: 'visible' });
await createApplicationButton.click();

const textArea = page.locator(
'textarea[data-testid=application-modal-text-area]',
);
await textArea.waitFor({ state: 'visible' });
await textArea.fill('Hello, I want to be your padawan.');

const submitApplicationButton = page.locator(
'button:has-text("Submit Application")',
);
await submitApplicationButton.waitFor({ state: 'visible' });
await submitApplicationButton.click();

const successMessage = page.locator('text=Application created');
await successMessage.waitFor({ state: 'visible', timeout: 120000 });

await logout(page);

// Accept Application as mentor
await login(page, mentor);

const acceptApplicationButton = page
.locator(`button[data-testid=accept-application-button-${mentee.id}]`)
.first();
await acceptApplicationButton.waitFor({ state: 'visible' });
await acceptApplicationButton.click();

await logout(page);

// Rate mentor
await login(page, mentee);
await goToApplications(page);

const rateMentorButton = page.locator(
`button[data-testid=rate-mentor-button-${mentor.id}]`,
);
await rateMentorButton.waitFor({ state: 'visible' });
await rateMentorButton.click();

const ratingInput = page.locator(`input[data-testid=rating-input]`);
await ratingInput.waitFor({ state: 'visible' });
await ratingInput.fill('4');

const reviewTextArea = page.locator(
`textarea[data-testid=review-text-area]`,
);
await reviewTextArea.waitFor({ state: 'visible' });
await reviewTextArea.fill('Very good master Jedi.');

const submitRatingButton = page.locator(`button:has-text("Submit Rating")`);
await submitRatingButton.waitFor({ state: 'visible' });
await submitRatingButton.click();
});
});
12 changes: 0 additions & 12 deletions client/tests/global.setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test as setup } from '@playwright/test';
import {
createApplication,
createCategory,
createMentorProfile,
createSkill,
Expand All @@ -9,7 +8,6 @@ import { skills } from 'tests/resources/skills';
import { categories } from 'tests/resources/categories';
import { rateMentor } from '@/api/rating';
import { ratings } from 'tests/resources/ratings';
import { applications } from 'tests/resources/applications';
import { users } from 'tests/resources/users';
import { createUser } from '@/api/user';
import { mentorProfiles } from 'tests/resources/profiles';
Expand Down Expand Up @@ -69,14 +67,4 @@ setup('setup system with mock users, skills, categories, etc.', async ({}) => {
console.error(`Error response:`, error);
}
}

for (const application of applications) {
try {
const result = await createApplication(application);
console.info(`Application created:`, result.data, result.status);
} catch (error) {
console.error(`Failed to create application:`, application);
console.error(`Error response:`, error);
}
}
});
4 changes: 1 addition & 3 deletions client/tests/global.teardown.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test as teardown } from '@playwright/test';
import { Client } from 'pg';

teardown('delete values from database table', async ({}) => {
teardown('delete all values from database tables', async ({}) => {
await deleteTables('user_backend', ['user_table']);
await deleteTables('mentorship_backend', [
'applications_table',
Expand All @@ -10,8 +10,6 @@ teardown('delete values from database table', async ({}) => {
'categories_table',
]);
await deleteTables('rating_backend', ['ratings_table']);
// TODO: fix teardown (genai, no permission)
// await deleteTables('genai_backend', ['summarization_logs']);
});

function getClient(database: string): Client {
Expand Down
1 change: 0 additions & 1 deletion client/tests/mentor-profile.spec.ts

This file was deleted.

1 change: 0 additions & 1 deletion client/tests/resources/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ export const applications: MentorApplication[] = [
applicationMessage:
"Dear Mr. Pitts, I'm Nancy Mccarty, and I'm looking for guidance in my professional journey. Your experience would be invaluable in helping me achieve my career goals. I've been researching potential mentors in my field and your background particularly stands out to me. I'm currently facing several challenges in my career path and would greatly benefit from the wisdom and perspective of someone who has successfully navigated similar situations. I'm particularly interested in learning about strategic career planning, professional networking, and leadership development. I would be thrilled to have the opportunity to learn from your experiences and gain insights that could help shape my future career decisions. I'm prepared to invest the time and effort necessary to make our mentorship relationship productive and meaningful.",
},
// TODO: add some dummy applications
];
Loading
Loading