From f9975d890096a84f4730f231b5326373f4dcd9db Mon Sep 17 00:00:00 2001 From: Aaron Williams Date: Fri, 17 Nov 2023 15:56:54 +0000 Subject: [PATCH 01/24] Updating manifest * Adding uat and production values to copilot config +ref: FS-3780 +semver: minor --- copilot/fsd-assessment/manifest.yml | 31 +++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/copilot/fsd-assessment/manifest.yml b/copilot/fsd-assessment/manifest.yml index 72416455..baac96fe 100644 --- a/copilot/fsd-assessment/manifest.yml +++ b/copilot/fsd-assessment/manifest.yml @@ -50,6 +50,7 @@ variables: ASSESSMENT_STORE_API_HOST: "http://fsd-assessment-store:8080" FUND_STORE_API_HOST: "http://fsd-fund-store:8080" ACCOUNT_STORE_API_HOST: "http://fsd-account-store:8080" + APPLICATION_STORE_API_HOST: "http://fsd-application-store:8080" COPILOT_AWS_BUCKET_NAME: from_cfn: ${COPILOT_APPLICATION_NAME}-${COPILOT_ENVIRONMENT_NAME}-FormUploadsBucket REDIS_INSTANCE_URI: @@ -61,12 +62,30 @@ secrets: # You can override any of the values defined above by environment. environments: - # uat: - # http: - # alias: assessment.uat.access-funding.test.levellingup.gov.uk - # production: - # http: - # alias: assessment.prod.access-funding.levellingup.gov.uk test: deployment: rolling: 'recreate' + uat: + count: + range: 2-4 + cooldown: + in: 60s + out: 30s + cpu_percentage: + value: 70 + memory_percentage: + value: 80 + requests: 30 + response_time: 2s + production: + alias: assessment.production.access-funding.levellingup.gov.uk + count: + range: 2-4 + cooldown: + in: 60s + out: 30s + cpu_percentage: + value: 70 + memory_percentage: + value: 80 + requests: 30 From e6c2428dd2ed6abe365aceba62406a2725c9b33d Mon Sep 17 00:00:00 2001 From: Robert Kibble Date: Mon, 20 Nov 2023 12:46:23 +0000 Subject: [PATCH 02/24] FS-3780 Confirm env in copilot --- .github/workflows/copilot_deploy.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/copilot_deploy.yml b/.github/workflows/copilot_deploy.yml index 3d190280..9d4a66c9 100644 --- a/.github/workflows/copilot_deploy.yml +++ b/.github/workflows/copilot_deploy.yml @@ -115,6 +115,14 @@ jobs: run: | curl -Lo aws-copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux && chmod +x aws-copilot && sudo mv aws-copilot /usr/local/bin/copilot + - name: confirm copilot env + shell: bash + run: | + if [ $(copilot env ls) != "${{ matrix.value }}" ]; then + echo $(copilot env ls) + exit 1 + fi + - name: Inject Git SHA into manifest run: | yq -i '.variables.GITHUB_SHA = "${{ github.sha }}"' copilot/fsd-assessment/manifest.yml From 4c9f5cab335a3b50d495ec197167cf0ce5b7825f Mon Sep 17 00:00:00 2001 From: Aaron Williams Date: Wed, 22 Nov 2023 11:59:17 +0000 Subject: [PATCH 03/24] Update CPU/RAM values * Increasing CPU to allow for spikes seen during testing +ref: BAU +semver: minor --- copilot/fsd-assessment/manifest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/copilot/fsd-assessment/manifest.yml b/copilot/fsd-assessment/manifest.yml index baac96fe..68e24ebc 100644 --- a/copilot/fsd-assessment/manifest.yml +++ b/copilot/fsd-assessment/manifest.yml @@ -24,9 +24,9 @@ image: # Valid values: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html # Number of CPU units for the task. -cpu: 512 +cpu: 1024 # Amount of memory in MiB used by the task. -memory: 1024 +memory: 2048 # See https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#platform platform: linux/x86_64 From 87cb9b9dce6400deb992425dd29f7bbd6961bbb6 Mon Sep 17 00:00:00 2001 From: Aaron Williams Date: Wed, 22 Nov 2023 12:01:35 +0000 Subject: [PATCH 04/24] Fix pre-deploy workflow * Remove E2E PAT as its no longer required +ref: BAU +semver: minor --- .github/workflows/copilot_deploy.yml | 2 -- copilot/fsd-assessment/manifest.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/copilot_deploy.yml b/.github/workflows/copilot_deploy.yml index 9d4a66c9..3e969557 100644 --- a/.github/workflows/copilot_deploy.yml +++ b/.github/workflows/copilot_deploy.yml @@ -56,8 +56,6 @@ jobs: assets_required: true pre_deploy_tests: - secrets: - E2E_PAT: ${{secrets.E2E_PAT}} uses: communitiesuk/funding-design-service-workflows/.github/workflows/pre-deploy.yml@main with: assets_required: true diff --git a/copilot/fsd-assessment/manifest.yml b/copilot/fsd-assessment/manifest.yml index 68e24ebc..8afc1772 100644 --- a/copilot/fsd-assessment/manifest.yml +++ b/copilot/fsd-assessment/manifest.yml @@ -31,7 +31,7 @@ memory: 2048 # See https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#platform platform: linux/x86_64 # Number of tasks that should be running in your service. -count: 1 +count: 2 # Enable running commands in your container. exec: true From b675e7c6e13b7e1eed5482ed7c541d512c266d22 Mon Sep 17 00:00:00 2001 From: Aaron Williams Date: Mon, 27 Nov 2023 15:27:39 +0000 Subject: [PATCH 05/24] Replacing Secrets * Removing unused E2E Personal Access Token * Replacing with github app organisational access token +ref: FS-3826 +semver: minor --- .github/workflows/deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e8cfe0b9..0e23999d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,5 +22,6 @@ jobs: CF_SPACE: ${{secrets.CF_SPACE}} CF_USER: ${{secrets.CF_USERNAME}} CF_PASSWORD: ${{secrets.CF_PASSWORD}} - E2E_PAT: ${{secrets.E2E_PAT}} RSA256_PUBLIC_KEY_BASE64: ${{secrets.RSA256_PUBLIC_KEY_BASE64}} + FSD_GH_APP_ID: ${{ secrets.FSD_GH_APP_ID }} + FSD_GH_APP_KEY: ${{ secrets.FSD_GH_APP_KEY }} From 6807530c4506362ff667753030e99931f3195212 Mon Sep 17 00:00:00 2001 From: Thomas <117724519+tferns@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:31:13 +0000 Subject: [PATCH 06/24] FS-3752: Hide total funding requested for DPIF (#504) --- .../templates/assessor_tasklist.html | 1 + app/templates/components/header.html | 1 + app/templates/macros/banner_summary.html | 7 ++-- tests/test_jinja_macros.py | 34 +++++++++++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/blueprints/assessments/templates/assessor_tasklist.html b/app/blueprints/assessments/templates/assessor_tasklist.html index aa2c889f..34f637a6 100644 --- a/app/blueprints/assessments/templates/assessor_tasklist.html +++ b/app/blueprints/assessments/templates/assessor_tasklist.html @@ -29,6 +29,7 @@ {{ banner_summary( state.fund_name, + state.fund_short_name, state.short_id, state.project_name, state.funding_amount_requested, diff --git a/app/templates/components/header.html b/app/templates/components/header.html index 541361d9..54e3c687 100644 --- a/app/templates/components/header.html +++ b/app/templates/components/header.html @@ -14,6 +14,7 @@ {{ banner_summary( (state.fund_name if state else fund.name), + state.fund_short_name, sub_criteria.short_id if sub_criteria else state.short_id, sub_criteria.project_name if sub_criteria else state.project_name, sub_criteria.funding_amount_requested if sub_criteria else state.funding_amount_requested, diff --git a/app/templates/macros/banner_summary.html b/app/templates/macros/banner_summary.html index bfac0f64..44791d9d 100644 --- a/app/templates/macros/banner_summary.html +++ b/app/templates/macros/banner_summary.html @@ -1,4 +1,4 @@ -{% macro banner_summary(fund_name, project_reference, project_name, funding_amount_requested, assessment_status, flag_status) %} +{% macro banner_summary(fund_name, fund_shortname, project_reference, project_name, funding_amount_requested, assessment_status, flag_status) %}
@@ -7,7 +7,10 @@

Fund: {{ fund_name }}

Project reference: {{ project_reference | format_project_ref }}

Project name: {{ project_name }}

-

Total funding requested: £{{ "{:,.2f}".format(funding_amount_requested | float) }}

+ + {% if fund_shortname not in ("DPIF",) %} {# Funds that should hide total funding requested. #} +

Total funding requested: £{{ "{:,.2f}".format(funding_amount_requested | float) }}

+ {% endif %} {% if g.access_controller.has_any_assessor_role %}

{{ assessment_status }} diff --git a/tests/test_jinja_macros.py b/tests/test_jinja_macros.py index 7660490c..ff3f68b2 100644 --- a/tests/test_jinja_macros.py +++ b/tests/test_jinja_macros.py @@ -630,7 +630,16 @@ def test_theme_mapping_works_based_on_meta_key( assert expected_unique_id in rendered_html, "Unique ID not found" - def test_banner_summary_macro(self, request_ctx): + @pytest.mark.parametrize( + "fund_short_name, show_funding_amount_requested", + [ + ("TFID", True), + ("DPIF", False), + ], + ) + def test_banner_summary_macro( + self, request_ctx, fund_short_name, show_funding_amount_requested + ): fund_name = "Test Fund" project_reference = "TEST123" project_name = "Test Project" @@ -639,12 +648,14 @@ def test_banner_summary_macro(self, request_ctx): flag_status = "Flagged" rendered_html = render_template_string( - "{{ banner_summary(fund_name, project_reference, project_name," - " funding_amount_requested, assessment_status, flag_status) }}", + "{{ banner_summary(fund_name, fund_short_name, project_reference," + " project_name, funding_amount_requested, assessment_status," + " flag_status) }}", banner_summary=get_template_attribute( "macros/banner_summary.html", "banner_summary" ), fund_name=fund_name, + fund_short_name=fund_short_name, project_reference=project_reference, project_name=project_name, funding_amount_requested=funding_amount_requested, @@ -674,11 +685,17 @@ def test_banner_summary_macro(self, request_ctx): ), text="Project name: Test Project", ), "Project name not found" - assert soup.find( + + funding_amount_requested_element = soup.find( "p", class_="govuk-body-l fsd-banner-content", text="Total funding requested: £123,456.78", - ), "Funding amount not found" + ) + if show_funding_amount_requested: + assert funding_amount_requested_element, "Funding amount not found" + else: + assert not funding_amount_requested_element, "Funding amount found" + assert soup.find( "strong", class_="govuk-tag", @@ -690,6 +707,7 @@ def test_banner_summary_macro(self, request_ctx): def test_stopped_flag_macro(self, request_ctx): fund_name = "Test Fund" + fund_short_name = "TFID" project_reference = "TEST123" project_name = "Test Project" funding_amount_requested = 123456.78 @@ -697,12 +715,14 @@ def test_stopped_flag_macro(self, request_ctx): flag_status = "Stopped" rendered_html = render_template_string( - "{{ banner_summary(fund_name, project_reference, project_name," - " funding_amount_requested, assessment_status, flag_status) }}", + "{{ banner_summary(fund_name, fund_short_name, project_reference," + " project_name, funding_amount_requested, assessment_status," + " flag_status) }}", banner_summary=get_template_attribute( "macros/banner_summary.html", "banner_summary" ), fund_name=fund_name, + fund_short_name=fund_short_name, project_reference=project_reference, project_name=project_name, funding_amount_requested=funding_amount_requested, From 6e5b37cdd07654ec3c70cbe52e8498f525144638 Mon Sep 17 00:00:00 2001 From: Ram <80714392+ramsharma-prog@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:29:24 +0000 Subject: [PATCH 07/24] FS-3685-view-assessment-activity-history-for-an-application (#488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added template activity_trail.html * ✨ updated Associated Tags mechanism to store all activities on application level * add empty single tag_id with user account_id if want to dis-associate the tag(s) * added mechanism to display application-related activities in descending order. * Enhanced comments history and scores links for sub criteria: - Expanded activity trail to include both original and edited comments - scores and comments links to target sub_criteria_ids * ✅ Added unit tests * added link takes to theme_id for comments * display most recent associated tags on assessment_dashboard * bug-fix: asessment_status set to None if not defined --- app/blueprints/assessments/activity_trail.py | 290 ++++++++++++++++++ app/blueprints/assessments/routes.py | 69 +++++ .../assessments/templates/activity_trail.html | 168 ++++++++++ .../templates/assessor_tasklist.html | 18 +- .../templates/macros/assessment_flag.html | 89 +++++- .../templates/macros/comments.html | 17 + .../templates/macros/tags_table.html | 20 ++ .../macros/scores_justification.html | 23 ++ app/blueprints/services/data_services.py | 18 +- app/blueprints/shared/filters.py | 1 + app/blueprints/tagging/models/tag.py | 1 + app/blueprints/tagging/routes.py | 25 +- app/templates/macros/banner_summary.html | 5 +- config/envs/default.py | 3 + tests/test_activity_trail.py | 82 +++++ 15 files changed, 808 insertions(+), 21 deletions(-) create mode 100644 app/blueprints/assessments/activity_trail.py create mode 100644 app/blueprints/assessments/templates/activity_trail.html create mode 100644 tests/test_activity_trail.py diff --git a/app/blueprints/assessments/activity_trail.py b/app/blueprints/assessments/activity_trail.py new file mode 100644 index 00000000..09bc81f3 --- /dev/null +++ b/app/blueprints/assessments/activity_trail.py @@ -0,0 +1,290 @@ +import inspect +from dataclasses import asdict +from dataclasses import dataclass +from dataclasses import field +from datetime import datetime + +from app.blueprints.services.data_services import get_bulk_accounts_dict +from app.blueprints.services.models.assessor_task_list import AssessorTaskList +from app.blueprints.services.models.flag import FlagType +from app.blueprints.tagging.models.tag import AssociatedTag +from flask import current_app +from flask_wtf import FlaskForm +from wtforms import BooleanField +from wtforms import StringField + + +@dataclass +class BaseModel: + @classmethod + def from_list(cls, data_list: list[dict]): + return [ + cls( + **{ + k: v + for k, v in d.items() + if k in inspect.signature(cls).parameters + and k != "date_created" + }, + date_created=cls._format_date(d.get("date_created")), + ) + for d in data_list + ] + + @staticmethod + def _format_date(date_str): + if date_str: + return datetime.fromisoformat(date_str).strftime( + "%Y-%m-%d %H:%M:%S" + ) + return date_str + + +@dataclass +class AssociatedTags(AssociatedTag): + full_name: str = "" + email_address: str = "" + highest_role: str = "" + email_address: str = "" + date_created: str = field(default="", metadata={"name": "created_at"}) + + @classmethod + def from_associated_tags_list(cls, associated_tags_list): + """Change the attribute 'created_at' to 'date_created'""" + return [ + cls(**asdict(tag), date_created=tag.created_at) + for tag in associated_tags_list + ] + + +@dataclass +class Flags(BaseModel): + allocation: str + date_created: str + id: str + justification: str + status: FlagType + user_id: str + sections_to_flag: list = "" + full_name: str = "" + email_address: str = "" + highest_role: str = "" + + @classmethod + def process_flags(cls, flags_list) -> list[dict]: + """ + Retrive all flags from updates & add sections_to_flag to RAISED flag. + + Args: flags_list (list): A list of flags. + + Returns: + list[dict]: A list of dictionaries containing flags with additional attribute. + """ + result = [] + + for flag in flags_list: + for update in flag.updates: + if update.get("status").name == "RAISED": + sections_to_flag = flag.sections_to_flag + + updated_update = { + **update, + "sections_to_flag": sections_to_flag, + } + result.append(updated_update) + else: + result.append(update) + return result + + @classmethod + def from_list(cls, flags_list: list[dict]): + if flags_list: + flags = cls.process_flags(flags_list) + return super().from_list(flags) + else: + return [] + + +@dataclass +class Comments(BaseModel): + id: str + comment: str + comment_type: str + date_created: str + sub_criteria_id: str + theme_id: str + user_id: str + full_name: str = "" + highest_role: str = "" + email_address: str = "" + + @classmethod + def process_comments(cls, comments_list: list) -> list[dict]: + """ + Retrieve all comments from updates and add the following attributes to each comment: + - "user_id" + - "sub_criteria_id" + - "theme_id" + - "comment_type" + - "full_name" + - "email_address" + - "highest_role" + + Args: comments_list (list): A list of comments. + + Returns: + list[dict]: A list of dictionaries containing comments with additional attributes. + """ + result = [] + + for comment in comments_list: + for update in comment.get("updates"): + updated_update = { + **update, + "user_id": comment.get("user_id"), + "sub_criteria_id": comment.get("sub_criteria_id"), + "theme_id": comment.get("theme_id"), + "comment_type": comment.get("comment_type"), + "full_name": comment.get("full_name"), + "email_address": comment.get("email_address"), + "highest_role": comment.get("highest_role"), + } + result.append(updated_update) + + return result + + @classmethod + def from_list(cls, comments_list: list[dict]): + if comments_list: + comments = cls.process_comments(comments_list) + return super().from_list(comments) + else: + return [] + + +@dataclass +class Scores(BaseModel): + application_id: str + date_created: str + id: str + justification: str + score: int + sub_criteria_id: str + user_id: str + full_name: str = "" + highest_role: str = "" + email_address: str = "" + + +def get_user_info(lst: list, state: AssessorTaskList) -> dict: + + if lst is None: + return [] + + user_ids = {item.user_id for item in lst if item.user_id} + + if user_ids: + current_app.logger.info("Retrieving account information") + account_list = get_bulk_accounts_dict( + list(user_ids), + state.fund_short_name, + ) + return account_list + else: + return [] + + +def add_user_info(list_data: list, state) -> list: + account_info = get_user_info(list_data, state) + if list_data is None: + return [] + + for item in list_data: + user_id = item.user_id + + if user_id in account_info: + user_data = account_info[user_id] + current_app.logger.info("Adding account information.") + for key, value in user_data.items(): + setattr(item, key, value) + return list_data + + +def order_by_dates(lst: list) -> tuple: + """Sorts a list of items by 'date_created' in descending order""" + + return sorted(lst, key=lambda item: item.date_created, reverse=True) + + +class SearchForm(FlaskForm): + search = StringField("Search") + + +class CheckboxForm(FlaskForm): + all_activity = BooleanField("All activity", default=True) + comments = BooleanField("Comments") + assessment_status = BooleanField("Assessment status") + score = BooleanField("Score") + flag = BooleanField("Flags") + tag = BooleanField("Tags") + + +def map_activities_classes_with_checkbox_filters(filters): + filters = [f.replace(" ", "") for f in filters if f != "All activity"] + _filters = [] + for filter in filters: + if filter == "Score": + _filters.append("Scores") + if filter == "Tags": + _filters.append("AssociatedTags") + else: + _filters.append(filter) + + return _filters + + +def filter_all_activities( + all_activities: list, search_keyword: str = "", filters: list = None +): + all_activities = order_by_dates(all_activities) + filtered_classes = map_activities_classes_with_checkbox_filters(filters) + + if not search_keyword and not filtered_classes: + return all_activities + + if search_keyword and filtered_classes: + return [ + activity + for activity in all_activities + if any( + search_keyword.lower() in str(attr).lower() + for attr in asdict(activity).values() + ) + and any( + class_name.lower() == activity.__class__.__name__.lower() + for class_name in filtered_classes + ) + ] + + if not search_keyword and filters: + return [ + activity + for activity in all_activities + if any( + class_name.lower() == activity.__class__.__name__.lower() + for class_name in filtered_classes + ) + ] + + if search_keyword and not filtered_classes: + return [ + activity + for activity in all_activities + if any( + search_keyword.lower() in str(attr).lower() + for attr in asdict(activity).values() + ) + ] + + else: + return all_activities diff --git a/app/blueprints/assessments/routes.py b/app/blueprints/assessments/routes.py index fe99f40d..b732da23 100644 --- a/app/blueprints/assessments/routes.py +++ b/app/blueprints/assessments/routes.py @@ -6,6 +6,16 @@ from urllib.parse import quote_plus from urllib.parse import unquote_plus +from app.blueprints.assessments.activity_trail import ( + add_user_info, +) +from app.blueprints.assessments.activity_trail import AssociatedTags +from app.blueprints.assessments.activity_trail import CheckboxForm +from app.blueprints.assessments.activity_trail import Comments +from app.blueprints.assessments.activity_trail import filter_all_activities +from app.blueprints.assessments.activity_trail import Flags +from app.blueprints.assessments.activity_trail import Scores +from app.blueprints.assessments.activity_trail import SearchForm from app.blueprints.assessments.forms.assessment_form import ( AssessmentCompleteForm, ) @@ -54,6 +64,9 @@ ) from app.blueprints.scoring.helpers import get_scoring_class from app.blueprints.services.aws import get_file_for_download_from_aws +from app.blueprints.services.data_services import ( + get_all_associated_tags_for_application, +) from app.blueprints.services.data_services import ( get_all_uploaded_documents_theme_answers, ) @@ -77,6 +90,7 @@ from app.blueprints.services.data_services import get_funds from app.blueprints.services.data_services import get_qa_complete from app.blueprints.services.data_services import get_round +from app.blueprints.services.data_services import get_score_and_justification from app.blueprints.services.data_services import get_sub_criteria from app.blueprints.services.data_services import ( get_sub_criteria_theme_answers, @@ -704,6 +718,61 @@ def application(application_id): ) +@assessment_bp.route("/activity_trail/", methods=["GET"]) +@check_access_application_id +def activity_trail(application_id: str): + state = get_state_for_tasklist_banner(application_id) + + # There is a better way of doing it by moving + # all activity related logics to an endpoint in the + # assessment store and write up a query to fetch all the information. + + # ALL FLAGS + flags_list = get_flags(application_id) + all_flags = Flags.from_list(flags_list) + + # ALL COMMENTS + comments_list = get_comments(application_id) + all_comments = Comments.from_list(comments_list) + + # ALL SCORES + scores = get_score_and_justification( + application_id=application_id, score_history=True + ) + all_scores = Scores.from_list(scores) + + # ALL TAGS + tags = get_all_associated_tags_for_application(application_id) + all_tags = AssociatedTags.from_associated_tags_list(tags) + + # Add search box and checkbox filters + available_filters = ["All activity", "Comments", "Score", "Flags", "Tags"] + search_form = SearchForm(request.form) + checkbox_form = CheckboxForm(request.form) + + # Filter all activities + search_keyword = request.args.get("search") + checkbox_filters = request.args.getlist("filter") + all_activities = all_scores + all_comments + all_tags + all_flags + + update_user_info = add_user_info(all_activities, state) + _all_activities = filter_all_activities( + update_user_info, search_keyword, checkbox_filters + ) + + return render_template( + "activity_trail.html", + application_id=application_id, + state=state, + activities=_all_activities, + search_form=search_form, + checkbox_form=checkbox_form, + available_filters=available_filters, + search_keyword=search_keyword, + checkbox_filters=checkbox_filters, + ) + + @assessment_bp.route( "/assessor_export///", methods=["GET"], diff --git a/app/blueprints/assessments/templates/activity_trail.html b/app/blueprints/assessments/templates/activity_trail.html new file mode 100644 index 00000000..1ff07810 --- /dev/null +++ b/app/blueprints/assessments/templates/activity_trail.html @@ -0,0 +1,168 @@ +{% extends "base.html" %} +{% from "macros/theme.html" import theme %} +{% from "govuk_frontend_jinja/components/label/macro.html" import govukLabel %} +{%- from "govuk_frontend_jinja/components/table/macro.html" import govukTable -%} +{%- from "govuk_frontend_jinja/components/fieldset/macro.html" import govukFieldset -%} +{%- from "govuk_frontend_jinja/components/textarea/macro.html" import govukTextarea -%} +{%- from "govuk_frontend_jinja/components/checkboxes/macro.html" import govukCheckboxes -%} +{%- from "govuk_frontend_jinja/components/radios/macro.html" import govukRadios %} +{%- from 'govuk_frontend_jinja/components/back-link/macro.html' import govukBackLink -%} +{% from "macros/logout_partial.html" import logout_partial %} +{% from "macros/banner_summary.html" import banner_summary %} +{% from "macros/assessment_flag.html" import assessment_flag as assessment_flagged %} +{% from "macros/assessment_flag.html" import assessment_resolve as assessment_resolved %} +{% from "macros/assessment_flag.html" import assessment_stop as assessment_stopped %} +{% from "macros/comments.html" import comments %} +{% from "macros/scores_justification.html" import score as scores %} +{% from "macros/tags_table.html" import tags %} + +{% set assessment_status = assessment_status if assessment_status is defined else None %} +{% block header %} +

+ + + {{ banner_summary( + (state.fund_name if state else fund.name), + sub_criteria.short_id if sub_criteria else state.short_id, + sub_criteria.project_name if sub_criteria else state.project_name, + sub_criteria.funding_amount_requested if sub_criteria else state.funding_amount_requested, + assessment_status, + flag_status, + display_status=False + ) }} +
+{% endblock header %} + + +{% block content %} +{% set tag_color = + 'flagged-tag' if state.workflow_status == 'FLAGGED' + else 'stopped-tag' if state.workflow_status == 'STOPPED' + else 'not-started-tag' if state.workflow_status == 'NOT_STARTED' +%} + +
+
+
+
+

Activity trail

+

Current assessment status: {{state.workflow_status|remove_dashes_underscores_capitalize|title }}

+
+ + +
+ + + + + + +
+ Filters +
+ + +
+ {# Search input #} +
+ + +
+ + {# Checkbox filters #} +
+ {% for filter_option in available_filters %} +
+ + +
+ {% endfor %} +
+ + {# Apply filters button #} +
+ +
+
+
+ +
+
+ + + + + + + + {% for activity in activities %} + + + + + + + {% endfor %} + +
+ Actions ({{ activities|length }})
+
+ {% if activity.__class__.__name__ == 'Flags' %} + {% if activity.status.name == "RAISED" %} + {{assessment_flagged(state, activity, application_id)}} + {% elif activity.status.name == "RESOLVED" %} + {{assessment_resolved(activity, application_id)}} + {% elif activity.status.name == "STOPPED" %} + {{assessment_stopped(activity, application_id)}} + {% endif %} + + {% elif activity.__class__.__name__ == 'Comments' %} + {{comments(activity, application_id)}} + + {% elif activity.__class__.__name__ == 'Scores' %} + {{scores(activity, application_id)}} + + {% elif activity.__class__.__name__ == 'AssociatedTags' %} + {% if activity.purpose =="NEGATIVE" %} + {{tags(activity, "red")}} + {% elif activity.purpose =="POSITIVE" %} + {{tags(activity, "green")}} + {% elif activity.purpose =="GENERAL" %} + {{tags(activity, "blue")}} + {% elif activity.purpose =="ACTION" %} + {{tags(activity, "yellow")}} + {% elif activity.purpose =="PEOPLE" %} + {{tags(activity, "grey")}} + {% endif %} + + {% else %} + {{activity}} + + {% endif %} + +
+
+
+
+ +
+ +
+ + +
+ +{% endblock content %} diff --git a/app/blueprints/assessments/templates/assessor_tasklist.html b/app/blueprints/assessments/templates/assessor_tasklist.html index 34f637a6..4fa7c202 100644 --- a/app/blueprints/assessments/templates/assessor_tasklist.html +++ b/app/blueprints/assessments/templates/assessor_tasklist.html @@ -74,12 +74,20 @@ {% if g.access_controller.has_any_assessor_role %}
-
- {{ flag_tabs(state, application_id, teams_flag_stats, flags_list, accounts_list) }} -
-
+ + + +
+
+ {{ flag_tabs(state, application_id, teams_flag_stats, flags_list, accounts_list) }} +
+ + +
- + {% if is_flaggable and g.access_controller.has_any_assessor_role %} {{ flag_application_button(application_id) }} {% endif %} diff --git a/app/blueprints/assessments/templates/macros/assessment_flag.html b/app/blueprints/assessments/templates/macros/assessment_flag.html index 6a388e1a..a914dbc7 100644 --- a/app/blueprints/assessments/templates/macros/assessment_flag.html +++ b/app/blueprints/assessments/templates/macros/assessment_flag.html @@ -24,6 +24,32 @@

Notification sent

{% endmacro %} +{% macro assessment_resolved(flag, user_info, application_id) %} + +{% endmacro %} + + {% macro assessment_stopped(flag, user_info, application_id) %} {% endmacro %} -{% macro assessment_resolved(flag, user_info, application_id) %} + + {# NOTE: Below macro is only being used activity_trail #} +{% macro assessment_flag(state, flag, application_id) %} +

Flag added to assessment

+ +{% endmacro %} + + + {# NOTE: Below macro is only being used activity_trail #} +{% macro assessment_resolve(flag, application_id) %} +

Flag resolved on assessment

+{% endmacro %} + + + {# NOTE: Below macro is only being used activity_trail #} +{% macro assessment_stop(flag, user_info, application_id) %} +

Flag stopped on assessment

+ {% endmacro %} diff --git a/app/blueprints/assessments/templates/macros/comments.html b/app/blueprints/assessments/templates/macros/comments.html index b4a12ade..68716f1b 100644 --- a/app/blueprints/assessments/templates/macros/comments.html +++ b/app/blueprints/assessments/templates/macros/comments.html @@ -50,3 +50,20 @@

Comments

{% endif %}
{% endmacro %} + +{# This macro is only being used in the activity_trail.html #} +{% macro comments(comment, application_id) %} + +
+

Comment on {{comment.theme_id|remove_dashes_underscores_capitalize}}

+ +
+

{{ comment.comment }}

+

{{ comment.full_name }} ({{ comment.highest_role|all_caps_to_human }}) {{ comment.email_address }}

+

{{ comment.date_created|utc_to_bst }}

+
+ +
+{% endmacro %} diff --git a/app/blueprints/assessments/templates/macros/tags_table.html b/app/blueprints/assessments/templates/macros/tags_table.html index 6fef3e4f..df2722e1 100644 --- a/app/blueprints/assessments/templates/macros/tags_table.html +++ b/app/blueprints/assessments/templates/macros/tags_table.html @@ -26,3 +26,23 @@ {% endif %} {% endmacro %} + +{# This macro is only being used in the activity_trail.html #} +{% macro tags(tag, colour) %} + +{% if tag.associated %} +
+

Tag {{tag.value}} added to assessment

+

Added by {{tag.full_name }} ({{ tag.email_address}}) on {{tag.date_created|utc_to_bst}} +

+
+ +{% else %} +
+

Tag {{tag.value}} removed from assessment

+

Removed by {{tag.full_name }} ({{ tag.email_address}}) on {{tag.date_created|utc_to_bst}} +

+
+{% endif %} + +{% endmacro %} diff --git a/app/blueprints/scoring/templates/macros/scores_justification.html b/app/blueprints/scoring/templates/macros/scores_justification.html index 3b59653d..cb8758c0 100644 --- a/app/blueprints/scoring/templates/macros/scores_justification.html +++ b/app/blueprints/scoring/templates/macros/scores_justification.html @@ -80,3 +80,26 @@

Rescore

{% endmacro %} + +{# This macro is only being used in the activity_trail.html #} +{% macro score(score, application_id) %} +

Scored on subcriteria {{score.sub_criteria_id|remove_dashes_underscores_capitalize}}

+
+

Scored: {{score.score }}

+
+

Rationale: {{ score.justification }}

+

+ + {% if score.full_name %}{{ score.full_name or 'NA' }} {% endif %} + ({{ score.highest_role|all_caps_to_human or 'NA' }}) + + {% if score.email_address %}{{ score.email_address or 'NA' }}{% endif %} +

+ +

Date/time: {{ score.date_created|utc_to_bst }}

+
+
+ +{% endmacro %} diff --git a/app/blueprints/services/data_services.py b/app/blueprints/services/data_services.py index e091b815..368d28b5 100644 --- a/app/blueprints/services/data_services.py +++ b/app/blueprints/services/data_services.py @@ -217,6 +217,21 @@ def get_associated_tags_for_application(application_id) -> List[Tag]: return None +def get_all_associated_tags_for_application(application_id) -> List[Tag]: + endpoint = Config.APPLICATION_ASSOCIATED_ALL_TAGS_ENDPOINT.format( + application_id=application_id + ) + result = get_data(endpoint) + if result: + result = [AssociatedTag.from_dict(item) for item in result] + return result + else: + current_app.logger.info( + f"No associated tags found for application: {application_id}." + ) + return [] + + def update_associated_tags(application_id, tags) -> bool: endpoint = Config.ASSESSMENT_ASSOCIATE_TAGS_ENDPOINT.format( application_id=application_id @@ -230,7 +245,6 @@ def update_associated_tags(application_id, tags) -> bool: f" application_id '{application_id}'" ) response = requests.put(endpoint, json=payload) - was_successful = response.ok if not was_successful: current_app.logger.error( @@ -347,7 +361,7 @@ def get_bulk_accounts_dict(account_ids: List, fund_short_name: str): def get_score_and_justification( - application_id, sub_criteria_id, score_history=True + application_id, sub_criteria_id=None, score_history=True ): score_url = Config.ASSESSMENT_SCORES_ENDPOINT score_params = { diff --git a/app/blueprints/shared/filters.py b/app/blueprints/shared/filters.py index 854cde25..29af3a83 100644 --- a/app/blueprints/shared/filters.py +++ b/app/blueprints/shared/filters.py @@ -29,6 +29,7 @@ def utc_to_bst(value, tz="Europe/London"): "%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S.%f%z", ] for dt_format in dt_formats: try: diff --git a/app/blueprints/tagging/models/tag.py b/app/blueprints/tagging/models/tag.py index 1c01992a..48197e07 100644 --- a/app/blueprints/tagging/models/tag.py +++ b/app/blueprints/tagging/models/tag.py @@ -48,6 +48,7 @@ class AssociatedTag: user_id: str associated: bool purpose: str + created_at: str = "" @classmethod def from_dict(cls, d: dict): diff --git a/app/blueprints/tagging/routes.py b/app/blueprints/tagging/routes.py index 70393b53..8b0a3142 100644 --- a/app/blueprints/tagging/routes.py +++ b/app/blueprints/tagging/routes.py @@ -60,12 +60,30 @@ ) @check_access_application_id(roles_required=["ASSESSOR"]) def load_change_tags(application_id): - tag_association_form = TagAssociationForm() + tag_association_form = TagAssociationForm() if request.method == "POST": + associated_tags = get_associated_tags_for_application(application_id) + association_form_data = tag_association_form.tags.data updated_tags = [] - for tag_id in tag_association_form.tags.data: - updated_tags.append({"tag_id": tag_id, "user_id": g.account_id}) + + if associated_tags and len(associated_tags) > len( + association_form_data + ): + # Create a list of dictionaries with tag_id and user_id + updated_tags = [ + {"tag_id": tag_id, "user_id": g.account_id} + for tag_id in association_form_data + ] + # Fill remaining with a empty tag_id and current user account_id + updated_tags.extend([{"tag_id": "", "user_id": g.account_id}]) + else: + # If associated_tags is zero or null, or not greater than association_form_data + updated_tags = [ + {"tag_id": tag_id, "user_id": g.account_id} + for tag_id in association_form_data + ] + update_associated_tags(application_id, updated_tags) return redirect( url_for( @@ -73,6 +91,7 @@ def load_change_tags(application_id): application_id=application_id, ) ) + state = get_state_for_tasklist_banner(application_id) all_tags = get_tags_for_fund_round(state.fund_id, state.round_id, "") associated_tags = get_associated_tags_for_application(application_id) diff --git a/app/templates/macros/banner_summary.html b/app/templates/macros/banner_summary.html index 44791d9d..5c1e3bb7 100644 --- a/app/templates/macros/banner_summary.html +++ b/app/templates/macros/banner_summary.html @@ -1,4 +1,5 @@ -{% macro banner_summary(fund_name, fund_shortname, project_reference, project_name, funding_amount_requested, assessment_status, flag_status) %} +{% macro banner_summary(fund_name,fund_shortname, project_reference, project_name, funding_amount_requested, assessment_status, flag_status, display_status=True) %} +
@@ -12,7 +13,7 @@

Total funding requested: £{{ "{:,.2f}".format(funding_amount_requested | float) }}

{% endif %} - {% if g.access_controller.has_any_assessor_role %} + {% if g.access_controller.has_any_assessor_role and display_status %}

{{ assessment_status }} {% if "Flagged" in flag_status or flag_status in ["Multiple flags to resolve", "Stopped", "Flagged"] %}

{{ flag_status or "" }}

diff --git a/config/envs/default.py b/config/envs/default.py index 97bff036..81559e19 100644 --- a/config/envs/default.py +++ b/config/envs/default.py @@ -186,6 +186,9 @@ class DefaultConfig: ASSESSMENT_ASSOCIATE_TAGS_ENDPOINT = ( ASSESSMENT_STORE_API_HOST + "/application/{application_id}/tag" ) + APPLICATION_ASSOCIATED_ALL_TAGS_ENDPOINT = ( + ASSESSMENT_STORE_API_HOST + "/application/{application_id}/tags" + ) ASSESSMENT_GET_TAG_ENDPOINT = ( ASSESSMENT_STORE_API_HOST + "/funds/{fund_id}/rounds/{round_id}/tags/{tag_id}" diff --git a/tests/test_activity_trail.py b/tests/test_activity_trail.py new file mode 100644 index 00000000..86ae5cfe --- /dev/null +++ b/tests/test_activity_trail.py @@ -0,0 +1,82 @@ +import pytest +from app.blueprints.assessments.activity_trail import add_user_info +from app.blueprints.assessments.activity_trail import AssociatedTags +from app.blueprints.assessments.activity_trail import BaseModel +from app.blueprints.assessments.activity_trail import get_user_info +from flask import Flask + + +class TestActivityTrail: + test_app = Flask("app") + list_data_class = [type("AssociatedTags", (), {"user_id": "00000"})] + list_data_dict = [{"user_id": "00000"}] + state = type("AssessorTaskList", (), {"fund_short_name": "COF"})() + _accounts_list = { + "00000": { + "full_name": "Development User", + "email": "dev@example.com", + "roles": ["COF_LEAD_ASSESSOR", "COF_ASSESSOR"], + "email_address": "dev@example.com", + "highest_role": "LEAD_ASSESSOR", + } + } + + @pytest.mark.parametrize( + "list_data", + [(list_data_class)], + ) + def test_get_user_info(self, list_data, mocker): + + # Patching the get_bulk_accounts_dict function + mocker.patch( + "app.blueprints.assessments.activity_trail.get_bulk_accounts_dict", + return_value=self._accounts_list, + ) + + with self.test_app.app_context(): + result = get_user_info(list_data, self.state) + assert result == self._accounts_list + + def test_add_user_info(self, mocker): + with self.test_app.app_context(): + mocker.patch( + "app.blueprints.assessments.activity_trail.get_user_info", + return_value=self._accounts_list, + ) + result = add_user_info(self.list_data_class, self.state) + + expected_result = [ + AssociatedTags( + application_id="e67851a3-a190-456e-b2fa-9f1f8a2732cb", + tag_id="bdd16710-5d13-4d15-a9b3-2344bc1fcc75", + value="Test General tag", + user_id="00000", + associated=True, + purpose="GENERAL", + created_at="2023-11-16 13:12:21.617459+00:00", + full_name="Development User", + email_address="dev@example.com", + highest_role="LEAD_ASSESSOR", + date_created="2023-11-16 13:12:21", + ), + ] + + for instance in result: + assert instance.user_id == expected_result[0].user_id + assert "dev@example.com" == expected_result[0].email_address + assert "LEAD_ASSESSOR" == expected_result[0].highest_role + assert "2023-11-16 13:12:21" == expected_result[0].date_created + + @pytest.mark.parametrize( + "date_str, expected_date_format", + [ + ("2023-11-16T13:26:25.686556", "2023-11-16 13:26:25"), + ("2023-11-16 18:35:08.285688+00:00", "2023-11-16 18:35:08"), + ("2023-11-16 19:01:04", "2023-11-16 19:01:04"), + ], + ) + def test_format_date(self, date_str, expected_date_format): + with self.test_app.app_context(): + result = BaseModel._format_date(date_str) + + assert result == expected_date_format From f5df870765702f58b7b9a77f47b4ce8785297dc4 Mon Sep 17 00:00:00 2001 From: Aaron Williams Date: Tue, 28 Nov 2023 12:22:35 +0000 Subject: [PATCH 08/24] Update copilot workflow * Update workflow for deployment to uat and production to only occur after post-deploy tests pass * Add triggers for changes to app, test, and the workflow itself +ref: FS-3797 +semver: minor --- .github/workflows/copilot_deploy.yml | 165 +++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 21 deletions(-) diff --git a/.github/workflows/copilot_deploy.yml b/.github/workflows/copilot_deploy.yml index 3e969557..180e5919 100644 --- a/.github/workflows/copilot_deploy.yml +++ b/.github/workflows/copilot_deploy.yml @@ -23,16 +23,15 @@ on: type: boolean description: Run e2e tests push: - paths: # Ignore README markdown and only deploy when something in the copilot folder has changed - - 'copilot/**' + paths: - '!**/README.md' + - 'app/**' + - 'tests/**' - 'requirements-dev.in' - 'requirements-dev.txt' - 'requirements.in' - 'requirements.txt' - pull_request: - types: - - closed # Further protection - only allow this workflow to run automatically on closed pull requests + - '.github/workflows/copilot_deploy.yml' jobs: tag_version: @@ -44,41 +43,46 @@ jobs: run: | echo "tag_value=$(echo '${{ github.ref }}' | sed -e 's,.*/\(.*\),\1,')" >> $GITHUB_OUTPUT + pre_deploy_tests: + uses: communitiesuk/funding-service-design-workflows/.github/workflows/pre-deploy.yml@main + with: + assets_required: true + postgres_unit_testing: false + paketo_build: needs: [ tag_version ] + concurrency: build-assessment-pack permissions: packages: write uses: communitiesuk/funding-service-design-workflows/.github/workflows/package.yml@main with: - version_to_build: $(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + version_to_build: ${{ needs.tag_version.outputs.version_to_tag }} owner: ${{ github.repository_owner }} application: funding-service-design-assessment assets_required: true - pre_deploy_tests: - uses: communitiesuk/funding-design-service-workflows/.github/workflows/pre-deploy.yml@main - with: - assets_required: true - postgres_unit_testing: false - copilot_environments_workflow_setup: runs-on: ubuntu-latest outputs: matrix: ${{ steps.copilot_env_matrix.outputs.env_list }} + pre_matrix: ${{ steps.copilot_env_matrix.outputs.pre_deploy_list }} + post_matrix: ${{ steps.copilot_env_matrix.outputs.post_deploy_list }} steps: - id: copilot_env_matrix run: | - if [ "${{ inputs.environment }}" != '' ]; then + if [ "${{ inputs.environment }}" != "" ]; then echo "env_list=[\"${{ inputs.environment }}\"]" >> $GITHUB_OUTPUT elif [ "${{ github.ref }}" == 'refs/heads/main' ]; then - echo "env_list=[\"dev\", \"test\", \"uat\", \"production\"]" >> $GITHUB_OUTPUT + echo "pre_deploy_list=[\"dev\", \"test\"]" >> $GITHUB_OUTPUT + echo "post_deploy_list=[\"uat\", \"production\"]" >> $GITHUB_OUTPUT else - echo "env_list=[\"dev\", \"test\"]" >> $GITHUB_OUTPUT + echo "pre_deploy_list=[\"dev\", \"test\"]" >> $GITHUB_OUTPUT fi - copilot_env_deploy: + individual_deploy: + if: inputs.environment != '' concurrency: - group: '${{ github.workflow }} @ ${{ github.ref }}' + group: 'fsd-preaward-copilot-individual-${{ matrix.value }}' cancel-in-progress: false permissions: id-token: write # This is required for requesting the JWT @@ -105,7 +109,7 @@ jobs: uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT }}:role/GithubCopilotDeploy - role-session-name: NOTIFICATION_${{ matrix.value }}_COPILOT_${{ steps.currentdatetime.outputs.datetime }} + role-session-name: ASSESSMENT_${{ matrix.value }}_COPILOT_${{ steps.currentdatetime.outputs.datetime }} aws-region: eu-west-2 - name: Install AWS Copilot CLI @@ -127,7 +131,66 @@ jobs: - name: Inject replacement image into manifest run: | - yq -i '.image.location = "ghcr.io/communitiesuk/funding-service-design-assessment:${{ github.ref_name == 'main' && 'latest' || github.ref_name }}"' copilot/fsd-assessment/manifest.yml + yq -i '.image.location = "ghcr.io/communitiesuk/funding-service-design-assessment:${{ github.ref_name == 'main' && 'latest' || needs.tag_version.outputs.version_to_tag }}"' copilot/fsd-assessment/manifest.yml + + - name: Copilot ${{ matrix.value }} deploy + id: deploy_build + run: | + copilot svc deploy --env ${{ matrix.value }} --app pre-award + + sandbox_deploy: + if: ${{ needs.copilot_environments_workflow_setup.outputs.pre_matrix != '' && toJson(fromJson(needs.copilot_environments_workflow_setup.outputs.pre_matrix)) != '[]' }} + concurrency: + group: 'fsd-preaward-copilot-sandbox-${{ matrix.value }}' + cancel-in-progress: false + permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + needs: [ tag_version, pre_deploy_tests, paketo_build, copilot_environments_workflow_setup ] + runs-on: ubuntu-latest + continue-on-error: true + strategy: + max-parallel: 1 + matrix: + value: ${{ fromJSON(needs.copilot_environments_workflow_setup.outputs.pre_matrix) }} + fail-fast: false + environment: ${{ matrix.value }} + steps: + - name: Git clone the repository + uses: actions/checkout@v4 + + - name: Get current date + shell: bash + id: currentdatetime + run: echo "datetime=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT }}:role/GithubCopilotDeploy + role-session-name: ASSESSMENT_${{ matrix.value }}_COPILOT_${{ steps.currentdatetime.outputs.datetime }} + aws-region: eu-west-2 + + - name: Install AWS Copilot CLI + shell: bash + run: | + curl -Lo aws-copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux && chmod +x aws-copilot && sudo mv aws-copilot /usr/local/bin/copilot + + - name: confirm copilot env + shell: bash + run: | + if [ $(copilot env ls) != "${{ matrix.value }}" ]; then + echo $(copilot env ls) + exit 1 + fi + + - name: Inject Git SHA into manifest + run: | + yq -i '.variables.GITHUB_SHA = "${{ github.sha }}"' copilot/fsd-assessment/manifest.yml + + - name: Inject replacement image into manifest + run: | + yq -i '.image.location = "ghcr.io/communitiesuk/funding-service-design-assessment:${{ github.ref_name == 'main' && 'latest' || needs.tag_version.outputs.version_to_tag }}"' copilot/fsd-assessment/manifest.yml - name: Copilot ${{ matrix.value }} deploy id: deploy_build @@ -135,13 +198,73 @@ jobs: copilot svc deploy --env ${{ matrix.value }} --app pre-award post_deploy_tests: - needs: copilot_env_deploy + needs: sandbox_deploy if: inputs.environment == 'test' || inputs.environment == 'dev' || inputs.environment == '' secrets: - E2E_PAT: ${{secrets.E2E_PAT}} + FSD_GH_APP_ID: ${{ secrets.FSD_GH_APP_ID }} + FSD_GH_APP_KEY: ${{ secrets.FSD_GH_APP_KEY }} uses: communitiesuk/funding-service-design-workflows/.github/workflows/post-deploy.yml@main with: run_performance_tests: ${{ inputs.run_performance_tests || false }} run_e2e_tests: ${{ inputs.run_e2e_tests || true }} app_name: assessment environment: ${{ inputs.environment == '' && 'test' || inputs.environment }} + + release_deploy: + if: ${{ needs.copilot_environments_workflow_setup.outputs.post_matrix != '' && toJson(fromJson(needs.copilot_environments_workflow_setup.outputs.post_matrix)) != '[]' }} + concurrency: + group: 'fsd-preaward-copilot-release-${{ matrix.value }}' + cancel-in-progress: false + permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + needs: [ tag_version, post_deploy_tests, paketo_build, copilot_environments_workflow_setup ] + runs-on: ubuntu-latest + continue-on-error: true + strategy: + max-parallel: 1 + matrix: + value: ${{ fromJSON(needs.copilot_environments_workflow_setup.outputs.post_matrix) }} + fail-fast: false + environment: ${{ matrix.value }} + steps: + - name: Git clone the repository + uses: actions/checkout@v4 + + - name: Get current date + shell: bash + id: currentdatetime + run: echo "datetime=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT }}:role/GithubCopilotDeploy + role-session-name: ASSESSMENT_${{ matrix.value }}_COPILOT_${{ steps.currentdatetime.outputs.datetime }} + aws-region: eu-west-2 + + - name: Install AWS Copilot CLI + shell: bash + run: | + curl -Lo aws-copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux && chmod +x aws-copilot && sudo mv aws-copilot /usr/local/bin/copilot + + - name: confirm copilot env + shell: bash + run: | + if [ $(copilot env ls) != "${{ matrix.value }}" ]; then + echo $(copilot env ls) + exit 1 + fi + + - name: Inject Git SHA into manifest + run: | + yq -i '.variables.GITHUB_SHA = "${{ github.sha }}"' copilot/fsd-assessment/manifest.yml + + - name: Inject replacement image into manifest + run: | + yq -i '.image.location = "ghcr.io/communitiesuk/funding-service-design-assessment:${{ github.ref_name == 'main' && 'latest' || needs.tag_version.outputs.version_to_tag }}"' copilot/fsd-assessment/manifest.yml + + - name: Copilot ${{ matrix.value }} deploy + id: deploy_build + run: | + copilot svc deploy --env ${{ matrix.value }} --app pre-award From 2d03cdabf5cdd8d04143644d2b24fada0a41727b Mon Sep 17 00:00:00 2001 From: Ram <80714392+ramsharma-prog@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:36:22 +0000 Subject: [PATCH 09/24] bug fixes: (#506) - display project name on the banner correctly - View activity trial button margin fix --- app/blueprints/assessments/templates/activity_trail.html | 2 ++ app/blueprints/assessments/templates/assessor_tasklist.html | 2 +- app/static/src/styles/landing.css | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/blueprints/assessments/templates/activity_trail.html b/app/blueprints/assessments/templates/activity_trail.html index 1ff07810..b959f024 100644 --- a/app/blueprints/assessments/templates/activity_trail.html +++ b/app/blueprints/assessments/templates/activity_trail.html @@ -17,6 +17,7 @@ {% from "macros/tags_table.html" import tags %} {% set assessment_status = assessment_status if assessment_status is defined else None %} +{% set fund_shortname = fund_shortname if fund_shortname is defined else None %} {% block header %}
-
+ diff --git a/app/static/src/styles/landing.css b/app/static/src/styles/landing.css index 37bbfe5c..e6cb1422 100644 --- a/app/static/src/styles/landing.css +++ b/app/static/src/styles/landing.css @@ -29,6 +29,11 @@ width: 16em; } +.left-margin { + margin-left: 80px; + +} + .search-bar-flex-container { float: none; display: flex; From 74241f3d16aeaa4889b00ceeb5a14ac212b42709 Mon Sep 17 00:00:00 2001 From: Ram <80714392+ramsharma-prog@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:18:14 +0000 Subject: [PATCH 10/24] set gapge heading for activity trail (#507) --- app/blueprints/assessments/templates/activity_trail.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/blueprints/assessments/templates/activity_trail.html b/app/blueprints/assessments/templates/activity_trail.html index b959f024..236007b6 100644 --- a/app/blueprints/assessments/templates/activity_trail.html +++ b/app/blueprints/assessments/templates/activity_trail.html @@ -16,6 +16,8 @@ {% from "macros/scores_justification.html" import score as scores %} {% from "macros/tags_table.html" import tags %} +{% set pageHeading = "Activity trail" %} + {% set assessment_status = assessment_status if assessment_status is defined else None %} {% set fund_shortname = fund_shortname if fund_shortname is defined else None %} {% block header %} From b52d8d6cf3f6fa5ee06284c3a03ca5015cba42e2 Mon Sep 17 00:00:00 2001 From: Adam Wallace Date: Thu, 30 Nov 2023 09:50:29 +0000 Subject: [PATCH 11/24] app dpi filter support (#502) --- app/blueprints/assessments/routes.py | 4 + .../templates/assessor_dashboard.html | 3 +- .../application_overviews_table_dpif.html | 5 +- .../templates/macros/filter_options_dpif.html | 148 +++++++++++------- config/display_value_mappings.py | 25 ++- tests/api_data/test_data.py | 6 + tests/conftest.py | 21 +++ tests/test_authorisation.py | 2 +- tests/test_routes.py | 77 ++++++++- 9 files changed, 224 insertions(+), 67 deletions(-) diff --git a/app/blueprints/assessments/routes.py b/app/blueprints/assessments/routes.py index b732da23..e2f48160 100644 --- a/app/blueprints/assessments/routes.py +++ b/app/blueprints/assessments/routes.py @@ -114,6 +114,7 @@ from config.display_value_mappings import assessment_statuses from config.display_value_mappings import asset_types from config.display_value_mappings import cohort +from config.display_value_mappings import dpi_filters from config.display_value_mappings import funding_types from config.display_value_mappings import landing_filters from config.display_value_mappings import search_params_cof @@ -260,10 +261,12 @@ def fund_dashboard(fund_short_name: str, round_short_name: str): "countries": ",".join(countries), } + # matches the query parameters provided in the search and filter form search_params, show_clear_filters = match_search_params( search_params, request.args ) + # request all the application overviews based on the search parameters application_overviews = get_application_overviews( fund_id, round_id, search_params ) @@ -363,6 +366,7 @@ def get_sorted_application_overviews( countries=all_application_locations.countries, regions=all_application_locations.regions, local_authorities=all_application_locations._local_authorities, + dpi_filters=dpi_filters, ) diff --git a/app/blueprints/assessments/templates/assessor_dashboard.html b/app/blueprints/assessments/templates/assessor_dashboard.html index eba2a6a4..eef3ac1d 100644 --- a/app/blueprints/assessments/templates/assessor_dashboard.html +++ b/app/blueprints/assessments/templates/assessor_dashboard.html @@ -129,7 +129,8 @@

{% if is_active_status %}All act sort_order, tag_option_groups, tags, - tagging_purpose_config + tagging_purpose_config, + dpi_filters ) }} {% endif %} diff --git a/app/blueprints/assessments/templates/macros/application_overviews_table_dpif.html b/app/blueprints/assessments/templates/macros/application_overviews_table_dpif.html index 093bf363..c226bdb8 100644 --- a/app/blueprints/assessments/templates/macros/application_overviews_table_dpif.html +++ b/app/blueprints/assessments/templates/macros/application_overviews_table_dpif.html @@ -12,13 +12,12 @@ {% macro render(application_overviews, round_details, query_params, asset_types, assessment_statuses,countries, regions, local_authorities, show_clear_filters, - sort_column, sort_order, tag_option_groups, tags, tagging_purpose_config) -%} + sort_column, sort_order, tag_option_groups, tags, tagging_purpose_config, dpi_filters) -%}