Skip to content

Commit

Permalink
feat: impl feature flags
Browse files Browse the repository at this point in the history
* feat: add timzone dropdown to system admin initial change password form

* refactor: added timezone to initial Login

* fix: lining

* refactor: removed nested components from the ResourceManagement.tsx (#500)

* feat: create feature access level checkboxes (#501)

* refactor: removed nested components from the ResourceManagement.tsx

* fix: made requested changes to PR

* refactor: added FeatureLevelCheckboxes

* fix: add frontend validation to user creation form to allow spaces in names (#502)

* fix: added_frontend_validation

* fix: allow numbers in username

---------

Co-authored-by: PThorpe92 <preston@unlockedlabs.org>

* feat: impl import activity for course for BrightspaceService

* fix: move drop and create statements to proper locations

* feat: add logic for the integration of brightspace activities for courses and add logic to SQL function for handling total times and time deltas specific to brightspace

* fix: modify Error check to be RowsAffected check

* feat: feature flag and create system_admin role

* feat: finish feature flagging

* fix: github action to restart deployments over ssh, fix migrations

---------

Co-authored-by: Cory Pride <cory.pride83@gmail.com>
Co-authored-by: Anthony Miller Sr. <155587006+MillerSenior@users.noreply.github.com>
Co-authored-by: Richard Salas <rs.timothyjr@gmail.com>
  • Loading branch information
4 people authored Nov 19, 2024
1 parent 7bd67b1 commit c2d5d1f
Show file tree
Hide file tree
Showing 68 changed files with 1,190 additions and 848 deletions.
161 changes: 75 additions & 86 deletions .github/workflows/container_builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,35 @@ jobs:
if: github.repository == 'UnlockedLabs/UnlockEdv2' || github.repository == 'PThorpe92/UnlockEdv2'
runs-on: ubuntu-latest
outputs:
frontend_changes: ${{ steps.frontend_changes.outputs.changed }}
backend_changes: ${{ steps.backend_changes.outputs.changed }}
middleware_changes: ${{ steps.middleware_changes.outputs.changed }}
crontasks_changes: ${{ steps.crontasks_changes.outputs.changed }}
changes: ${{ steps.check-changes.outputs.changes }}

steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 3

- id: frontend_changes
- id: check-changes
run: |
echo "changed=$(git diff --name-only HEAD~1 | grep '^frontend/' | wc -l)" >> $GITHUB_OUTPUT
- id: backend_changes
run: |
echo "changed=$(git diff --name-only HEAD~1 | grep '^backend/' | wc -l)" >> $GITHUB_OUTPUT
- id: middleware_changes
run: |
echo "changed=$(git diff --name-only HEAD~1 | grep '^provider-middleware/' | wc -l)" >> $GITHUB_OUTPUT
- id: crontasks_changes
run: |
echo "changed=$(git diff --name-only HEAD~1 | grep '^backend/tasks' | wc -l)" >> $GITHUB_OUTPUT
paths=("frontend/" "backend/" "provider-middleware/" "backend/tasks")
changes=""
for path in "${paths[@]}"; do
count=$(git diff --name-only HEAD~1 | grep "^${path}" | wc -l)
changes+="${path}:${count},"
done
echo "changes=${changes}" >> $GITHUB_OUTPUT
- name: Debug outputs
run: |
echo "Frontend changes: ${{ steps.frontend_changes.outputs.changed }}"
echo "Backend changes: ${{ steps.backend_changes.outputs.changed }}"
echo "Middleware changes: ${{ steps.middleware_changes.outputs.changed }}"
echo "Crontasks changes: ${{ steps.crontasks_changes.outputs.changed }}"
- name: Debug changes
run: echo "${{ steps.check-changes.outputs.changes }}"

build-and-push:
if: github.repository == 'UnlockedLabs/UnlockEdv2' || github.repository == 'PThorpe92/UnlockEdv2'
needs: setup-env
runs-on: ubuntu-latest
env:
CHANGES: ${{ needs.setup-env.outputs.changes }}
outputs:
deployments: ${{ steps.determine-deployments.outputs.deployments }}

steps:
- name: Checkout code
Expand All @@ -68,68 +62,63 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build and push frontend image
if: ${{ needs.setup-env.outputs.frontend_changes != '0' }} && github.ref == 'refs/heads/main'
run: |
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/frontend:latest --push frontend/.
- name: Build and push backend image
if: ${{ needs.setup-env.outputs.backend_changes != '0' }} && github.ref == 'refs/heads/main'
run: |
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/unlockedv2:latest --push -f backend/Dockerfile .
- name: Build and push middleware image
if: ${{ needs.setup-env.outputs.middleware_changes != '0' }} && github.ref == 'refs/heads/main'
run: |
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/provider_middleware:latest --push -f provider-middleware/Dockerfile .
- name: Build and push crontasks image
if: ${{ needs.setup-env.outputs.crontasks_changes != '0' }} && github.ref == 'refs/heads/main'
run: |
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/cron_tasks:latest --push -f backend/tasks/Dockerfile .
- name: Set up kubectl
uses: azure/setup-kubectl@v1
with:
version: v1.21.0

- name: Set up Kubeconfig Staging
if: github.ref == 'refs/heads/main'
run: |
mkdir -p $HOME/.kube && touch $HOME/.kube/config
echo "${{ secrets.KUBECTL_CONFIG }}" | base64 -d > $HOME/.kube/config
- name: Set up Kubeconfig Demo
if: github.ref == 'refs/heads/demo'
run: |
mkdir -p $HOME/.kube && touch $HOME/.kube/config
echo "${{ secrets.KUBECTL_CONFIG_DEMO }}" | base64 -d > $HOME/.kube/config
- name: Create k8s ECR secret
run: |
kubectl delete secret ecr-secret --ignore-not-found
kubectl create secret docker-registry ecr-secret \
--docker-server=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-west-2.amazonaws.com \
--docker-username=AWS \
--docker-password=$(aws ecr get-login-password --region us-west-2) \
--docker-email=none
- name: restart frontend
if: ${{ needs.setup-env.outputs.frontend_changes != '0' }}
run: |
kubectl rollout restart deployment frontend
- name: restart server
if: ${{ needs.setup-env.outputs.backend_changes != '0' }}
run: |
kubectl rollout restart deployment server
- name: restart provider_middleware
if: ${{ needs.setup-env.outputs.middleware_changes != '0' }}
run: |
kubectl rollout restart deployment provider-service
- name: restart cron_tasks
if: ${{ needs.setup-env.outputs.crontasks_changes != '0' }}
run: |
kubectl rollout restart deployment cron-tasks
- id: build-images
run: |
deployments=()
for entry in ${{ needs.setup-env.outputs.changes }}; do
path=$(echo $entry | cut -d':' -f1)
count=$(echo $entry | cut -d':' -f2)
if [[ $count -ne 0 ]]; then
case $path in
"frontend/")
echo "Building frontend image"
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/frontend:latest --push frontend/.
deployments+=("frontend")
;;
"backend/")
echo "Building backend image"
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/unlockedv2:latest --push -f backend/Dockerfile .
deployments+=("server")
;;
"provider-middleware/")
echo "Building middleware image"
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/provider_middleware:latest --push -f provider-middleware/Dockerfile .
deployments+=("provider-service")
;;
"backend/tasks")
echo "Building scheduler image"
docker buildx build --platform linux/amd64 -t=${{ steps.login-ecr.outputs.registry }}/cron_tasks:latest --push -f backend/tasks/Dockerfile .
deployments+=("cron-tasks")
;;
esac
fi
done
echo "deployments=${deployments[*]}" >> $GITHUB_OUTPUT
restart-deployments:
if: github.repository == 'UnlockedLabs/UnlockEdv2' || github.repository == 'PThorpe92/UnlockEdv2'
needs: build-and-push
runs-on: ubuntu-latest
env:
BASTION_HOST: ${{ secrets.BASTION_HOST }}
SSH_KEY: ${{ secrets.SSH_KEY }}

steps:
- name: Restart Deployments
run: |
deployments="${{ needs.build-and-push.outputs.deployments }}"
if [[ -z "$deployments" ]]; then
echo "No deployments need restarting."
exit 0
fi
if [[ "${GITHUB_REF}" == "refs/heads/demo" ]]; then
CONTEXT="demo"
elif [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
CONTEXT="staging"
else
echo "Unknown branch: ${GITHUB_REF}. No deployments restarted."
exit 1
fi
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "$BASTION_HOST" "bash -s" <<EOF
rollout.sh $CONTEXT $deployments
EOF
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

All notable changes to this project will be documented in this file.

## [0.0.3] - 2024-11-10
## [0.0.4] - 2024-11-15

### 🚀 Features

Expand All @@ -24,12 +24,20 @@ All notable changes to this project will be documented in this file.
- Add facility context switch and ui
- Add toast context to reduce code duplication
- Add facility management
- Brightspace integration (#473)
- Brightspace data model
- Add Programs page to UI and seed program tags (#471)
- Create-department-wide-program-464 (#477)
- Add GetUsers, IntoImportUsers in provider-middleware
- Add video download capabilities
- Add manual retry to video downloads
- Add logic for the integration of brightspace milestones (#483)
- Create new program & mark department-wide (#485)
- Add timzone dropdown to system admin initial change password form
- Create feature access level checkboxes (#501)
- Impl import activity for course for BrightspaceService
- Feature flag and create system_admin role
- Finish feature flagging

### 🐛 Bug Fixes

Expand Down Expand Up @@ -92,6 +100,12 @@ All notable changes to this project will be documented in this file.
- Modify anonymous struct to correctly parse http response
- Resource mgmt detects when no changes happen
- Ensure video key updates and handle UI issues
- Prevent kiwix from downloading/uploading thumbnails if they exist
- Updating users wasn't working correctly and tooltips were clipped (#481)
- Add facility management link back to navbar
- Multiple imports from merge conflict broke build
- Add frontend validation to user creation form to allow spaces in names (#502)
- Github action to use bastion host

### 🚜 Refactor

Expand All @@ -104,6 +118,7 @@ All notable changes to this project will be documented in this file.
- Completed heroIcon component replacement on the studentManagement and AdminManagement pages
- Completed heroIcon component replacement on the ProviderCard.tsx
- Completed heroIcon component replacement on the PageNav.tsx
- Removed nested components from the ResourceManagement.tsx (#500)

### 📚 Documentation

Expand Down
2 changes: 1 addition & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
github.com/teambition/rrule-go v1.8.2
golang.org/x/crypto v0.26.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.9
Expand Down Expand Up @@ -57,6 +56,7 @@ require (
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
Expand Down
28 changes: 14 additions & 14 deletions backend/migrations/00014_make_drop_create_insert_daily_activity.sql
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
-- +goose Up
-- +goose StatementBegin
create function
CREATE OR REPLACE FUNCTION
public.insert_daily_activity(
_user_id integer,
_course_id integer,
_type character varying,
_total_time integer,
_external_id character varying
)
returns void
language plpgsql
as
'
RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE prev_total_time INT;
DECLARE provider_type CHAR ( 100 );
BEGIN
select p.type INTO provider_type from activities a
inner join courses c on c.id = a.course_id
and c.deleted_at IS NULL
inner join provider_platforms p on p.id = c.provider_platform_id
and p.deleted_at IS NULL
where a.user_id = _user_id AND a.course_id = _course_id
SELECT p.type INTO provider_type FROM activities a
INNER JOIN courses c ON c.id = a.course_id
AND c.deleted_at IS NULL
INNER JOIN provider_platforms p ON p.id = c.provider_platform_id
AND p.deleted_at IS NULL
WHERE a.user_id = _user_id AND a.course_id = _course_id
LIMIT 1;
SELECT total_time INTO prev_total_time FROM activities
WHERE user_id = _user_id AND course_id = _course_id
Expand All @@ -30,21 +30,21 @@ as
END IF;
--kolibri total_time is the delta and total_time
--is required to be added to previous_total_time
IF provider_type = ''kolibri'' THEN
IF provider_type = 'kolibri' THEN
INSERT INTO activities (user_id, course_id, type, total_time, time_delta, external_id, created_at, updated_at)
VALUES (_user_id, _course_id, _type, _total_time + prev_total_time, _total_time, _external_id, NOW(), NOW());
ELSE
INSERT INTO activities (user_id, course_id, type, total_time, time_delta, external_id, created_at, updated_at)
VALUES (_user_id, _course_id, _type, _total_time, _total_time - prev_total_time, _external_id, NOW(), NOW());
END IF;
END;
'
$$
;
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
drop function if exists
DROP function IF EXISTS
public.insert_daily_activity(
integer, integer, character varying, integer, character varying
);
Expand Down
30 changes: 15 additions & 15 deletions backend/migrations/00017_drop_create_insert_daily_activity_sql.sql
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
-- +goose Up
-- +goose StatementBegin
create function
CREATE OR REPLACE FUNCTION
public.insert_daily_activity(
_user_id integer,
_course_id integer,
_type character varying,
_total_time integer,
_external_id character varying
)
returns void
language plpgsql
RETURNS void
LANGUAGE plpgsql
as
'
$$
DECLARE prev_total_time INT;
DECLARE prev_delta_time INT;
DECLARE provider_type CHAR ( 100 );
BEGIN
select p.type INTO provider_type from activities a
inner join courses c on c.id = a.course_id
and c.deleted_at IS NULL
inner join provider_platforms p on p.id = c.provider_platform_id
and p.deleted_at IS NULL
where a.user_id = _user_id AND a.course_id = _course_id
SELECT p.type INTO provider_type FROM activities a
INNER JOIN courses c ON c.id = a.course_id
AND c.deleted_at IS NULL
INNER JOIN provider_platforms p ON p.id = c.provider_platform_id
AND p.deleted_at IS NULL
WHERE a.user_id = _user_id AND a.course_id = _course_id
LIMIT 1;
SELECT total_time INTO prev_total_time FROM activities
WHERE user_id = _user_id AND course_id = _course_id
Expand All @@ -33,10 +33,10 @@ as
--brightspace previous time_delta is needed for recalulating actual
--total time since brightspace is accumulating time spent on
--possibly the same content until student has completed content
IF prev_total_time > 0 AND provider_type = ''brightspace'' THEN
IF prev_total_time > 0 AND provider_type = 'brightspace' THEN
SELECT time_delta INTO prev_delta_time FROM activities
WHERE user_id = _user_id AND course_id = _course_id
AND SUBSTRING(external_id FROM ''^[^-]+'') = SUBSTRING(_external_id FROM ''^[^-]+'')
AND SUBSTRING(external_id FROM '^[^-]+') = SUBSTRING(_external_id FROM '^[^-]+')
ORDER BY created_at DESC LIMIT 1;
END IF;

Expand All @@ -45,18 +45,18 @@ as
END IF;
--kolibri total_time is the delta and total_time
--is required to be added to previous_total_time
IF provider_type = ''kolibri'' THEN
IF provider_type = 'kolibri' THEN
INSERT INTO activities (user_id, course_id, type, total_time, time_delta, external_id, created_at, updated_at)
VALUES (_user_id, _course_id, _type, _total_time + prev_total_time, _total_time, _external_id, NOW(), NOW());
ELSEIF provider_type = ''brightspace'' THEN
ELSEIF provider_type = 'brightspace' THEN
INSERT INTO activities (user_id, course_id, type, total_time, time_delta, external_id, created_at, updated_at)
VALUES (_user_id, _course_id, _type, (_total_time-prev_delta_time)+prev_total_time, _total_time-prev_delta_time, _external_id, NOW(), NOW());
ELSE
INSERT INTO activities (user_id, course_id, type, total_time, time_delta, external_id, created_at, updated_at)
VALUES (_user_id, _course_id, _type, _total_time, _total_time - prev_total_time, _external_id, NOW(), NOW());
END IF;
END;
'
$$
;
-- +goose StatementEnd

Expand Down
Loading

0 comments on commit c2d5d1f

Please sign in to comment.