Skip to content

Team of 2 Guide

In this section, you and your partner will work on two different features simultaneously. This will simulate a real-world scenario where multiple developers contribute to the same codebase, potentially leading to merge conflicts.

9.1 Divide Roles

Decide who will be Member A and who will be Member B.

  • Member A: Will implement the Average Calculation feature.
  • Member B: Will implement the Reset Button feature.

9.2 Create Issues

Member A: Find Issue #6 ("Average Calculation Form") on the Project Board and move it to "In Progress".

Member B: Create a new issue:

  • Title: Add Reset Functionality
  • Description: Add a button to clear the input field and reset the result label.
  • Configure the issue:
    • Select the same "Feature" type and assign it to yourself.
    • Select Project from the list and change status to "In Progress".
    • Link the issue to Milestone 2: Average Calculation.

9.3 Implement Features (Parallel Work)

Both members should execute their tasks simultaneously on their own machines (or separate Codespaces).

Member A: Implement Average Calculation

  1. Sync your repository: ensure you are on main and have the latest changes.
  2. Create a new branch named feature/average-calc from main.
  3. Open app/main.py.
  4. Add from statistics import mean to imports (at the top of the file).
  5. Add the parse_and_average function (before the home function):

    def parse_and_average(raw: str | None) -> str:
        raw = raw or ""
        tokens = [t for part in raw.split(",") for t in part.strip().split()] if raw else []
        values = [float(t) for t in tokens if t]
        return f"Average: {mean(values):.4f}"
    
  6. Update the home() function to include the form:

    @ui.page('/')
    def home() -> None:
        ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold")
        ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4")
    
        numbers_input = ui.textarea(label="Numbers (comma or space separated)").classes("w-full h-32")
        result_label = ui.label("Enter numbers and click Compute").classes("mt-4 text-lg")
    
        def compute() -> None:
            # Connect the UI to the logic function
            result_label.text = parse_and_average(numbers_input.value)
    
        ui.button("Compute Average", on_click=compute).classes("mt-2")
        ui.separator()
    
  7. Add a Unit Test: To ensure your logic helps catch bugs, add a test case. Create tests/test_average.py:

    from app.main import parse_and_average
    
    def test_average_simple():
        assert parse_and_average("1, 2, 3") == "Average: 2.0000"
    

    Run the test:

    uv run pytest
    

  8. Verify it works locally (uv run fastapi dev).

  9. Stage and commit changes with the message: feat: add average calculation logic.
  10. Push branch to GitHub.

Member B: Implement Reset Functionality

  1. Sync your repository: ensure you are on main and have the latest changes.
  2. Create a new branch named feature/reset-button from main.
  3. Open app/main.py.
  4. Update the home() function to include a reset button. Note: You won't see Member A's code yet. Just implement your button as if you were the only one working on the file.

    @ui.page('/')
    def home() -> None:
        ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold")
        ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4")
    
        # Add ID or reference to inputs if needed, or just define them
        numbers_input = ui.textarea(label="Numbers (comma or space separated)").classes("w-full h-32")
        result_label = ui.label("Enter numbers and click Compute").classes("mt-4 text-lg")
    
        def reset() -> None:
            numbers_input.value = ""
            result_label.text = "Enter numbers..."
    
        # Add the Reset button
        ui.button("Reset", on_click=reset).classes("mt-2 ml-4 bg-red-500 text-white")
    
  5. Verify it works locally (uv run fastapi dev).

  6. Stage and commit changes with the message: feat: add reset button.
  7. Push branch to GitHub.

9.4 Create Pull Requests

  1. Member A: Create a Pull Request for feature/average-calc -> main.
  2. Member B: Create a Pull Request for feature/reset-button -> main.

[!NOTE] Fill out the PR template with a clear description of your changes and link the relevant issue.

9.5 Review and Merge (Conflict Phase)

Step 1: Merge Member A's PR 1. Member B reviews Member A's PR. 2. Member B approves the PR. 3. Member B merges the PR. 4. Member B deletes the branch.

[!NOTE] Wait for the merge to complete and the main branch to update before proceeding to the next step.

Step 2: Member B's PR (Conflict!) 1. Member A goes to review Member B's PR. 2. You will likely see a message: "This branch has conflicts that must be resolved". - This happens because both of you modified the home() function in app/main.py independently.

Step 3: Resolve Conflict

You have three options to resolve this conflict. Choose ONE method:

Option A: Using GitHub UI (Fastest)

  1. Member A clicks the Resolve conflicts button on the PR page.
  2. GitHub will show the conflicting file.
  3. Goal: Combine both features (Compute and Reset).
  4. Manually edit the file in the browser:

    • Remove conflict markers (<<<<<<<, =======, >>>>>>>).
    • Keep Member A's compute function and imports.
    • Keep Member B's reset function.
    • Keep BOTH buttons.
    • Remove duplicate definitions of input fields.
    • CRITICAL: Be very careful with indentation (4 spaces). Python will crash if indentation is wrong!

    🆘 Stuck? Click here to see the Solution Code

    (See solution code below in Option B)

  5. Click Mark as resolved.

  6. Click Commit merge.

This method allows you to run tests before committing, ensuring you didn't break the code.

  1. Member A pulls the latest changes to their local machine:

    git pull origin main
    
    Note: This pulls Member B's changes into your local branch, triggering the conflict locally.

  2. VS Code will highlight the conflicting file (app/main.py) in red.

  3. Open app/main.py.
  4. Use the Merge Editor (Click "Resolve in Merge Editor" button if visible) OR look for the "Accept Current", "Accept Incoming", "Accept Both" links above the conflict.
  5. Choose Accept Both Changes (or manually edit to keep both logic parts).
  6. Verify: Run the tests to make sure you didn't break anything!
    uv run pytest
    
  7. Commit and push:
    git add app/main.py
    git commit -m "merge: resolve conflicts between average and reset features"
    git push origin feature/reset-button
    

Option C: Using CLI (Advanced)

  1. git pull origin main
  2. Edit app/main.py to remove markers.
  3. uv run pytest
  4. git add app/main.py
  5. git commit -m "merge: resolve conflicts"
  6. git push

Solution Code (Reference for all options)

If you get stuck, here is how the final app/main.py should look:

🆘 Stuck? Click here to see the Solution Code Here is how the resolved code should look:
@ui.page('/')
def home() -> None:
    ui.label("CSE120 GitHub Workshop").classes("text-2xl font-bold")
    ui.label("Average Calculator").classes("text-lg text-gray-600 mb-4")

    # Shared input definitions
    numbers_input = ui.textarea(label="Numbers (comma or space separated)").classes("w-full h-32")
    result_label = ui.label("Enter numbers and click Compute").classes("mt-4 text-lg")

    # Member B's Reset Logic
    def reset() -> None:
        numbers_input.value = ""
        result_label.text = "Enter numbers..."

    # Member A's Compute Logic
    def compute() -> None:
        result_label.text = parse_and_average(numbers_input.value)

    # Combined Buttons
    ui.button("Compute Average", on_click=compute).classes("mt-2")
    ui.separator()
    ui.button("Reset", on_click=reset).classes("mt-2 ml-4 bg-red-500 text-white")
  1. Click Mark as resolved.
  2. Click Commit merge.

Step 4: Merge Member B's PR 1. Go back to the PR on GitHub. 2. The conflict message should be gone. 3. Member A approves the PR. 4. Member A merges the PR.

9.6 Bug Discovery and Fix

Now that both features are merged, pull the latest main branch locally.

  1. Member B: Pull the latest code locally.
  2. Member B: Test the application (uv run fastapi dev).
    • Try entering non-numeric values (e.g., 1, a, 3).
    • Result: The app crashes! (ValueError).
  3. Member B: Create an Bug Report Issue on GitHub titled "Fix crash on non-numeric input".
  4. Member B: Create a branch patch/input-validation locally.
  5. Member B: Update the parse_and_average function in app/main.py to handle errors:

    def parse_and_average(raw: str | None) -> str:
        raw = raw or ""
        tokens = [t for part in raw.split(",") for t in part.strip().split()] if raw else []
    
        if not tokens:
            return "Error: no values provided"
    
        try:
            values = [float(t) for t in tokens if t]
        except ValueError:
            return "Error: non-numeric value detected"
    
        if not values:
            return "Error: no valid numeric values"
    
        return f"Average: {mean(values):.4f}"
    
  6. Member B: Commit and Push.

  7. Member B: Create PR.
  8. Member A: Review and Merge.

9.7 Conclusion

You have successfully: 1. Developed features in parallel. 2. Resolved a merge conflict. 3. Fixed a bug in collaborative code.

Proceed to Step 10: Deploy to Render.