From 0330985b3655accdd3514e1761c3888a5acc7405 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 27 Jan 2025 02:58:14 -0500 Subject: [PATCH 01/25] apt-buildpack, php-buildpack, and node buildpack config for cloud.gov. Basic auth dependencies and config for drupal site. --- .bp-config/httpd/httpd.conf | 39 ++++ .bp-config/nginx/server-defaults.conf | 11 + .bp-config/options.json | 11 + .bp-config/php/php.ini.d/extensions.ini | 8 + .bp-config/php/php.ini.d/memory_limit.ini | 3 + .bp-config/user-provided/httpd-basicauth.conf | 5 + .../user-provided/httpd-drupalsupport.conf | 2 + .cfignore | 33 +++ .gitignore | 5 +- apt.yml | 7 + bin/deploy-cloudgov.sh | 90 +++++++++ bootstrap.sh | 172 ++++++++++++++++ web/template-.htaccess | 190 ++++++++++++++++++ 13 files changed, 574 insertions(+), 2 deletions(-) create mode 100644 .bp-config/httpd/httpd.conf create mode 100644 .bp-config/nginx/server-defaults.conf create mode 100644 .bp-config/options.json create mode 100644 .bp-config/php/php.ini.d/extensions.ini create mode 100644 .bp-config/php/php.ini.d/memory_limit.ini create mode 100644 .bp-config/user-provided/httpd-basicauth.conf create mode 100644 .bp-config/user-provided/httpd-drupalsupport.conf create mode 100644 .cfignore create mode 100644 apt.yml create mode 100644 bin/deploy-cloudgov.sh create mode 100644 bootstrap.sh create mode 100644 web/template-.htaccess diff --git a/.bp-config/httpd/httpd.conf b/.bp-config/httpd/httpd.conf new file mode 100644 index 00000000..edbb1403 --- /dev/null +++ b/.bp-config/httpd/httpd.conf @@ -0,0 +1,39 @@ +# Overrides https://github.com/cloudfoundry/php-buildpack/blob/master/defaults/config/httpd/httpd.conf +# to include our customization. +# See https://docs.cloudfoundry.org/buildpacks/php/gsg-php-config.html#engine-configurations for background + +ServerRoot "${HOME}/httpd" +Listen ${PORT} +ServerAdmin "${HTTPD_SERVER_ADMIN}" +ServerName "0.0.0.0" +DocumentRoot "${HOME}/#{WEBDIR}" +Include conf/extra/httpd-modules.conf +Include conf/extra/httpd-directories.conf +Include conf/extra/httpd-mime.conf +Include conf/extra/httpd-deflate.conf +Include conf/extra/httpd-logging.conf +Include conf/extra/httpd-mpm.conf +Include conf/extra/httpd-default.conf +Include conf/extra/httpd-remoteip.conf +Include conf/extra/httpd-php.conf + +# If they exist, include any user-provided customizations +IncludeOptional conf/user-provided/*.conf + + + LoadModule headers_module modules/mod_headers.so + + +RequestHeader unset Proxy early + +# Basic auth + + AuthType Basic + AuthName "Dev site" + AuthUserFile "/home/vcap/app/apache2/.htpasswd" + + Require ip 10.10.2 + Require host localhost:3000 + Require valid-user + + diff --git a/.bp-config/nginx/server-defaults.conf b/.bp-config/nginx/server-defaults.conf new file mode 100644 index 00000000..7256ae10 --- /dev/null +++ b/.bp-config/nginx/server-defaults.conf @@ -0,0 +1,11 @@ + + listen @{PORT}; + server_name _; + + fastcgi_temp_path @{TMPDIR}/nginx_fastcgi 1 2; + client_body_temp_path @{TMPDIR}/nginx_client_body 1 2; + proxy_temp_path @{TMPDIR}/nginx_proxy 1 2; + + real_ip_header x-forwarded-for; + set_real_ip_from 10.0.0.0/8; + real_ip_recursive on; diff --git a/.bp-config/options.json b/.bp-config/options.json new file mode 100644 index 00000000..5c8b428b --- /dev/null +++ b/.bp-config/options.json @@ -0,0 +1,11 @@ +{ + "COMPOSER_INSTALL_OPTIONS": [ + "--no-progress --no-interaction" + ], + "COMPOSER_VENDOR_DIR": "vendor", + "WEBDIR": "web", + "PHP_VERSION": "{PHP_83_LATEST}", + "ADDITIONAL_PREPROCESS_CMDS": [ + "$HOME/bootstrap.sh" + ] +} diff --git a/.bp-config/php/php.ini.d/extensions.ini b/.bp-config/php/php.ini.d/extensions.ini new file mode 100644 index 00000000..e91cd366 --- /dev/null +++ b/.bp-config/php/php.ini.d/extensions.ini @@ -0,0 +1,8 @@ +extension=apcu +extension=igbinary +extension=imagick +extension=pdo_mysql +extension=redis +extension=mysqli + +zend_extension=opcache.so diff --git a/.bp-config/php/php.ini.d/memory_limit.ini b/.bp-config/php/php.ini.d/memory_limit.ini new file mode 100644 index 00000000..d0c56edb --- /dev/null +++ b/.bp-config/php/php.ini.d/memory_limit.ini @@ -0,0 +1,3 @@ +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = 1024M diff --git a/.bp-config/user-provided/httpd-basicauth.conf b/.bp-config/user-provided/httpd-basicauth.conf new file mode 100644 index 00000000..f69f973a --- /dev/null +++ b/.bp-config/user-provided/httpd-basicauth.conf @@ -0,0 +1,5 @@ +# Enable modules needed for http basic auth +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule auth_basic_module modules/mod_auth_basic.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authz_user_module modules/mod_authz_user.so diff --git a/.bp-config/user-provided/httpd-drupalsupport.conf b/.bp-config/user-provided/httpd-drupalsupport.conf new file mode 100644 index 00000000..e8638579 --- /dev/null +++ b/.bp-config/user-provided/httpd-drupalsupport.conf @@ -0,0 +1,2 @@ +# Enable proxy_http for our s3fs module +LoadModule proxy_http_module modules/mod_proxy_http.so diff --git a/.cfignore b/.cfignore new file mode 100644 index 00000000..3d203d6a --- /dev/null +++ b/.cfignore @@ -0,0 +1,33 @@ +# Ignore directories generated by Composer +/drush/contrib/ +/vendor/ +/web/core/ +/web/modules/contrib/ +/web/themes/contrib/ +/web/profiles/contrib/ +/web/libraries/ + +# Ignore node modules from USWDS or otherwise. +node_modules/ + +# Typically, composer generates a .gitignore to ignore the +# `settings.php` files. For cloud.gov and Cloud Foundry, no sensitive +# information is stored in the settings files. Instead, those files +# have code that parses environment variables for DB and S3 +# +# Ignore sensitive information [This is a `composer` default] +# /web/sites/*/settings.php +# /web/sites/*/settings.local.php + + +# Ignore Drupal's file directory +/web/sites/*/files/ + +# Ignore SimpleTest multi-site environment. +/web/sites/simpletest + +# Ignore files generated by PhpStorm +/.idea/ + +.DS_Store +/.ddev/ diff --git a/.gitignore b/.gitignore index cecd9c06..09afc58a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,9 +22,8 @@ # Ignore files generated by PhpStorm /.idea/ -# Ignore .env and keu files as they are personal +# Ignore .env and key files as they are personal .env -*.key # dependencies /node_modules @@ -43,6 +42,8 @@ vendor /build /db/ /keys +*.key + # misc .DS_Store diff --git a/apt.yml b/apt.yml new file mode 100644 index 00000000..1520331d --- /dev/null +++ b/apt.yml @@ -0,0 +1,7 @@ +--- +cleancache: true +packages: + - mariadb-client + - nano + - apache2-utils + - sendmail diff --git a/bin/deploy-cloudgov.sh b/bin/deploy-cloudgov.sh new file mode 100644 index 00000000..a34be733 --- /dev/null +++ b/bin/deploy-cloudgov.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# +# This script will attempt to create the services required +# and then launch everything. +# + +# this function will generate a random string, or bail if uuidgen is not available. +generate_string() +{ + if [ -z "$1" ] ; then + if command -v uuidgen >/dev/null ; then + NEW_STRING=$(uuidgen) + export NEW_STRING + else + echo "cannot find uuidgen utility: You will need to generate some random strings and put them in the CRON_KEY, HASH_SALT, and ROOT_USER_PASS environment variables, then re-run this script." + exit 1 + fi + fi +} + +# If the user does not supply required data, generate some secrets. +generate_string "$CRON_KEY" +CRON_KEY=${CRON_KEY:-$NEW_STRING} + +generate_string "$HASH_SALT" +HASH_SALT=${HASH_SALT:-$NEW_STRING} + +generate_string "$ROOT_USER_PASS" +ROOT_USER_PASS=${ROOT_USER_PASS:-$NEW_STRING} + +ROOT_USER_NAME=${ROOT_USER_NAME:-root} + + +# function to check if a service exists +service_exists() +{ + cf service "$1" >/dev/null 2>&1 +} + +# create services (if needed) +if service_exists "database" ; then + echo database already created +else + if [ "$1" = "prod" ] ; then + cf create-service aws-rds medium-mysql-redundant database + else + cf create-service aws-rds small-mysql database + fi +fi + +if service_exists "secrets" ; then + echo secrets already created +else + yes '' | cf create-user-provided-service secrets -p "{\"CRON_KEY\": \"$CRON_KEY\", \"HASH_SALT\": \"$HASH_SALT\", \"ROOT_USER_NAME\": \"$ROOT_USER_NAME\", \"ROOT_USER_PASS\": \"$ROOT_USER_PASS\"}" +fi + +if service_exists "storage" ; then + echo storage already created +else + cf create-service s3 basic-sandbox storage +fi + +# wait until the db is fully provisioned +until cf create-service-key database test-db-ok ; do + echo waiting until database is live... + sleep 20 +done +cf delete-service-key database test-db-ok -f + +# make the bootstrap script runnable +chmod +x ./bootstrap.sh + +# launch the apps +cf push + +# make sure that the app knows where it's s3fs stuff lives +cf create-service-key storage storagekey +S3INFO=$(cf service-key storage storagekey) +S3_BUCKET=$(echo "$S3INFO" | grep '"bucket":' | sed 's/.*"bucket": "\(.*\)",/\1/') +S3_REGION=$(echo "$S3INFO" | grep '"region":' | sed 's/.*"region": "\(.*\)",/\1/') +cf set-env PGOV-CMS S3_BUCKET "$S3_BUCKET" +cf set-env PGOV-CMS S3_REGION "$S3_REGION" +cf delete-service-key storage storagekey -f +cf restart PGOV-CMS + +# tell people where to go +ROUTE=$(cf apps | grep PGOV-CMS | awk '{print $4}') +echo +echo +echo "To log into the drupal site, you will want to go to https://${ROUTE}/user/login and enter your username/password." diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 00000000..c9c97f89 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,172 @@ +#!/bin/bash +set -euo pipefail + +# Initialize our own variables: +verbose=0 +noop=0 + +# Check function +while getopts "v:nh" opt; do + case "$opt" in + v) verbose=$OPTARG + ;; + n) noop=1 + ;; + h) manpage=1 + ;; + esac +done + +shift $((OPTIND-1)) + +# [ "$1" = "--" ] && shift + +if [ ! -z "${manpage+isset}" ]; +then + printf "Performance.gov bootstrap script +Options: + -v N Verbosity level + -n No Op - dry run + -h Help - print this text\n\n" + exit 0 +fi + + export home="/home/vcap" + export app_path="${home}/app" + export apt_path="${home}/deps/0/apt" + + ## Updated ~/.bashrc to update $PATH + [ -z $(cat ${home}/.bashrc | grep PATH) ] && \ + touch ${home}/.bashrc && \ + echo "alias nano=\"${home}/deps/0/apt/bin/nano\"" >> ${home}/.bashrc + echo "PATH=$PATH:/home/vcap/app/php/bin:/home/vcap/app/vendor/drush/drush" >> /home/vcap/.bashrc + + source ${home}/.bashrc + +## App Info +SECRETS=$(echo "$VCAP_SERVICES" | jq -r '.["user-provided"][] | select(.name == "secrets") | .credentials') +APP_NAME=$(echo "$VCAP_APPLICATION" | jq -r '.name') +APP_ROOT=$(dirname "${BASH_SOURCE[0]}") +DOC_ROOT="$APP_ROOT/web" +APP_ID=$(echo "$VCAP_APPLICATION" | jq -r '.application_id') +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] +then + printf "\nApp Info\n" +fi +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 2 ] +then + for appinfo in SECRETS APP_NAME APP_ROOT DOC_ROOT APP_ID + do + printf "%s :\t%s\n" "${appinfo}" "${!appinfo}" + done +fi + +## DB Info +DB_NAME=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.db_name') +DB_USER=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.username') +DB_PW=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.password') +DB_HOST=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.host') +DB_PORT=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.port') +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] +then + printf "\nDB Info\n" +fi +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 2 ] +then + for appinfo in DB_NAME DB_USER DB_PW DB_HOST DB_PORT + do + printf "%s :\t%s\n" "${appinfo}" "${!appinfo}" + done +fi + +## S3 Info +S3_BUCKET=$(echo "$VCAP_SERVICES" | jq -r '.["s3"][]? | select(.name == "storage") | .credentials.bucket') +export S3_BUCKET +S3_REGION=$(echo "$VCAP_SERVICES" | jq -r '.["s3"][]? | select(.name == "storage") | .credentials.region') +export S3_REGION +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] +then + printf "\nS3 Info\n" +fi +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 2 ] +then + for appinfo in S3_BUCKET S3_REGION + do + printf "%s :\t%s\n" "${appinfo}" "${!appinfo}" + done +fi + +if [ -n "$S3_BUCKET" ] && [ -n "$S3_REGION" ]; then + # Add Proxy rewrite rules to the top of the htaccess file + sed "s/^#RewriteRule .s3fs/RewriteRule ^s3fs/" "$DOC_ROOT/template-.htaccess" > "$DOC_ROOT/.htaccess" +else + cp "$DOC_ROOT/template-.htaccess" "$DOC_ROOT/.htaccess" +fi + +install_drupal() { + if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] + then + printf "\ninstall_drupal called on %s\n" "${APP_ID}" + fi + + ROOT_USER_NAME=$(echo "$SECRETS" | jq -r '.ROOT_USER_NAME') + ROOT_USER_PASS=$(echo "$SECRETS" | jq -r '.ROOT_USER_PASS') + + : "${ROOT_USER_NAME:?Need root user name for Drupal}" + : "${ROOT_USER_PASS:?Need root user pass for Drupal}" + + if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] + then + printf "\nRunning drush site-install\n" + fi + drush site-install minimal --existing-config \ + --db-url="mysql://$DB_USER:$DB_PW@$DB_HOST:$DB_PORT/$DB_NAME" \ + --account-name="$ROOT_USER_NAME" \ + --account-pass="$ROOT_USER_PASS" \ + -y + + if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] + then + printf "\nImporting previous database\n" + fi + drush sql-drop -y + drush sql-cli < ../db/performancedb.sql + + # Set site uuid to match our config + if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] + then + printf "\nSet Site UUID\n" + fi + UUID=$(grep uuid ../config/system.site.yml | cut -d' ' -f2) + drush config:set "system.site" uuid "$UUID" --yes + if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] + then + printf "\nSite UUID set to: %s\n" "${UUID}" + fi +} + +# Go into the Drupal web root directory +cd "$DOC_ROOT" + +# If there is no "config:import" command, Drupal needs to be installed +drush list | grep "config:import" > /dev/null || install_drupal + +# Clear the cache +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] +then + printf "\nRebuild cache and update DB\n" +fi + +drush deploy + + # Set basic auth credentials + chmod 775 /home/vcap/app/apache2 + htpasswd -b -c /home/vcap/app/apache2/.htpasswd admin civicactions + + # Restart nginx +# service nginx restart + +if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] +then + printf "\nDone\n" +fi diff --git a/web/template-.htaccess b/web/template-.htaccess new file mode 100644 index 00000000..062d08e5 --- /dev/null +++ b/web/template-.htaccess @@ -0,0 +1,190 @@ +# +# Apache/PHP/Drupal settings: +# +#RewriteRule ^s3fs-(css|js)/(.*)$ http://${S3_BUCKET}.s3-${S3_REGION}.amazonaws.com/public/$2 [P] + +# Protect files and directories from prying eyes. + + + Require all denied + + + Order allow,deny + + + +# Don't show directory listings for URLs which map to a directory. +Options -Indexes + +# Set the default handler. +DirectoryIndex index.php index.html index.htm + +# Add correct encoding for SVGZ. +AddType image/svg+xml svg svgz +AddEncoding gzip svgz + +# Most of the following PHP settings cannot be changed at runtime. See +# sites/default/default.settings.php and +# Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be +# changed at runtime. + +# PHP 5, Apache 1 and 2. + + php_value assert.active 0 + php_flag session.auto_start off + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_flag mbstring.encoding_translation off + # PHP 5.6 has deprecated $HTTP_RAW_POST_DATA and produces warnings if this is + # not set. + php_value always_populate_raw_post_data -1 + + +# Requires mod_expires to be enabled. + + # Enable expirations. + ExpiresActive On + + # Cache all files for 2 weeks after access (A). + ExpiresDefault A1209600 + + + # Do not allow PHP scripts to be cached unless they explicitly send cache + # headers themselves. Otherwise all scripts would have to overwrite the + # headers set by mod_expires if they want another caching behavior. This may + # fail if an error occurs early in the bootstrap process, and it may cause + # problems if a non-Drupal PHP file is installed in a subdirectory. + ExpiresActive Off + + + +# Set a fallback resource if mod_rewrite is not enabled. This allows Drupal to +# work without clean URLs. This requires Apache version >= 2.2.16. If Drupal is +# not accessed by the top level URL (i.e.: http://example.com/drupal/ instead of +# http://example.com/), the path to index.php will need to be adjusted. + + FallbackResource /index.php + + +# Various rewrite rules. + + RewriteEngine on + + # Set "protossl" to "s" if we were accessed via https://. This is used later + # if you enable "www." stripping or enforcement, in order to ensure that + # you don't bounce between http and https. + RewriteRule ^ - [E=protossl] + RewriteCond %{HTTPS} on + RewriteRule ^ - [E=protossl:s] + + # Make sure Authorization HTTP header is available to PHP + # even when running as CGI or FastCGI. + RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Block access to "hidden" directories whose names begin with a period. This + # includes directories used by version control systems such as Subversion or + # Git to store control files. Files whose names begin with a period, as well + # as the control files used by CVS, are protected by the FilesMatch directive + # above. + # + # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is + # not possible to block access to entire directories from .htaccess because + # is not allowed here. + # + # If you do not have mod_rewrite installed, you should remove these + # directories from your webroot or otherwise protect them from being + # downloaded. + RewriteRule "/\.|^\.(?!well-known/)" - [F] + + # If your site can be accessed both with and without the 'www.' prefix, you + # can use one of the following settings to redirect users to your preferred + # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: + # + # To redirect all users to access the site WITH the 'www.' prefix, + # (http://example.com/foo will be redirected to http://www.example.com/foo) + # uncomment the following: + # RewriteCond %{HTTP_HOST} . + # RewriteCond %{HTTP_HOST} !^www\. [NC] + # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + # + # To redirect all users to access the site WITHOUT the 'www.' prefix, + # (http://www.example.com/foo will be redirected to http://example.com/foo) + # uncomment the following: + # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] + + # Modify the RewriteBase if you are using Drupal in a subdirectory or in a + # VirtualDocumentRoot and the rewrite rules are not working properly. + # For example if your site is at http://example.com/drupal uncomment and + # modify the following line: + # RewriteBase /drupal + # + # If your site is running in a VirtualDocumentRoot at http://example.com/, + # uncomment the following line: + # RewriteBase / + + # Redirect common PHP files to their new locations. + RewriteCond %{REQUEST_URI} ^(.*)?/(install.php) [OR] + RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild.php) + RewriteCond %{REQUEST_URI} !core + RewriteRule ^ %1/core/%2 [L,QSA,R=301] + + # Rewrite install.php during installation to see if mod_rewrite is working + RewriteRule ^core/install.php core/install.php?rewrite=ok [QSA,L] + + # Pass all requests not referring directly to files in the filesystem to + # index.php. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !=/favicon.ico + RewriteRule ^ index.php [L] + + # For security reasons, deny access to other PHP files on public sites. + # Note: The following URI conditions are not anchored at the start (^), + # because Drupal may be located in a subdirectory. To further improve + # security, you can replace '!/' with '!^/'. + # Allow access to PHP files in /core (like authorize.php or install.php): + RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$ + # Allow access to test-specific PHP files: + RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php + # Allow access to Statistics module's custom front controller. + # Copy and adapt this rule to directly execute PHP files in contributed or + # custom modules or to run another PHP application in the same directory. + RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$ + # Deny access to any other PHP files that do not match the rules above. + # Specifically, disallow autoload.php from being served directly. + RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F] + + # Rules to correctly serve gzip compressed CSS and JS files. + # Requires both mod_rewrite and mod_headers to be enabled. + + # Serve gzip compressed CSS files if they exist and the client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.css $1\.css\.gz [QSA] + + # Serve gzip compressed JS files if they exist and the client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.js $1\.js\.gz [QSA] + + # Serve correct content types, and prevent mod_deflate double gzip. + RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1] + RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1] + + + # Serve correct encoding type. + Header set Content-Encoding gzip + # Force proxies to cache gzipped & non-gzipped css/js files separately. + Header append Vary Accept-Encoding + + + + +# Various header fixes. + + # Disable content sniffing, since it's an attack vector. + Header always set X-Content-Type-Options nosniff + # Disable Proxy header, since it's an attack vector. + RequestHeader unset Proxy + From 832985fae3fbc712e12ff0c83827cfe3f4ec788a Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 27 Jan 2025 02:59:56 -0500 Subject: [PATCH 02/25] PGOV-400: Manifest.yml with cloud.gov environment config for deployment. --- manifest.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 manifest.yml diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 00000000..d4ad3dc2 --- /dev/null +++ b/manifest.yml @@ -0,0 +1,29 @@ +--- +version: 1 +default_config: &defaults + buildpacks: + - https://github.com/cloudfoundry/apt-buildpack + - php_buildpack + disk_quota: 4G + stack: cflinuxfs4 + timeout: 180 + services: + - database # cf create-service aws-rds shared-mysql database + - secrets # cf create-user-provided-service secrets -p '{ + # "CRON_KEY": ... + # "HASH_SALT": ... + # "ROOT_USER_NAME": ..., + # "ROOT_USER_PASS": ..., + # }' + - storage # cf create-service s3 basic-sandbox storage + +applications: + - name: PGOV-CMS + <<: *defaults + memory: 512M + instances: 1 + - name: PGOV-Frontend + path: ./src/frontend + memory: 256M + buildpacks: + - nodejs_buildpack From c45e50183a22270b9d97554aa9330b8bb2965e07 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Wed, 18 Dec 2024 18:52:09 -0500 Subject: [PATCH 03/25] httpd basic auth config for drupal site. --- .bp-config/httpd/httpd.conf | 6 +----- .bp-config/nginx/server-defaults.conf | 1 + .bp-config/user-provided/httpd-basicauth.conf | 2 +- .gitignore | 1 + src/frontend/pages/api/basicauth/route.ts | 0 5 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 src/frontend/pages/api/basicauth/route.ts diff --git a/.bp-config/httpd/httpd.conf b/.bp-config/httpd/httpd.conf index edbb1403..e4724f74 100644 --- a/.bp-config/httpd/httpd.conf +++ b/.bp-config/httpd/httpd.conf @@ -31,9 +31,5 @@ RequestHeader unset Proxy early AuthType Basic AuthName "Dev site" AuthUserFile "/home/vcap/app/apache2/.htpasswd" - - Require ip 10.10.2 - Require host localhost:3000 - Require valid-user - + Require user admin diff --git a/.bp-config/nginx/server-defaults.conf b/.bp-config/nginx/server-defaults.conf index 7256ae10..a82fc2f5 100644 --- a/.bp-config/nginx/server-defaults.conf +++ b/.bp-config/nginx/server-defaults.conf @@ -9,3 +9,4 @@ real_ip_header x-forwarded-for; set_real_ip_from 10.0.0.0/8; real_ip_recursive on; + diff --git a/.bp-config/user-provided/httpd-basicauth.conf b/.bp-config/user-provided/httpd-basicauth.conf index f69f973a..2c85292b 100644 --- a/.bp-config/user-provided/httpd-basicauth.conf +++ b/.bp-config/user-provided/httpd-basicauth.conf @@ -2,4 +2,4 @@ LoadModule authn_core_module modules/mod_authn_core.so LoadModule auth_basic_module modules/mod_auth_basic.so LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authz_user_module modules/mod_authz_user.so +LoadModule authz_user_module modules/mod_authz_user.so \ No newline at end of file diff --git a/.gitignore b/.gitignore index 09afc58a..07437db7 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ vendor npm-debug.log* yarn-debug.log* yarn-error.log* +*.tsbuildinfo diff --git a/src/frontend/pages/api/basicauth/route.ts b/src/frontend/pages/api/basicauth/route.ts new file mode 100644 index 00000000..e69de29b From cbb79b1d3599ba3cf07ea4a801e44656216aefe3 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Wed, 18 Dec 2024 23:41:11 -0500 Subject: [PATCH 04/25] Basic auth for backend implemented in server config. --- .bp-config/httpd/httpd.conf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.bp-config/httpd/httpd.conf b/.bp-config/httpd/httpd.conf index e4724f74..edbb1403 100644 --- a/.bp-config/httpd/httpd.conf +++ b/.bp-config/httpd/httpd.conf @@ -31,5 +31,9 @@ RequestHeader unset Proxy early AuthType Basic AuthName "Dev site" AuthUserFile "/home/vcap/app/apache2/.htpasswd" - Require user admin + + Require ip 10.10.2 + Require host localhost:3000 + Require valid-user + From 0ab011d6dbb8b50b62141771c44a78641195f24e Mon Sep 17 00:00:00 2001 From: Adrienne Date: Wed, 18 Dec 2024 18:52:09 -0500 Subject: [PATCH 05/25] httpd basic auth config for drupal site. --- .bp-config/httpd/httpd.conf | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.bp-config/httpd/httpd.conf b/.bp-config/httpd/httpd.conf index edbb1403..e4724f74 100644 --- a/.bp-config/httpd/httpd.conf +++ b/.bp-config/httpd/httpd.conf @@ -31,9 +31,5 @@ RequestHeader unset Proxy early AuthType Basic AuthName "Dev site" AuthUserFile "/home/vcap/app/apache2/.htpasswd" - - Require ip 10.10.2 - Require host localhost:3000 - Require valid-user - + Require user admin From fc1d2cf64fa1d1fd587c410741628ab5abedd571 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Tue, 4 Feb 2025 00:30:38 -0500 Subject: [PATCH 06/25] PGOV-400: Fixing directory structure of httpd config files. --- .bp-config/httpd/httpd.conf | 6 +++++- .bp-config/{ => httpd}/user-provided/httpd-basicauth.conf | 0 .../{ => httpd}/user-provided/httpd-drupalsupport.conf | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename .bp-config/{ => httpd}/user-provided/httpd-basicauth.conf (100%) rename .bp-config/{ => httpd}/user-provided/httpd-drupalsupport.conf (100%) diff --git a/.bp-config/httpd/httpd.conf b/.bp-config/httpd/httpd.conf index e4724f74..edbb1403 100644 --- a/.bp-config/httpd/httpd.conf +++ b/.bp-config/httpd/httpd.conf @@ -31,5 +31,9 @@ RequestHeader unset Proxy early AuthType Basic AuthName "Dev site" AuthUserFile "/home/vcap/app/apache2/.htpasswd" - Require user admin + + Require ip 10.10.2 + Require host localhost:3000 + Require valid-user + diff --git a/.bp-config/user-provided/httpd-basicauth.conf b/.bp-config/httpd/user-provided/httpd-basicauth.conf similarity index 100% rename from .bp-config/user-provided/httpd-basicauth.conf rename to .bp-config/httpd/user-provided/httpd-basicauth.conf diff --git a/.bp-config/user-provided/httpd-drupalsupport.conf b/.bp-config/httpd/user-provided/httpd-drupalsupport.conf similarity index 100% rename from .bp-config/user-provided/httpd-drupalsupport.conf rename to .bp-config/httpd/user-provided/httpd-drupalsupport.conf From 5dab610a638159ac2e16c18623fdee4a144ed527 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Tue, 4 Feb 2025 00:32:42 -0500 Subject: [PATCH 07/25] PGOV-400: executable bootstrap script. --- bootstrap.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bootstrap.sh diff --git a/bootstrap.sh b/bootstrap.sh old mode 100644 new mode 100755 From b24ac3df5fe46c670bc1fe5bbd9f96fb3d72c56c Mon Sep 17 00:00:00 2001 From: Adrienne Date: Tue, 4 Feb 2025 00:52:51 -0500 Subject: [PATCH 08/25] PGOV-400: remove unneeded frontend basic auth route.ts --- src/frontend/pages/api/basicauth/route.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/frontend/pages/api/basicauth/route.ts diff --git a/src/frontend/pages/api/basicauth/route.ts b/src/frontend/pages/api/basicauth/route.ts deleted file mode 100644 index e69de29b..00000000 From b808507dcd813d04c8a493efca65bf2a44579646 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Wed, 5 Feb 2025 21:27:24 -0500 Subject: [PATCH 09/25] Make sure preview works on dev site and turn off css and js aggregation. --- config/next.next_entity_type_config.node.agency.yml | 9 ++++++++- config/next.next_entity_type_config.node.article.yml | 9 ++++++++- config/next.next_entity_type_config.node.goal.yml | 9 ++++++++- config/next.next_entity_type_config.node.plan.yml | 9 ++++++++- config/next.next_site.dev.yml | 11 +++++++++++ config/simple_oauth.settings.yml | 4 ++-- config/system.performance.yml | 4 ++-- 7 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 config/next.next_site.dev.yml diff --git a/config/next.next_entity_type_config.node.agency.yml b/config/next.next_entity_type_config.node.agency.yml index addffea8..02be2f6e 100644 --- a/config/next.next_entity_type_config.node.agency.yml +++ b/config/next.next_entity_type_config.node.agency.yml @@ -1,11 +1,18 @@ uuid: dc5778e1-02cd-4642-b866-0afc577b5e79 langcode: en status: true -dependencies: { } +dependencies: + module: + - next_extras +third_party_settings: + next_extras: + revalidate: false + revalidate_paths: '' id: node.agency site_resolver: site_selector configuration: sites: + dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_entity_type_config.node.article.yml b/config/next.next_entity_type_config.node.article.yml index 6c4f6ffc..e7bf82a9 100644 --- a/config/next.next_entity_type_config.node.article.yml +++ b/config/next.next_entity_type_config.node.article.yml @@ -1,11 +1,18 @@ uuid: a21a30ff-a5cc-4e19-8612-6bc34ae9c247 langcode: en status: true -dependencies: { } +dependencies: + module: + - next_extras +third_party_settings: + next_extras: + revalidate: false + revalidate_paths: '' id: node.article site_resolver: site_selector configuration: sites: + dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_entity_type_config.node.goal.yml b/config/next.next_entity_type_config.node.goal.yml index c0447867..7738d287 100644 --- a/config/next.next_entity_type_config.node.goal.yml +++ b/config/next.next_entity_type_config.node.goal.yml @@ -1,11 +1,18 @@ uuid: 8625f358-9ad4-47a6-aa18-c63bb4445bf1 langcode: en status: true -dependencies: { } +dependencies: + module: + - next_extras +third_party_settings: + next_extras: + revalidate: false + revalidate_paths: '' id: node.goal site_resolver: site_selector configuration: sites: + dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_entity_type_config.node.plan.yml b/config/next.next_entity_type_config.node.plan.yml index 55aec481..10a2ba64 100644 --- a/config/next.next_entity_type_config.node.plan.yml +++ b/config/next.next_entity_type_config.node.plan.yml @@ -1,11 +1,18 @@ uuid: f6278e3b-8631-4309-aaae-f01393841e5b langcode: en status: true -dependencies: { } +dependencies: + module: + - next_extras +third_party_settings: + next_extras: + revalidate: false + revalidate_paths: '' id: node.plan site_resolver: site_selector configuration: sites: + dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_site.dev.yml b/config/next.next_site.dev.yml new file mode 100644 index 00000000..f4b82788 --- /dev/null +++ b/config/next.next_site.dev.yml @@ -0,0 +1,11 @@ +uuid: 8acf35ef-4a5b-4b35-874d-6e6562ce8d43 +langcode: en +status: true +dependencies: { } +id: dev +label: Dev +base_url: 'https://pgov-frontend.app.cloud.gov/' +preview_url: 'https://pgov-frontend.app.cloud.gov/api/draft' +preview_secret: secret +revalidate_url: '' +revalidate_secret: '' diff --git a/config/simple_oauth.settings.yml b/config/simple_oauth.settings.yml index 0cffd710..dfb3a3a2 100644 --- a/config/simple_oauth.settings.yml +++ b/config/simple_oauth.settings.yml @@ -4,8 +4,8 @@ access_token_expiration: 300 authorization_code_expiration: 300 refresh_token_expiration: 1209600 token_cron_batch_size: 0 -public_key: ../public.key -private_key: ../private.key +public_key: ../keys/public.key +private_key: ../keys/private.key remember_clients: true use_implicit: false disable_openid_connect: false diff --git a/config/system.performance.yml b/config/system.performance.yml index 2219cf10..61f4e153 100644 --- a/config/system.performance.yml +++ b/config/system.performance.yml @@ -4,7 +4,7 @@ cache: page: max_age: 0 css: - preprocess: true + preprocess: false gzip: true fast_404: enabled: true @@ -12,5 +12,5 @@ fast_404: exclude_paths: '/\/(?:styles|imagecache)\//' html: '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

' js: - preprocess: true + preprocess: false gzip: true From 67ac24a6f24fc1a767e81b6e3cdadb0884cfb3b6 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Thu, 6 Feb 2025 16:16:34 -0500 Subject: [PATCH 10/25] Updated hero. --- src/frontend/components/view--goal-search.tsx | 28 +++++++++++-------- src/frontend/next.config.js | 17 +++++------ src/frontend/styles/style.scss | 11 ++++++++ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index 5bcfe4cd..85ede2cc 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -23,7 +23,7 @@ export default function GoalsSearchView({ filters, goals, total, description }: const [facets, setFacets] = useState(filters[1]?.options ? filters[1].options : []) const [filtersOpen, setFiltersOpen] = useState(false); const [offset, setOffset] = useState(offsetAmount); - + const handleSearch = useCallback(async (e: FormEvent, facets: Array = []) => { if (e) { e.preventDefault(); @@ -35,7 +35,7 @@ export default function GoalsSearchView({ filters, goals, total, description }: if (!response.ok) { throw new Error(`Response status: ${response.status}`); } - + const { data } = await response.json(); setDisplayGoals(data?.goalsGraphql1?.results ?? []); setTotalResults(data?.goalsGraphql1?.pageInfo.total); @@ -49,16 +49,20 @@ export default function GoalsSearchView({ filters, goals, total, description }: const masonryBP = filtersOpen ? {350: 1, 750: 2, 1400: 3} : {350: 1, 750: 2, 1060: 3, 1400: 4}; return (
-
+

{description}

+

Smart Strategy. Strong Execution.

+

Data-Driven Updates on America’s Strategic Goals.

+
+
+ +
- - - +
@@ -75,10 +79,10 @@ export default function GoalsSearchView({ filters, goals, total, description }: ))} - + ) : (
-
+

No matching goals.

diff --git a/src/frontend/next.config.js b/src/frontend/next.config.js index b862f691..d38a8c6e 100644 --- a/src/frontend/next.config.js +++ b/src/frontend/next.config.js @@ -1,16 +1,15 @@ const path = require("path"); - const cspHeader = ` default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; - img-src 'self' data: https://pgov-cms.app.cloud.gov; + img-src 'self' data: https://pgov-cms.app.cloud.gov performance.ddev.site; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; - frame-ancestors 'self' https://pgov-cms.app.cloud.gov; + frame-ancestors 'self' https://pgov-cms.app.cloud.gov performance.ddev.site; upgrade-insecure-requests `; @@ -28,18 +27,16 @@ const nextConfig = { async headers() { return [ { - source: '/(.*)', + source: "/(.*)", headers: [ { - key: 'Content-Security-Policy', - value: cspHeader - .replace(/\s{2,}/g, " ") - .trim(), + key: "Content-Security-Policy", + value: cspHeader.replace(/\s{2,}/g, " ").trim(), }, ], }, - ] - } + ]; + }, }; module.exports = nextConfig; diff --git a/src/frontend/styles/style.scss b/src/frontend/styles/style.scss index 3024aa78..1ceb517e 100644 --- a/src/frontend/styles/style.scss +++ b/src/frontend/styles/style.scss @@ -35,3 +35,14 @@ .content-area { flex: 1; } + +.hero { + background-color: #09102a; + background-size: 20px 30px; + background-image: linear-gradient( + to right, + rgba(217, 217, 217, 0.1) 1px, + transparent 1px + ), + linear-gradient(to bottom, rgba(217, 217, 217, 0.1) 1px, transparent 1px); +} From c2fa3bcc39447cfea44c5a1179c1d6ccca9b7d6a Mon Sep 17 00:00:00 2001 From: Adrienne Date: Thu, 6 Feb 2025 16:17:23 -0500 Subject: [PATCH 11/25] Updating php pre-commit checks to exclude frontend folder. --- .githooks/pre-commit | 2 +- src/frontend/next.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 93a71819..e4ee78e5 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -21,7 +21,7 @@ fi # Run the sniffer echo "Running Drupal Coder." echo -PHPCS=("$BIN/phpcs" "--standard=$STANDARD" "--filter=gitstaged" "--encoding=utf-8" "-p" ".") +PHPCS=("$BIN/phpcs" "--standard=$STANDARD" "--filter=gitstaged" "--ignore=*/src/*" "--encoding=utf-8" "-p" ".") "${PHPCS[@]}" VIOLATIONS=$((VIOLATIONS + $?)) diff --git a/src/frontend/next.config.js b/src/frontend/next.config.js index d38a8c6e..da3be876 100644 --- a/src/frontend/next.config.js +++ b/src/frontend/next.config.js @@ -17,7 +17,7 @@ const cspHeader = ` const nextConfig = { images: { domains: [process.env.NEXT_IMAGE_DOMAIN], - unoptimized: true, + unoptimized: TRUE, }, sassOptions: { includePaths: [ From f4e172885e107bdb91749c98807b481dd7ee3025 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Thu, 6 Feb 2025 21:09:10 -0500 Subject: [PATCH 12/25] Search and filter bar styling. --- .../view--goal-search--administration.tsx | 16 ++++++++ .../view--goal-search--fulltext.tsx | 39 ++++++++++--------- src/frontend/components/view--goal-search.tsx | 25 ++++++++---- src/frontend/next.config.js | 2 +- src/frontend/styles/style.scss | 30 ++++++++++++++ src/frontend/styles/uswds-settings.scss | 2 +- 6 files changed, 87 insertions(+), 27 deletions(-) create mode 100644 src/frontend/components/view--goal-search--administration.tsx diff --git a/src/frontend/components/view--goal-search--administration.tsx b/src/frontend/components/view--goal-search--administration.tsx new file mode 100644 index 00000000..91c95e08 --- /dev/null +++ b/src/frontend/components/view--goal-search--administration.tsx @@ -0,0 +1,16 @@ +import { FormEvent } from "react"; + + +export function ViewGoalSearchAdministration() { + return ( +
+ + +
+ ); +} diff --git a/src/frontend/components/view--goal-search--fulltext.tsx b/src/frontend/components/view--goal-search--fulltext.tsx index 1791b919..787cf78e 100644 --- a/src/frontend/components/view--goal-search--fulltext.tsx +++ b/src/frontend/components/view--goal-search--fulltext.tsx @@ -1,5 +1,6 @@ import { FormEvent } from "react"; -import { Search } from "@trussworks/react-uswds"; +import { Icon } from "@trussworks/react-uswds"; + interface ViewGoalSearchFulltextProps { fulltext: string; @@ -12,23 +13,25 @@ export function ViewGoalSearchFulltext({ }: ViewGoalSearchFulltextProps) { return ( -
{handleSearch(e)}}> - - - setFulltext(e.target.value)} - /> - + {handleSearch(e)}}> +
+ + setFulltext(e.target.value)} + /> + +
); } diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index 85ede2cc..6ea2665d 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -6,6 +6,7 @@ import { NodeGoalCard } from "./node--goal--card"; import { ViewGoalSearchFacet } from "./view--goal-search--facet"; import { ViewGoalSearchFulltext } from "./view--goal-search--fulltext"; import { ViewGoalSearchResults } from "./view--goal-search--results"; +import { ViewGoalSearchAdministration } from "./view--goal-search--administration" import ViewGoalFacets from "./view--goal-facets"; interface ViewGoalSearch { @@ -37,6 +38,7 @@ export default function GoalsSearchView({ filters, goals, total, description }: } const { data } = await response.json(); + console.log(data); setDisplayGoals(data?.goalsGraphql1?.results ?? []); setTotalResults(data?.goalsGraphql1?.pageInfo.total); setFacets(data?.goalsGraphql1?.filters[1].options) @@ -54,13 +56,22 @@ export default function GoalsSearchView({ filters, goals, total, description }:

Smart Strategy. Strong Execution.

Data-Driven Updates on America’s Strategic Goals.

-
- - +
+
+ +
+
+ +
+
+ +
diff --git a/src/frontend/next.config.js b/src/frontend/next.config.js index da3be876..d38a8c6e 100644 --- a/src/frontend/next.config.js +++ b/src/frontend/next.config.js @@ -17,7 +17,7 @@ const cspHeader = ` const nextConfig = { images: { domains: [process.env.NEXT_IMAGE_DOMAIN], - unoptimized: TRUE, + unoptimized: true, }, sassOptions: { includePaths: [ diff --git a/src/frontend/styles/style.scss b/src/frontend/styles/style.scss index 1ceb517e..30852fd6 100644 --- a/src/frontend/styles/style.scss +++ b/src/frontend/styles/style.scss @@ -46,3 +46,33 @@ ), linear-gradient(to bottom, rgba(217, 217, 217, 0.1) 1px, transparent 1px); } + +.search-goals { + &--filter { + border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color + } + &--wrapper { + position: relative; + display: flex; // Use flexbox + width: 100%; + input { + padding-left: 3em; + height: 2.5em; + flex: 1; // Make the input take up the remaining space + width: 100%; // Ensure the input spans the full width + border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color + outline: none; // Remove the default browser outline on focus (optional) + } + button { + position: absolute; + top: 0; + left: 0; + border: none; + background-color: transparent; + cursor: pointer; + } + } + &--administration { + border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color + } +} diff --git a/src/frontend/styles/uswds-settings.scss b/src/frontend/styles/uswds-settings.scss index 693c1cff..51d7ccfe 100644 --- a/src/frontend/styles/uswds-settings.scss +++ b/src/frontend/styles/uswds-settings.scss @@ -11,7 +11,7 @@ $ASSET_PREFIX: ""; $theme-banner-max-width: "widescreen", $theme-checkbox-border-radius: "md", /* these are referenced from the root of the /public directory */ - $theme-font-path: "#{$ASSET_PREFIX}/uswds/fonts", + $theme-font-path: "#{$ASSET_PREFIX}/uswds/fonts", $theme-image-path: "#{$ASSET_PREFIX}/uswds/img", $theme-hero-image: "#{$ASSET_PREFIX}/uswds/img/hero.webp" ); From 683afa6caed8a47bd5bcbe92e7a1a4efaae7f413 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Fri, 7 Feb 2025 08:38:18 -0500 Subject: [PATCH 13/25] Search bar styles. --- src/frontend/components/view--goal-search--fulltext.tsx | 2 +- src/frontend/components/view--goal-search.tsx | 9 ++++++--- src/frontend/styles/style.scss | 3 +++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/frontend/components/view--goal-search--fulltext.tsx b/src/frontend/components/view--goal-search--fulltext.tsx index 787cf78e..5a15944a 100644 --- a/src/frontend/components/view--goal-search--fulltext.tsx +++ b/src/frontend/components/view--goal-search--fulltext.tsx @@ -14,7 +14,7 @@ export function ViewGoalSearchFulltext({ return (
{handleSearch(e)}}> -
+
Smart Strategy. Strong Execution.

Data-Driven Updates on America’s Strategic Goals.

-
+
-
@@ -73,6 +73,9 @@ export default function GoalsSearchView({ filters, goals, total, description }:
+
+ +
diff --git a/src/frontend/styles/style.scss b/src/frontend/styles/style.scss index 30852fd6..00200b67 100644 --- a/src/frontend/styles/style.scss +++ b/src/frontend/styles/style.scss @@ -48,6 +48,9 @@ } .search-goals { + &--container { + border-bottom: 1px solid rgba(202, 204, 216, 1); + } &--filter { border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color } From da928bc4149781935dc87b50a682280b1f5c57ba Mon Sep 17 00:00:00 2001 From: Adrienne Date: Sun, 9 Feb 2025 22:16:24 -0500 Subject: [PATCH 14/25] Add plans to search api index, plan card. Filter and search field updates. --- config/search_api.index.goals.yml | 2 + config/views.view.goals.yml | 130 ++++++++++++++++-- src/frontend/components/field--goal-type.tsx | 4 + src/frontend/components/node--goal--card.tsx | 4 +- src/frontend/components/node--plan--card.tsx | 42 ++++++ .../view--goal-search--fulltext.tsx | 2 +- src/frontend/components/view--goal-search.tsx | 18 ++- src/frontend/lib/graphqlQueries.ts | 82 +++++++---- src/frontend/lib/types.ts | 39 ++++-- 9 files changed, 265 insertions(+), 58 deletions(-) create mode 100644 src/frontend/components/node--plan--card.tsx diff --git a/config/search_api.index.goals.yml b/config/search_api.index.goals.yml index f531be4f..fe913390 100644 --- a/config/search_api.index.goals.yml +++ b/config/search_api.index.goals.yml @@ -21,6 +21,7 @@ field_settings: type: union fields: - 'entity:node/body' + - 'entity:node/field_administration' - 'entity:node/title' name: label: 'Topics » Taxonomy term » Name' @@ -38,6 +39,7 @@ datasource_settings: default: false selected: - goal + - plan languages: default: true selected: { } diff --git a/config/views.view.goals.yml b/config/views.view.goals.yml index 8f842b59..dfadffee 100644 --- a/config/views.view.goals.yml +++ b/config/views.view.goals.yml @@ -4,6 +4,7 @@ status: true dependencies: config: - field.storage.node.body + - field.storage.node.field_administration - search_api.index.goals module: - better_exposed_filters @@ -14,7 +15,7 @@ dependencies: id: goals label: Goals module: views -description: "Track the U.S. Government's goals" +description: 'Delivering Results.' tag: '' base_table: search_api_index_goals base_field: search_api_id @@ -238,6 +239,93 @@ display: use_highlighting: false multi_type: separator multi_separator: ', ' + field_administration: + id: field_administration + table: search_api_datasource_goals_entity_node + field: field_administration + relationship: none + group_type: group + admin_label: '' + entity_type: node + plugin_id: search_api_field + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + field_rendering: true + fallback_handler: search_api_entity + fallback_options: + link_to_item: false + use_highlighting: false + multi_type: separator + multi_separator: ', ' + display_methods: + administration: + display_method: label + view_mode: default + indicator: + display_method: label + view_mode: default + measurement: + display_method: label + view_mode: default + period: + display_method: label + view_mode: default + person: + display_method: label + view_mode: default pager: type: mini options: @@ -310,7 +398,20 @@ display: type: search_api_none options: { } empty: { } - sorts: { } + sorts: + aggregated_field: + id: aggregated_field + table: search_api_index_goals + field: aggregated_field + relationship: none + group_type: group + admin_label: '' + plugin_id: search_api + order: ASC + expose: + label: '' + field_identifier: '' + exposed: false arguments: { } filters: aggregated_field: @@ -351,16 +452,19 @@ display: placeholder: '' is_grouped: false group_info: - label: '' - description: '' - identifier: '' + label: 'Aggregated field' + description: null + identifier: aggregated_field optional: true widget: select multiple: false remember: false default_group: All default_group_multiple: { } - group_items: { } + group_items: + 1: { } + 2: { } + 3: { } facets_name: id: facets_name table: search_api_index_goals @@ -410,10 +514,15 @@ display: min_count: 1 show_numbers: true processor_configs: - hide_inactive_siblings_processor: + raw_value_widget_order: weights: - build: 10 - settings: { } + sort: 50 + settings: + sort: ASC + filter_groups: + operator: AND + groups: + 1: AND style: type: default row: @@ -452,6 +561,7 @@ display: - 'user.node_grants:view' tags: - 'config:field.storage.node.body' + - 'config:field.storage.node.field_administration' - 'config:search_api.index.goals' - 'search_api_list:goals' graphql_1: @@ -477,6 +587,7 @@ display: - 'user.node_grants:view' tags: - 'config:field.storage.node.body' + - 'config:field.storage.node.field_administration' - 'config:search_api.index.goals' - 'search_api_list:goals' page_1: @@ -497,5 +608,6 @@ display: - 'user.node_grants:view' tags: - 'config:field.storage.node.body' + - 'config:field.storage.node.field_administration' - 'config:search_api.index.goals' - 'search_api_list:goals' diff --git a/src/frontend/components/field--goal-type.tsx b/src/frontend/components/field--goal-type.tsx index 556e74f9..fdabcfda 100644 --- a/src/frontend/components/field--goal-type.tsx +++ b/src/frontend/components/field--goal-type.tsx @@ -14,6 +14,10 @@ export function FieldGoalType({ field_goal_type }) { goalTypeName = "National"; goalTypeClasses = "bg-base-darkest"; break; + case "plan": + goalTypeName = "Plan"; + goalTypeClasses = "bg-base-darkest"; + break; default: goalTypeName = "Default" goalTypeClasses = "bg-base"; diff --git a/src/frontend/components/node--goal--card.tsx b/src/frontend/components/node--goal--card.tsx index c9bc1f4e..cc495004 100644 --- a/src/frontend/components/node--goal--card.tsx +++ b/src/frontend/components/node--goal--card.tsx @@ -26,13 +26,13 @@ export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) {

{title}

- +
{agencyLogo?.mediaImage.alt} diff --git a/src/frontend/components/node--plan--card.tsx b/src/frontend/components/node--plan--card.tsx new file mode 100644 index 00000000..dfacfc84 --- /dev/null +++ b/src/frontend/components/node--plan--card.tsx @@ -0,0 +1,42 @@ +import Link from 'next/link' +import { NodePlanProps } from "lib/types"; +import Image from "next/image"; +import AgencyInfoBox from './agency-info-box'; +import { FieldGoalType } from './field--goal-type'; +import { FieldPeriod } from './field--period'; + +interface NodePlanCardProps { + goal: NodePlanProps +} + +export function NodePlanCard({ goal, ...props }: NodePlanCardProps) { + const { title, body, path, agency, goalType,period } = goal; + const { acronym: agencyAcronym, logo: agencyLogo, title: agencyTitle } = agency; + return ( +
+ +
+
+
+ + +
+
+

{title}

+
+ +
+
+
+ +
+
+
+ +
+ ); +} diff --git a/src/frontend/components/view--goal-search--fulltext.tsx b/src/frontend/components/view--goal-search--fulltext.tsx index 5a15944a..26f5dbc3 100644 --- a/src/frontend/components/view--goal-search--fulltext.tsx +++ b/src/frontend/components/view--goal-search--fulltext.tsx @@ -29,7 +29,7 @@ export function ViewGoalSearchFulltext({ className="text-bold padding-left-3 padding-y-1" type="submit" > - +
diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index a1fb23d0..ad2f2243 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -1,24 +1,30 @@ import { useState, useEffect, useRef, FormEvent, useCallback } from "react"; import { Button } from "@trussworks/react-uswds"; import Masonry, {ResponsiveMasonry} from "react-responsive-masonry" -import { NodeGoalProps, ViewFilter } from "lib/types"; +import { NodeGoalProps, NodePlanProps, ViewFilter } from "lib/types"; import { NodeGoalCard } from "./node--goal--card"; +import { NodePlanCard } from "./node--plan--card"; import { ViewGoalSearchFacet } from "./view--goal-search--facet"; import { ViewGoalSearchFulltext } from "./view--goal-search--fulltext"; import { ViewGoalSearchResults } from "./view--goal-search--results"; import { ViewGoalSearchAdministration } from "./view--goal-search--administration" import ViewGoalFacets from "./view--goal-facets"; +import goalTotals from "./goal-totals"; interface ViewGoalSearch { - goals: Array, + goals: Array, description: string, filters: Array, total: number } +const isNodeGoalProps = (goal: NodeGoalProps | NodePlanProps): goal is NodeGoalProps => { + return 'goalType' in goal; +}; + export default function GoalsSearchView({ filters, goals, total, description }: ViewGoalSearch) { const offsetAmount = 15; - const [fulltext, setFulltext] = useState(filters[0].value ? filters[0].value : ""); + const [fulltext, setFulltext] = useState(filters[0]?.value ? filters[0].value : ""); const [totalResults, setTotalResults] = useState(total) const [displayGoals, setDisplayGoals] = useState(goals) const [facets, setFacets] = useState(filters[1]?.options ? filters[1].options : []) @@ -74,7 +80,7 @@ export default function GoalsSearchView({ filters, goals, total, description }:
- +
@@ -89,7 +95,11 @@ export default function GoalsSearchView({ filters, goals, total, description }: > {displayGoals.slice(0, offset).map((goal) => ( + isNodeGoalProps(goal) ? ( + ) : ( + + ) ))} diff --git a/src/frontend/lib/graphqlQueries.ts b/src/frontend/lib/graphqlQueries.ts index 235905c9..09aab316 100644 --- a/src/frontend/lib/graphqlQueries.ts +++ b/src/frontend/lib/graphqlQueries.ts @@ -43,7 +43,7 @@ export const graphqlQueries = { targetValue value status - + } } } @@ -156,47 +156,73 @@ export const graphqlQueries = { value } description - results { - ... on NodeGoal { + results { + ... on NodeGoal { + id + title + path + goalType + topics { + ... on TermTopic { id - title - path - goalType - topics { - ... on TermTopic { - id - name - } - } - plan { - ... on NodePlan { + name + } + } + plan { + ... on NodePlan { + id + agency { + ... on NodeAgency { id - agency { - ... on NodeAgency { + acronym + logo { + ... on MediaImage { id - acronym - logo { - ... on MediaImage { - id - name - mediaImage { - url - } - } + name + mediaImage { + url } - title } } + title } } - period { - ... on StoragePeriod { + } + } + period { + ... on StoragePeriod { + id + name + } + } + } + ... on NodePlan { + title + path + agency { + ... on NodeAgency { + id + acronym + logo { + ... on MediaImage { id name + mediaImage { + url + } } } + title + } + } + period { + ... on StoragePeriod { + id + name } } } + } + } }`, }; diff --git a/src/frontend/lib/types.ts b/src/frontend/lib/types.ts index 37e070b5..d9113595 100644 --- a/src/frontend/lib/types.ts +++ b/src/frontend/lib/types.ts @@ -1,22 +1,33 @@ export interface NodeGoalProps { - title: string, - id: string, + title: string; + id: string; body: { - value: string - }, - path: string, + value: string; + }; + path: string; topics: [ { - id: string, - name: string, - } - ], - plan: any, - goalType: any, - period: any, + id: string; + name: string; + }, + ]; + plan: any; + goalType: any; + period: any; +} + +export interface NodePlanProps { + title: string; + id: string; + body: { + value: string; + }; + agency: any; + path: string; + period: any; } export interface ViewFilter { - options: Object, - value: any + options: Object; + value: any; } From 5ae7d1d2bc5245742dd83f6edf622150410365b7 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Sun, 9 Feb 2025 22:22:23 -0500 Subject: [PATCH 15/25] (cherry picked from commit 0da35dfdbb0dc2ea186c42f17db69cb04bacb8f5) --- .bp-config/httpd/httpd.conf | 39 ---- .../httpd/user-provided/httpd-basicauth.conf | 5 - .../user-provided/httpd-drupalsupport.conf | 2 - .bp-config/nginx/server-defaults.conf | 12 -- .bp-config/options.json | 11 - .bp-config/php/php.ini.d/extensions.ini | 8 - .bp-config/php/php.ini.d/memory_limit.ini | 3 - .cfignore | 33 --- .gitignore | 6 +- apt.yml | 7 - bin/deploy-cloudgov.sh | 90 --------- bootstrap.sh | 172 ---------------- ...xt.next_entity_type_config.node.agency.yml | 9 +- ...t.next_entity_type_config.node.article.yml | 9 +- ...next.next_entity_type_config.node.goal.yml | 9 +- ...next.next_entity_type_config.node.plan.yml | 9 +- config/next.next_site.dev.yml | 11 - config/simple_oauth.settings.yml | 4 +- config/system.performance.yml | 4 +- manifest.yml | 29 --- web/template-.htaccess | 190 ------------------ 21 files changed, 10 insertions(+), 652 deletions(-) delete mode 100644 .bp-config/httpd/httpd.conf delete mode 100644 .bp-config/httpd/user-provided/httpd-basicauth.conf delete mode 100644 .bp-config/httpd/user-provided/httpd-drupalsupport.conf delete mode 100644 .bp-config/nginx/server-defaults.conf delete mode 100644 .bp-config/options.json delete mode 100644 .bp-config/php/php.ini.d/extensions.ini delete mode 100644 .bp-config/php/php.ini.d/memory_limit.ini delete mode 100644 .cfignore delete mode 100644 apt.yml delete mode 100644 bin/deploy-cloudgov.sh delete mode 100755 bootstrap.sh delete mode 100644 config/next.next_site.dev.yml delete mode 100644 manifest.yml delete mode 100644 web/template-.htaccess diff --git a/.bp-config/httpd/httpd.conf b/.bp-config/httpd/httpd.conf deleted file mode 100644 index edbb1403..00000000 --- a/.bp-config/httpd/httpd.conf +++ /dev/null @@ -1,39 +0,0 @@ -# Overrides https://github.com/cloudfoundry/php-buildpack/blob/master/defaults/config/httpd/httpd.conf -# to include our customization. -# See https://docs.cloudfoundry.org/buildpacks/php/gsg-php-config.html#engine-configurations for background - -ServerRoot "${HOME}/httpd" -Listen ${PORT} -ServerAdmin "${HTTPD_SERVER_ADMIN}" -ServerName "0.0.0.0" -DocumentRoot "${HOME}/#{WEBDIR}" -Include conf/extra/httpd-modules.conf -Include conf/extra/httpd-directories.conf -Include conf/extra/httpd-mime.conf -Include conf/extra/httpd-deflate.conf -Include conf/extra/httpd-logging.conf -Include conf/extra/httpd-mpm.conf -Include conf/extra/httpd-default.conf -Include conf/extra/httpd-remoteip.conf -Include conf/extra/httpd-php.conf - -# If they exist, include any user-provided customizations -IncludeOptional conf/user-provided/*.conf - - - LoadModule headers_module modules/mod_headers.so - - -RequestHeader unset Proxy early - -# Basic auth - - AuthType Basic - AuthName "Dev site" - AuthUserFile "/home/vcap/app/apache2/.htpasswd" - - Require ip 10.10.2 - Require host localhost:3000 - Require valid-user - - diff --git a/.bp-config/httpd/user-provided/httpd-basicauth.conf b/.bp-config/httpd/user-provided/httpd-basicauth.conf deleted file mode 100644 index 2c85292b..00000000 --- a/.bp-config/httpd/user-provided/httpd-basicauth.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Enable modules needed for http basic auth -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule auth_basic_module modules/mod_auth_basic.so -LoadModule authn_file_module modules/mod_authn_file.so -LoadModule authz_user_module modules/mod_authz_user.so \ No newline at end of file diff --git a/.bp-config/httpd/user-provided/httpd-drupalsupport.conf b/.bp-config/httpd/user-provided/httpd-drupalsupport.conf deleted file mode 100644 index e8638579..00000000 --- a/.bp-config/httpd/user-provided/httpd-drupalsupport.conf +++ /dev/null @@ -1,2 +0,0 @@ -# Enable proxy_http for our s3fs module -LoadModule proxy_http_module modules/mod_proxy_http.so diff --git a/.bp-config/nginx/server-defaults.conf b/.bp-config/nginx/server-defaults.conf deleted file mode 100644 index a82fc2f5..00000000 --- a/.bp-config/nginx/server-defaults.conf +++ /dev/null @@ -1,12 +0,0 @@ - - listen @{PORT}; - server_name _; - - fastcgi_temp_path @{TMPDIR}/nginx_fastcgi 1 2; - client_body_temp_path @{TMPDIR}/nginx_client_body 1 2; - proxy_temp_path @{TMPDIR}/nginx_proxy 1 2; - - real_ip_header x-forwarded-for; - set_real_ip_from 10.0.0.0/8; - real_ip_recursive on; - diff --git a/.bp-config/options.json b/.bp-config/options.json deleted file mode 100644 index 5c8b428b..00000000 --- a/.bp-config/options.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "COMPOSER_INSTALL_OPTIONS": [ - "--no-progress --no-interaction" - ], - "COMPOSER_VENDOR_DIR": "vendor", - "WEBDIR": "web", - "PHP_VERSION": "{PHP_83_LATEST}", - "ADDITIONAL_PREPROCESS_CMDS": [ - "$HOME/bootstrap.sh" - ] -} diff --git a/.bp-config/php/php.ini.d/extensions.ini b/.bp-config/php/php.ini.d/extensions.ini deleted file mode 100644 index e91cd366..00000000 --- a/.bp-config/php/php.ini.d/extensions.ini +++ /dev/null @@ -1,8 +0,0 @@ -extension=apcu -extension=igbinary -extension=imagick -extension=pdo_mysql -extension=redis -extension=mysqli - -zend_extension=opcache.so diff --git a/.bp-config/php/php.ini.d/memory_limit.ini b/.bp-config/php/php.ini.d/memory_limit.ini deleted file mode 100644 index d0c56edb..00000000 --- a/.bp-config/php/php.ini.d/memory_limit.ini +++ /dev/null @@ -1,3 +0,0 @@ -; Maximum amount of memory a script may consume (128MB) -; http://php.net/memory-limit -memory_limit = 1024M diff --git a/.cfignore b/.cfignore deleted file mode 100644 index 3d203d6a..00000000 --- a/.cfignore +++ /dev/null @@ -1,33 +0,0 @@ -# Ignore directories generated by Composer -/drush/contrib/ -/vendor/ -/web/core/ -/web/modules/contrib/ -/web/themes/contrib/ -/web/profiles/contrib/ -/web/libraries/ - -# Ignore node modules from USWDS or otherwise. -node_modules/ - -# Typically, composer generates a .gitignore to ignore the -# `settings.php` files. For cloud.gov and Cloud Foundry, no sensitive -# information is stored in the settings files. Instead, those files -# have code that parses environment variables for DB and S3 -# -# Ignore sensitive information [This is a `composer` default] -# /web/sites/*/settings.php -# /web/sites/*/settings.local.php - - -# Ignore Drupal's file directory -/web/sites/*/files/ - -# Ignore SimpleTest multi-site environment. -/web/sites/simpletest - -# Ignore files generated by PhpStorm -/.idea/ - -.DS_Store -/.ddev/ diff --git a/.gitignore b/.gitignore index 07437db7..cecd9c06 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,9 @@ # Ignore files generated by PhpStorm /.idea/ -# Ignore .env and key files as they are personal +# Ignore .env and keu files as they are personal .env +*.key # dependencies /node_modules @@ -42,8 +43,6 @@ vendor /build /db/ /keys -*.key - # misc .DS_Store @@ -56,4 +55,3 @@ vendor npm-debug.log* yarn-debug.log* yarn-error.log* -*.tsbuildinfo diff --git a/apt.yml b/apt.yml deleted file mode 100644 index 1520331d..00000000 --- a/apt.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -cleancache: true -packages: - - mariadb-client - - nano - - apache2-utils - - sendmail diff --git a/bin/deploy-cloudgov.sh b/bin/deploy-cloudgov.sh deleted file mode 100644 index a34be733..00000000 --- a/bin/deploy-cloudgov.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/sh -# -# This script will attempt to create the services required -# and then launch everything. -# - -# this function will generate a random string, or bail if uuidgen is not available. -generate_string() -{ - if [ -z "$1" ] ; then - if command -v uuidgen >/dev/null ; then - NEW_STRING=$(uuidgen) - export NEW_STRING - else - echo "cannot find uuidgen utility: You will need to generate some random strings and put them in the CRON_KEY, HASH_SALT, and ROOT_USER_PASS environment variables, then re-run this script." - exit 1 - fi - fi -} - -# If the user does not supply required data, generate some secrets. -generate_string "$CRON_KEY" -CRON_KEY=${CRON_KEY:-$NEW_STRING} - -generate_string "$HASH_SALT" -HASH_SALT=${HASH_SALT:-$NEW_STRING} - -generate_string "$ROOT_USER_PASS" -ROOT_USER_PASS=${ROOT_USER_PASS:-$NEW_STRING} - -ROOT_USER_NAME=${ROOT_USER_NAME:-root} - - -# function to check if a service exists -service_exists() -{ - cf service "$1" >/dev/null 2>&1 -} - -# create services (if needed) -if service_exists "database" ; then - echo database already created -else - if [ "$1" = "prod" ] ; then - cf create-service aws-rds medium-mysql-redundant database - else - cf create-service aws-rds small-mysql database - fi -fi - -if service_exists "secrets" ; then - echo secrets already created -else - yes '' | cf create-user-provided-service secrets -p "{\"CRON_KEY\": \"$CRON_KEY\", \"HASH_SALT\": \"$HASH_SALT\", \"ROOT_USER_NAME\": \"$ROOT_USER_NAME\", \"ROOT_USER_PASS\": \"$ROOT_USER_PASS\"}" -fi - -if service_exists "storage" ; then - echo storage already created -else - cf create-service s3 basic-sandbox storage -fi - -# wait until the db is fully provisioned -until cf create-service-key database test-db-ok ; do - echo waiting until database is live... - sleep 20 -done -cf delete-service-key database test-db-ok -f - -# make the bootstrap script runnable -chmod +x ./bootstrap.sh - -# launch the apps -cf push - -# make sure that the app knows where it's s3fs stuff lives -cf create-service-key storage storagekey -S3INFO=$(cf service-key storage storagekey) -S3_BUCKET=$(echo "$S3INFO" | grep '"bucket":' | sed 's/.*"bucket": "\(.*\)",/\1/') -S3_REGION=$(echo "$S3INFO" | grep '"region":' | sed 's/.*"region": "\(.*\)",/\1/') -cf set-env PGOV-CMS S3_BUCKET "$S3_BUCKET" -cf set-env PGOV-CMS S3_REGION "$S3_REGION" -cf delete-service-key storage storagekey -f -cf restart PGOV-CMS - -# tell people where to go -ROUTE=$(cf apps | grep PGOV-CMS | awk '{print $4}') -echo -echo -echo "To log into the drupal site, you will want to go to https://${ROUTE}/user/login and enter your username/password." diff --git a/bootstrap.sh b/bootstrap.sh deleted file mode 100755 index c9c97f89..00000000 --- a/bootstrap.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Initialize our own variables: -verbose=0 -noop=0 - -# Check function -while getopts "v:nh" opt; do - case "$opt" in - v) verbose=$OPTARG - ;; - n) noop=1 - ;; - h) manpage=1 - ;; - esac -done - -shift $((OPTIND-1)) - -# [ "$1" = "--" ] && shift - -if [ ! -z "${manpage+isset}" ]; -then - printf "Performance.gov bootstrap script -Options: - -v N Verbosity level - -n No Op - dry run - -h Help - print this text\n\n" - exit 0 -fi - - export home="/home/vcap" - export app_path="${home}/app" - export apt_path="${home}/deps/0/apt" - - ## Updated ~/.bashrc to update $PATH - [ -z $(cat ${home}/.bashrc | grep PATH) ] && \ - touch ${home}/.bashrc && \ - echo "alias nano=\"${home}/deps/0/apt/bin/nano\"" >> ${home}/.bashrc - echo "PATH=$PATH:/home/vcap/app/php/bin:/home/vcap/app/vendor/drush/drush" >> /home/vcap/.bashrc - - source ${home}/.bashrc - -## App Info -SECRETS=$(echo "$VCAP_SERVICES" | jq -r '.["user-provided"][] | select(.name == "secrets") | .credentials') -APP_NAME=$(echo "$VCAP_APPLICATION" | jq -r '.name') -APP_ROOT=$(dirname "${BASH_SOURCE[0]}") -DOC_ROOT="$APP_ROOT/web" -APP_ID=$(echo "$VCAP_APPLICATION" | jq -r '.application_id') -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] -then - printf "\nApp Info\n" -fi -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 2 ] -then - for appinfo in SECRETS APP_NAME APP_ROOT DOC_ROOT APP_ID - do - printf "%s :\t%s\n" "${appinfo}" "${!appinfo}" - done -fi - -## DB Info -DB_NAME=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.db_name') -DB_USER=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.username') -DB_PW=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.password') -DB_HOST=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.host') -DB_PORT=$(echo "$VCAP_SERVICES" | jq -r '.["aws-rds"][] | .credentials.port') -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] -then - printf "\nDB Info\n" -fi -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 2 ] -then - for appinfo in DB_NAME DB_USER DB_PW DB_HOST DB_PORT - do - printf "%s :\t%s\n" "${appinfo}" "${!appinfo}" - done -fi - -## S3 Info -S3_BUCKET=$(echo "$VCAP_SERVICES" | jq -r '.["s3"][]? | select(.name == "storage") | .credentials.bucket') -export S3_BUCKET -S3_REGION=$(echo "$VCAP_SERVICES" | jq -r '.["s3"][]? | select(.name == "storage") | .credentials.region') -export S3_REGION -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] -then - printf "\nS3 Info\n" -fi -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 2 ] -then - for appinfo in S3_BUCKET S3_REGION - do - printf "%s :\t%s\n" "${appinfo}" "${!appinfo}" - done -fi - -if [ -n "$S3_BUCKET" ] && [ -n "$S3_REGION" ]; then - # Add Proxy rewrite rules to the top of the htaccess file - sed "s/^#RewriteRule .s3fs/RewriteRule ^s3fs/" "$DOC_ROOT/template-.htaccess" > "$DOC_ROOT/.htaccess" -else - cp "$DOC_ROOT/template-.htaccess" "$DOC_ROOT/.htaccess" -fi - -install_drupal() { - if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] - then - printf "\ninstall_drupal called on %s\n" "${APP_ID}" - fi - - ROOT_USER_NAME=$(echo "$SECRETS" | jq -r '.ROOT_USER_NAME') - ROOT_USER_PASS=$(echo "$SECRETS" | jq -r '.ROOT_USER_PASS') - - : "${ROOT_USER_NAME:?Need root user name for Drupal}" - : "${ROOT_USER_PASS:?Need root user pass for Drupal}" - - if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] - then - printf "\nRunning drush site-install\n" - fi - drush site-install minimal --existing-config \ - --db-url="mysql://$DB_USER:$DB_PW@$DB_HOST:$DB_PORT/$DB_NAME" \ - --account-name="$ROOT_USER_NAME" \ - --account-pass="$ROOT_USER_PASS" \ - -y - - if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] - then - printf "\nImporting previous database\n" - fi - drush sql-drop -y - drush sql-cli < ../db/performancedb.sql - - # Set site uuid to match our config - if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] - then - printf "\nSet Site UUID\n" - fi - UUID=$(grep uuid ../config/system.site.yml | cut -d' ' -f2) - drush config:set "system.site" uuid "$UUID" --yes - if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] - then - printf "\nSite UUID set to: %s\n" "${UUID}" - fi -} - -# Go into the Drupal web root directory -cd "$DOC_ROOT" - -# If there is no "config:import" command, Drupal needs to be installed -drush list | grep "config:import" > /dev/null || install_drupal - -# Clear the cache -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] -then - printf "\nRebuild cache and update DB\n" -fi - -drush deploy - - # Set basic auth credentials - chmod 775 /home/vcap/app/apache2 - htpasswd -b -c /home/vcap/app/apache2/.htpasswd admin civicactions - - # Restart nginx -# service nginx restart - -if [ "${verbose+isset}" ] && [ "${verbose}" -ge 1 ] -then - printf "\nDone\n" -fi diff --git a/config/next.next_entity_type_config.node.agency.yml b/config/next.next_entity_type_config.node.agency.yml index 02be2f6e..addffea8 100644 --- a/config/next.next_entity_type_config.node.agency.yml +++ b/config/next.next_entity_type_config.node.agency.yml @@ -1,18 +1,11 @@ uuid: dc5778e1-02cd-4642-b866-0afc577b5e79 langcode: en status: true -dependencies: - module: - - next_extras -third_party_settings: - next_extras: - revalidate: false - revalidate_paths: '' +dependencies: { } id: node.agency site_resolver: site_selector configuration: sites: - dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_entity_type_config.node.article.yml b/config/next.next_entity_type_config.node.article.yml index e7bf82a9..6c4f6ffc 100644 --- a/config/next.next_entity_type_config.node.article.yml +++ b/config/next.next_entity_type_config.node.article.yml @@ -1,18 +1,11 @@ uuid: a21a30ff-a5cc-4e19-8612-6bc34ae9c247 langcode: en status: true -dependencies: - module: - - next_extras -third_party_settings: - next_extras: - revalidate: false - revalidate_paths: '' +dependencies: { } id: node.article site_resolver: site_selector configuration: sites: - dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_entity_type_config.node.goal.yml b/config/next.next_entity_type_config.node.goal.yml index 7738d287..c0447867 100644 --- a/config/next.next_entity_type_config.node.goal.yml +++ b/config/next.next_entity_type_config.node.goal.yml @@ -1,18 +1,11 @@ uuid: 8625f358-9ad4-47a6-aa18-c63bb4445bf1 langcode: en status: true -dependencies: - module: - - next_extras -third_party_settings: - next_extras: - revalidate: false - revalidate_paths: '' +dependencies: { } id: node.goal site_resolver: site_selector configuration: sites: - dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_entity_type_config.node.plan.yml b/config/next.next_entity_type_config.node.plan.yml index 10a2ba64..55aec481 100644 --- a/config/next.next_entity_type_config.node.plan.yml +++ b/config/next.next_entity_type_config.node.plan.yml @@ -1,18 +1,11 @@ uuid: f6278e3b-8631-4309-aaae-f01393841e5b langcode: en status: true -dependencies: - module: - - next_extras -third_party_settings: - next_extras: - revalidate: false - revalidate_paths: '' +dependencies: { } id: node.plan site_resolver: site_selector configuration: sites: - dev: dev pgov_frontend: pgov_frontend revalidator: '' revalidator_configuration: { } diff --git a/config/next.next_site.dev.yml b/config/next.next_site.dev.yml deleted file mode 100644 index f4b82788..00000000 --- a/config/next.next_site.dev.yml +++ /dev/null @@ -1,11 +0,0 @@ -uuid: 8acf35ef-4a5b-4b35-874d-6e6562ce8d43 -langcode: en -status: true -dependencies: { } -id: dev -label: Dev -base_url: 'https://pgov-frontend.app.cloud.gov/' -preview_url: 'https://pgov-frontend.app.cloud.gov/api/draft' -preview_secret: secret -revalidate_url: '' -revalidate_secret: '' diff --git a/config/simple_oauth.settings.yml b/config/simple_oauth.settings.yml index dfb3a3a2..0cffd710 100644 --- a/config/simple_oauth.settings.yml +++ b/config/simple_oauth.settings.yml @@ -4,8 +4,8 @@ access_token_expiration: 300 authorization_code_expiration: 300 refresh_token_expiration: 1209600 token_cron_batch_size: 0 -public_key: ../keys/public.key -private_key: ../keys/private.key +public_key: ../public.key +private_key: ../private.key remember_clients: true use_implicit: false disable_openid_connect: false diff --git a/config/system.performance.yml b/config/system.performance.yml index 61f4e153..2219cf10 100644 --- a/config/system.performance.yml +++ b/config/system.performance.yml @@ -4,7 +4,7 @@ cache: page: max_age: 0 css: - preprocess: false + preprocess: true gzip: true fast_404: enabled: true @@ -12,5 +12,5 @@ fast_404: exclude_paths: '/\/(?:styles|imagecache)\//' html: '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

' js: - preprocess: false + preprocess: true gzip: true diff --git a/manifest.yml b/manifest.yml deleted file mode 100644 index d4ad3dc2..00000000 --- a/manifest.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -version: 1 -default_config: &defaults - buildpacks: - - https://github.com/cloudfoundry/apt-buildpack - - php_buildpack - disk_quota: 4G - stack: cflinuxfs4 - timeout: 180 - services: - - database # cf create-service aws-rds shared-mysql database - - secrets # cf create-user-provided-service secrets -p '{ - # "CRON_KEY": ... - # "HASH_SALT": ... - # "ROOT_USER_NAME": ..., - # "ROOT_USER_PASS": ..., - # }' - - storage # cf create-service s3 basic-sandbox storage - -applications: - - name: PGOV-CMS - <<: *defaults - memory: 512M - instances: 1 - - name: PGOV-Frontend - path: ./src/frontend - memory: 256M - buildpacks: - - nodejs_buildpack diff --git a/web/template-.htaccess b/web/template-.htaccess deleted file mode 100644 index 062d08e5..00000000 --- a/web/template-.htaccess +++ /dev/null @@ -1,190 +0,0 @@ -# -# Apache/PHP/Drupal settings: -# -#RewriteRule ^s3fs-(css|js)/(.*)$ http://${S3_BUCKET}.s3-${S3_REGION}.amazonaws.com/public/$2 [P] - -# Protect files and directories from prying eyes. - - - Require all denied - - - Order allow,deny - - - -# Don't show directory listings for URLs which map to a directory. -Options -Indexes - -# Set the default handler. -DirectoryIndex index.php index.html index.htm - -# Add correct encoding for SVGZ. -AddType image/svg+xml svg svgz -AddEncoding gzip svgz - -# Most of the following PHP settings cannot be changed at runtime. See -# sites/default/default.settings.php and -# Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be -# changed at runtime. - -# PHP 5, Apache 1 and 2. - - php_value assert.active 0 - php_flag session.auto_start off - php_value mbstring.http_input pass - php_value mbstring.http_output pass - php_flag mbstring.encoding_translation off - # PHP 5.6 has deprecated $HTTP_RAW_POST_DATA and produces warnings if this is - # not set. - php_value always_populate_raw_post_data -1 - - -# Requires mod_expires to be enabled. - - # Enable expirations. - ExpiresActive On - - # Cache all files for 2 weeks after access (A). - ExpiresDefault A1209600 - - - # Do not allow PHP scripts to be cached unless they explicitly send cache - # headers themselves. Otherwise all scripts would have to overwrite the - # headers set by mod_expires if they want another caching behavior. This may - # fail if an error occurs early in the bootstrap process, and it may cause - # problems if a non-Drupal PHP file is installed in a subdirectory. - ExpiresActive Off - - - -# Set a fallback resource if mod_rewrite is not enabled. This allows Drupal to -# work without clean URLs. This requires Apache version >= 2.2.16. If Drupal is -# not accessed by the top level URL (i.e.: http://example.com/drupal/ instead of -# http://example.com/), the path to index.php will need to be adjusted. - - FallbackResource /index.php - - -# Various rewrite rules. - - RewriteEngine on - - # Set "protossl" to "s" if we were accessed via https://. This is used later - # if you enable "www." stripping or enforcement, in order to ensure that - # you don't bounce between http and https. - RewriteRule ^ - [E=protossl] - RewriteCond %{HTTPS} on - RewriteRule ^ - [E=protossl:s] - - # Make sure Authorization HTTP header is available to PHP - # even when running as CGI or FastCGI. - RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] - - # Block access to "hidden" directories whose names begin with a period. This - # includes directories used by version control systems such as Subversion or - # Git to store control files. Files whose names begin with a period, as well - # as the control files used by CVS, are protected by the FilesMatch directive - # above. - # - # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is - # not possible to block access to entire directories from .htaccess because - # is not allowed here. - # - # If you do not have mod_rewrite installed, you should remove these - # directories from your webroot or otherwise protect them from being - # downloaded. - RewriteRule "/\.|^\.(?!well-known/)" - [F] - - # If your site can be accessed both with and without the 'www.' prefix, you - # can use one of the following settings to redirect users to your preferred - # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: - # - # To redirect all users to access the site WITH the 'www.' prefix, - # (http://example.com/foo will be redirected to http://www.example.com/foo) - # uncomment the following: - # RewriteCond %{HTTP_HOST} . - # RewriteCond %{HTTP_HOST} !^www\. [NC] - # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] - # - # To redirect all users to access the site WITHOUT the 'www.' prefix, - # (http://www.example.com/foo will be redirected to http://example.com/foo) - # uncomment the following: - # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] - - # Modify the RewriteBase if you are using Drupal in a subdirectory or in a - # VirtualDocumentRoot and the rewrite rules are not working properly. - # For example if your site is at http://example.com/drupal uncomment and - # modify the following line: - # RewriteBase /drupal - # - # If your site is running in a VirtualDocumentRoot at http://example.com/, - # uncomment the following line: - # RewriteBase / - - # Redirect common PHP files to their new locations. - RewriteCond %{REQUEST_URI} ^(.*)?/(install.php) [OR] - RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild.php) - RewriteCond %{REQUEST_URI} !core - RewriteRule ^ %1/core/%2 [L,QSA,R=301] - - # Rewrite install.php during installation to see if mod_rewrite is working - RewriteRule ^core/install.php core/install.php?rewrite=ok [QSA,L] - - # Pass all requests not referring directly to files in the filesystem to - # index.php. - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^ index.php [L] - - # For security reasons, deny access to other PHP files on public sites. - # Note: The following URI conditions are not anchored at the start (^), - # because Drupal may be located in a subdirectory. To further improve - # security, you can replace '!/' with '!^/'. - # Allow access to PHP files in /core (like authorize.php or install.php): - RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$ - # Allow access to test-specific PHP files: - RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php - # Allow access to Statistics module's custom front controller. - # Copy and adapt this rule to directly execute PHP files in contributed or - # custom modules or to run another PHP application in the same directory. - RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$ - # Deny access to any other PHP files that do not match the rules above. - # Specifically, disallow autoload.php from being served directly. - RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F] - - # Rules to correctly serve gzip compressed CSS and JS files. - # Requires both mod_rewrite and mod_headers to be enabled. - - # Serve gzip compressed CSS files if they exist and the client accepts gzip. - RewriteCond %{HTTP:Accept-encoding} gzip - RewriteCond %{REQUEST_FILENAME}\.gz -s - RewriteRule ^(.*)\.css $1\.css\.gz [QSA] - - # Serve gzip compressed JS files if they exist and the client accepts gzip. - RewriteCond %{HTTP:Accept-encoding} gzip - RewriteCond %{REQUEST_FILENAME}\.gz -s - RewriteRule ^(.*)\.js $1\.js\.gz [QSA] - - # Serve correct content types, and prevent mod_deflate double gzip. - RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1] - RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1] - - - # Serve correct encoding type. - Header set Content-Encoding gzip - # Force proxies to cache gzipped & non-gzipped css/js files separately. - Header append Vary Accept-Encoding - - - - -# Various header fixes. - - # Disable content sniffing, since it's an attack vector. - Header always set X-Content-Type-Options nosniff - # Disable Proxy header, since it's an attack vector. - RequestHeader unset Proxy - From a32a1bc11625dbda5af104eea58dd85fe5a59753 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Sun, 9 Feb 2025 22:30:22 -0500 Subject: [PATCH 16/25] Revert uswds settings change. --- src/frontend/package-lock.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 3c360884..0a6b953f 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -450,7 +450,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "detect-libc": "^1.0.3", @@ -736,7 +736,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "detect-libc": "bin/detect-libc.js" @@ -749,7 +749,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@pkgjs/parseargs": { @@ -1846,7 +1846,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -1993,7 +1993,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -3451,7 +3451,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3917,7 +3917,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/import-fresh": { @@ -4151,7 +4151,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4200,7 +4200,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4239,7 +4239,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4948,7 +4948,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5523,7 +5523,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5850,7 +5850,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 14.16.0" @@ -6155,7 +6155,7 @@ "version": "1.79.5", "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz", "integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@parcel/watcher": "^2.4.1", @@ -6854,7 +6854,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" From 657adb1975159fe6294795c9ce148220e7e1980d Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 01:12:21 -0500 Subject: [PATCH 17/25] Search index includes agency title. Biden goals filtered out. Correct admins in dropdown. --- ...ore.entity_view_display.node.goal.card.yml | 4 +- ....entity_view_display.node.goal.default.yml | 4 +- ...h_api__views_graphql__goals__graphql_1.yml | 9 + config/search_api.index.goals.yml | 11 +- config/simple_oauth.settings.yml | 4 +- config/views.view.goals.yml | 212 ++++++++++++++++++ src/frontend/components/node--goal--card.tsx | 6 +- src/frontend/components/view--goal-facets.tsx | 1 - .../view--goal-search--administration.tsx | 5 +- src/frontend/lib/graphqlQueries.ts | 13 ++ src/frontend/pages/api/goal-search.ts | 11 +- 11 files changed, 261 insertions(+), 19 deletions(-) create mode 100644 config/facets.facet_source.search_api__views_graphql__goals__graphql_1.yml diff --git a/config/core.entity_view_display.node.goal.card.yml b/config/core.entity_view_display.node.goal.card.yml index 1343be7d..ac309ace 100644 --- a/config/core.entity_view_display.node.goal.card.yml +++ b/config/core.entity_view_display.node.goal.card.yml @@ -13,7 +13,7 @@ dependencies: - field.field.node.goal.field_plan - field.field.node.goal.field_sequence - field.field.node.goal.field_topics - - image.style.1x1_third + - image.style.third_1x1 - node.type.goal module: - media @@ -28,7 +28,7 @@ content: label: hidden settings: image_link: content - image_style: 1x1_third + image_style: third_1x1 image_loading: attribute: lazy third_party_settings: { } diff --git a/config/core.entity_view_display.node.goal.default.yml b/config/core.entity_view_display.node.goal.default.yml index 4dccec97..d08e19c6 100644 --- a/config/core.entity_view_display.node.goal.default.yml +++ b/config/core.entity_view_display.node.goal.default.yml @@ -12,7 +12,7 @@ dependencies: - field.field.node.goal.field_plan - field.field.node.goal.field_sequence - field.field.node.goal.field_topics - - image.style.1x1_third + - image.style.third_1x1 - node.type.goal module: - media @@ -43,7 +43,7 @@ content: label: hidden settings: image_link: content - image_style: 1x1_third + image_style: third_1x1 image_loading: attribute: lazy third_party_settings: { } diff --git a/config/facets.facet_source.search_api__views_graphql__goals__graphql_1.yml b/config/facets.facet_source.search_api__views_graphql__goals__graphql_1.yml new file mode 100644 index 00000000..d502fbf4 --- /dev/null +++ b/config/facets.facet_source.search_api__views_graphql__goals__graphql_1.yml @@ -0,0 +1,9 @@ +uuid: 28224ac1-e739-4896-876f-87f5e3cd0883 +langcode: en +status: true +dependencies: { } +id: search_api__views_graphql__goals__graphql_1 +name: 'search_api:views_graphql__goals__graphql_1' +filter_key: null +url_processor: query_string +breadcrumb: { } diff --git a/config/search_api.index.goals.yml b/config/search_api.index.goals.yml index fe913390..8f876791 100644 --- a/config/search_api.index.goals.yml +++ b/config/search_api.index.goals.yml @@ -3,6 +3,7 @@ langcode: en status: true dependencies: config: + - field.storage.node.field_administration - field.storage.node.field_topics - search_api.server.database_search module: @@ -21,8 +22,16 @@ field_settings: type: union fields: - 'entity:node/body' - - 'entity:node/field_administration' + - 'entity:node/field_agency' - 'entity:node/title' + field_administration: + label: Administration + datasource_id: 'entity:node' + property_path: field_administration + type: integer + dependencies: + config: + - field.storage.node.field_administration name: label: 'Topics » Taxonomy term » Name' datasource_id: 'entity:node' diff --git a/config/simple_oauth.settings.yml b/config/simple_oauth.settings.yml index 0cffd710..dfb3a3a2 100644 --- a/config/simple_oauth.settings.yml +++ b/config/simple_oauth.settings.yml @@ -4,8 +4,8 @@ access_token_expiration: 300 authorization_code_expiration: 300 refresh_token_expiration: 1209600 token_cron_batch_size: 0 -public_key: ../public.key -private_key: ../private.key +public_key: ../keys/public.key +private_key: ../keys/private.key remember_clients: true use_implicit: false disable_openid_connect: false diff --git a/config/views.view.goals.yml b/config/views.view.goals.yml index dfadffee..eda3fe4a 100644 --- a/config/views.view.goals.yml +++ b/config/views.view.goals.yml @@ -514,6 +514,10 @@ display: min_count: 1 show_numbers: true processor_configs: + combine_processor: + weights: + build: 5 + settings: { } raw_value_widget_order: weights: sort: 50 @@ -574,9 +578,217 @@ display: type: none options: offset: 0 + filters: + aggregated_field: + id: aggregated_field + table: search_api_index_goals + field: aggregated_field + relationship: none + group_type: group + admin_label: '' + plugin_id: search_api_string + operator: '=' + value: + min: '' + max: '' + value: '' + group: 1 + exposed: true + expose: + operator_id: aggregated_field_op + label: 'Aggregated field' + description: '' + use_operator: false + operator: aggregated_field_op + operator_limit_selection: false + operator_list: { } + identifier: aggregated_field + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + content_editor: '0' + administrator: '0' + next_js_site: '0' + min_placeholder: '' + max_placeholder: '' + placeholder: '' + is_grouped: false + group_info: + label: 'Aggregated field' + description: null + identifier: aggregated_field + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: { } + 2: { } + 3: { } + facets_name: + id: facets_name + table: search_api_index_goals + field: facets_name + relationship: none + group_type: group + admin_label: '' + plugin_id: facets_filter + operator: '=' + value: '' + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Topics » Taxonomy term » Name' + description: '' + use_operator: false + operator: '' + operator_limit_selection: false + operator_list: { } + identifier: Topics + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + content_editor: '0' + administrator: '0' + next_js_site: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + hierarchy: false + label_display: visible + facet: + query_operator: or + min_count: 1 + show_numbers: true + processor_configs: + raw_value_widget_order: + weights: + sort: 50 + settings: + sort: ASC + field_administration: + id: field_administration + table: search_api_index_goals + field: field_administration + relationship: none + group_type: group + admin_label: '' + plugin_id: search_api_numeric + operator: '!=' + value: + min: '' + max: '' + value: '54' + group: 1 + exposed: false + expose: + operator_id: field_administration_op + label: Administration + description: null + use_operator: false + operator: field_administration_op + operator_limit_selection: false + operator_list: { } + identifier: field_administration + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + min_placeholder: null + max_placeholder: null + placeholder: null + is_grouped: false + group_info: + label: Administration + description: null + identifier: field_administration + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: { } + 2: { } + 3: { } + field_administration_1: + id: field_administration_1 + table: search_api_index_goals + field: field_administration + relationship: none + group_type: group + admin_label: '' + plugin_id: search_api_numeric + operator: '=' + value: + min: '' + max: '' + value: '' + group: 1 + exposed: true + expose: + operator_id: field_administration_1_op + label: Administration + description: '' + use_operator: false + operator: field_administration_1_op + operator_limit_selection: false + operator_list: { } + identifier: Administration + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + content_editor: '0' + administrator: '0' + next_js_site: '0' + min_placeholder: '' + max_placeholder: '' + placeholder: '' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + filter_groups: + operator: AND + groups: + 1: AND row: type: graphql_entity options: { } + defaults: + filters: false + filter_groups: false display_extenders: { } cache_metadata: max-age: -1 diff --git a/src/frontend/components/node--goal--card.tsx b/src/frontend/components/node--goal--card.tsx index cc495004..8f2affb1 100644 --- a/src/frontend/components/node--goal--card.tsx +++ b/src/frontend/components/node--goal--card.tsx @@ -11,7 +11,7 @@ interface NodeGoalCardProps { } export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) { - const { title, body, path, plan, goalType,period } = goal; + const { title, body, path, plan, goalType,period, image } = goal; const { agency } = plan; const { acronym: agencyAcronym, logo: agencyLogo, title: agencyTitle } = agency; return ( @@ -29,10 +29,10 @@ export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) {
{agencyTitle} diff --git a/src/frontend/components/view--goal-facets.tsx b/src/frontend/components/view--goal-facets.tsx index c0fba752..ac20b698 100644 --- a/src/frontend/components/view--goal-facets.tsx +++ b/src/frontend/components/view--goal-facets.tsx @@ -35,7 +35,6 @@ const ViewGoalFacets = ({filter_options, handleSearch}) => { value={facetKey} onChange={() => handleCheck(facetKey)} checked={checkedFacets.includes(facetKey)} - disabled={checkedFacets.length > 0 && !checkedFacets.includes(facetKey) ? true : false} /> ); diff --git a/src/frontend/lib/graphqlQueries.ts b/src/frontend/lib/graphqlQueries.ts index 09aab316..c3f7589f 100644 --- a/src/frontend/lib/graphqlQueries.ts +++ b/src/frontend/lib/graphqlQueries.ts @@ -168,6 +168,19 @@ export const graphqlQueries = { name } } + image { + ... on MediaImage { + id + name + mediaImage { + alt + title + variations(styles: THIRD1X1) { + url + } + } + } + } plan { ... on NodePlan { id diff --git a/src/frontend/pages/api/goal-search.ts b/src/frontend/pages/api/goal-search.ts index 93a52d21..7cafe489 100644 --- a/src/frontend/pages/api/goal-search.ts +++ b/src/frontend/pages/api/goal-search.ts @@ -1,19 +1,20 @@ -import type { NextApiRequest, NextApiResponse } from 'next' +import type { NextApiRequest, NextApiResponse } from "next"; import { drupal } from "lib/drupal"; import { graphqlQueries } from "lib/graphqlQueries"; type ResponseData = { message: string; data: any; -} - +}; + export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse, ) { const graphqlUrl = drupal.buildUrl("/graphql"); const fulltext = req.query.fulltext ? req.query.fulltext : ""; const facets = req.query.facets ? req.query.facets : []; + console.log(facets); const response = await drupal.fetch(graphqlUrl.toString(), { method: "POST", withAuth: true, // Make authenticated requests using OAuth. @@ -22,5 +23,5 @@ export default async function handler( }), }); const { data } = await response.json(); - res.status(200).json({ message: 'Hello from Next.js!', data: data }) + res.status(200).json({ message: "Hello from Next.js!", data: data }); } From f169127c361933cfc982cdcf5e28557b52cb0039 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 01:26:10 -0500 Subject: [PATCH 18/25] Update card styling, sort topic filters alphabetically. --- src/frontend/.gitignore | 3 ++- src/frontend/components/field--period.tsx | 4 ++-- src/frontend/components/node--goal--card.tsx | 20 ++++++++++--------- src/frontend/components/node--plan--card.tsx | 2 +- src/frontend/components/view--goal-facets.tsx | 1 + 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/frontend/.gitignore b/src/frontend/.gitignore index 78adc9ed..8d108138 100644 --- a/src/frontend/.gitignore +++ b/src/frontend/.gitignore @@ -25,6 +25,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* +*.tsbuildinfo # local env files .env.local @@ -35,4 +36,4 @@ lerna-debug.log* # vercel .vercel -/certificates/* \ No newline at end of file +/certificates/* diff --git a/src/frontend/components/field--period.tsx b/src/frontend/components/field--period.tsx index f2c0a262..e4a99608 100644 --- a/src/frontend/components/field--period.tsx +++ b/src/frontend/components/field--period.tsx @@ -1,14 +1,14 @@ export function FieldPeriod({field_period}) { if (!field_period?.name) { return ( - + default ) } let period = field_period.name.startsWith("FY") ? field_period.name.replace(/^[^0-9]+/, '') : field_period.name; return ( - + {period} ); diff --git a/src/frontend/components/node--goal--card.tsx b/src/frontend/components/node--goal--card.tsx index 8f2affb1..d3f53d5b 100644 --- a/src/frontend/components/node--goal--card.tsx +++ b/src/frontend/components/node--goal--card.tsx @@ -19,7 +19,7 @@ export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) {
-
+
@@ -28,14 +28,16 @@ export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) {
- {title} + {image?.mediaImage?.url && ( + {title} + )}
-
+
diff --git a/src/frontend/components/view--goal-facets.tsx b/src/frontend/components/view--goal-facets.tsx index ac20b698..615ec019 100644 --- a/src/frontend/components/view--goal-facets.tsx +++ b/src/frontend/components/view--goal-facets.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; const ViewGoalFacets = ({filter_options, handleSearch}) => { const facetKeys = filter_options ? Object.keys(filter_options) : []; + facetKeys.sort(); const [checkedFacets, setCheckedFacets] = useState([]) From 8e3418d4146c2fd9ffdd35aa2254d29203e30482 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 02:25:51 -0500 Subject: [PATCH 19/25] Working administration filter. --- src/frontend/components/view--goal-facets.tsx | 16 +++++++--------- .../view--goal-search--administration.tsx | 15 --------------- src/frontend/components/view--goal-search.tsx | 17 ++++++++++++----- src/frontend/lib/graphqlQueries.ts | 7 ++++++- src/frontend/pages/api/goal-search.ts | 6 ++++-- src/frontend/styles/style.scss | 19 +++++++++++++------ 6 files changed, 42 insertions(+), 38 deletions(-) delete mode 100644 src/frontend/components/view--goal-search--administration.tsx diff --git a/src/frontend/components/view--goal-facets.tsx b/src/frontend/components/view--goal-facets.tsx index 615ec019..3fd54249 100644 --- a/src/frontend/components/view--goal-facets.tsx +++ b/src/frontend/components/view--goal-facets.tsx @@ -6,17 +6,15 @@ const ViewGoalFacets = ({filter_options, handleSearch}) => { const [checkedFacets, setCheckedFacets] = useState([]) + const removeFacet = (facetToRemove) => { + setCheckedFacets(checkedFacets.filter((facetKey) => facetKey !== facetToRemove)); + }; + function handleCheck(facetKey) { - if (checkedFacets.includes(facetKey)) { - const newFacetList = checkedFacets.filter((facet) => { - if (facet == facetKey) { - return null; - } - return facet; - }) - setCheckedFacets(newFacetList) - } else { + if (!checkedFacets.includes(facetKey)) { setCheckedFacets([...checkedFacets, facetKey]) + } else { + removeFacet(facetKey); } } diff --git a/src/frontend/components/view--goal-search--administration.tsx b/src/frontend/components/view--goal-search--administration.tsx deleted file mode 100644 index 94643091..00000000 --- a/src/frontend/components/view--goal-search--administration.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FormEvent } from "react"; - - -export function ViewGoalSearchAdministration() { - return ( -
- - -
- ); -} diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index ad2f2243..9051c367 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -7,7 +7,6 @@ import { NodePlanCard } from "./node--plan--card"; import { ViewGoalSearchFacet } from "./view--goal-search--facet"; import { ViewGoalSearchFulltext } from "./view--goal-search--fulltext"; import { ViewGoalSearchResults } from "./view--goal-search--results"; -import { ViewGoalSearchAdministration } from "./view--goal-search--administration" import ViewGoalFacets from "./view--goal-facets"; import goalTotals from "./goal-totals"; @@ -25,6 +24,7 @@ const isNodeGoalProps = (goal: NodeGoalProps | NodePlanProps): goal is NodeGoalP export default function GoalsSearchView({ filters, goals, total, description }: ViewGoalSearch) { const offsetAmount = 15; const [fulltext, setFulltext] = useState(filters[0]?.value ? filters[0].value : ""); + const [administration, setAdministration] = useState("53"); const [totalResults, setTotalResults] = useState(total) const [displayGoals, setDisplayGoals] = useState(goals) const [facets, setFacets] = useState(filters[1]?.options ? filters[1].options : []) @@ -35,7 +35,7 @@ export default function GoalsSearchView({ filters, goals, total, description }: if (e) { e.preventDefault(); } - const url = `/api/goal-search?fulltext=${fulltext}&facets=${facets}`; + const url = `/api/goal-search?fulltext=${fulltext}&facets=${facets}&administration=${administration}`; setOffset(offsetAmount) try { const response = await fetch(url); @@ -51,7 +51,7 @@ export default function GoalsSearchView({ filters, goals, total, description }: } catch (error) { console.error(error.message); } - }, [fulltext]) + }, [fulltext, administration]) const masonryBP = filtersOpen ? {350: 1, 750: 2, 1400: 3} : {350: 1, 750: 2, 1060: 3, 1400: 4}; @@ -76,7 +76,14 @@ export default function GoalsSearchView({ filters, goals, total, description }: />
- +
+ + +
@@ -89,7 +96,7 @@ export default function GoalsSearchView({ filters, goals, total, description }:
{displayGoals?.length ? ( - diff --git a/src/frontend/lib/graphqlQueries.ts b/src/frontend/lib/graphqlQueries.ts index c3f7589f..430bdac6 100644 --- a/src/frontend/lib/graphqlQueries.ts +++ b/src/frontend/lib/graphqlQueries.ts @@ -142,11 +142,16 @@ export const graphqlQueries = { } } }`, - goalsView: (fulltext: string | string[], facets: string | string[]) => + goalsView: ( + fulltext: string | string[], + facets: string | string[], + administration: string | string[], + ) => `query GoalsQuery { goalsGraphql1(filter: { aggregated_field: "${fulltext}", Topics: ${JSON.stringify(facets)}, + Administration: ${administration} }) { pageInfo { total diff --git a/src/frontend/pages/api/goal-search.ts b/src/frontend/pages/api/goal-search.ts index 7cafe489..d9e2bea9 100644 --- a/src/frontend/pages/api/goal-search.ts +++ b/src/frontend/pages/api/goal-search.ts @@ -14,12 +14,14 @@ export default async function handler( const graphqlUrl = drupal.buildUrl("/graphql"); const fulltext = req.query.fulltext ? req.query.fulltext : ""; const facets = req.query.facets ? req.query.facets : []; - console.log(facets); + const administration = req.query.administration + ? req.query.administration + : "53"; const response = await drupal.fetch(graphqlUrl.toString(), { method: "POST", withAuth: true, // Make authenticated requests using OAuth. body: JSON.stringify({ - query: graphqlQueries.goalsView(fulltext, facets), + query: graphqlQueries.goalsView(fulltext, facets, administration), }), }); const { data } = await response.json(); diff --git a/src/frontend/styles/style.scss b/src/frontend/styles/style.scss index 00200b67..2dfd1e7b 100644 --- a/src/frontend/styles/style.scss +++ b/src/frontend/styles/style.scss @@ -56,15 +56,15 @@ } &--wrapper { position: relative; - display: flex; // Use flexbox + display: flex; width: 100%; input { padding-left: 3em; height: 2.5em; - flex: 1; // Make the input take up the remaining space - width: 100%; // Ensure the input spans the full width - border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color - outline: none; // Remove the default browser outline on focus (optional) + flex: 1; + width: 100%; + border: 1px solid rgba(202, 204, 216, 1); + outline: none; } button { position: absolute; @@ -76,6 +76,13 @@ } } &--administration { - border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color + border: 1px solid rgba(202, 204, 216, 1); + + /* Arrow */ + appearance: none; + background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23131313%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E"); + background-repeat: no-repeat; + background-position: right 0.5em top 50%; + background-size: 0.65em auto; } } From a03c52c1693df3474c7670e13761ea06a0541e1d Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 02:28:04 -0500 Subject: [PATCH 20/25] Plan id to plan results in goal search query. --- src/frontend/lib/graphqlQueries.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/lib/graphqlQueries.ts b/src/frontend/lib/graphqlQueries.ts index 430bdac6..a268c805 100644 --- a/src/frontend/lib/graphqlQueries.ts +++ b/src/frontend/lib/graphqlQueries.ts @@ -215,6 +215,7 @@ export const graphqlQueries = { } } ... on NodePlan { + id title path agency { From 5e9dfe400adf57537dd60366ac21a4a85837c979 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 03:17:43 -0500 Subject: [PATCH 21/25] Stable list of facets. --- src/frontend/components/view--goal-facets.tsx | 33 +++++++++++++------ src/frontend/components/view--goal-search.tsx | 5 +-- src/frontend/lib/graphqlQueries.ts | 11 +++++++ src/frontend/pages/api/get-facets.ts | 24 ++++++++++++++ src/frontend/pages/index.tsx | 2 +- 5 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/frontend/pages/api/get-facets.ts diff --git a/src/frontend/components/view--goal-facets.tsx b/src/frontend/components/view--goal-facets.tsx index 3fd54249..94f80ac6 100644 --- a/src/frontend/components/view--goal-facets.tsx +++ b/src/frontend/components/view--goal-facets.tsx @@ -1,12 +1,21 @@ import { useState, useEffect } from 'react'; const ViewGoalFacets = ({filter_options, handleSearch}) => { + const [facets, setFacets] = useState(null); + const url = `/api/get-facets`; + + useEffect(() => { + fetch(url) + .then((res) => res.json()) + .then((data) => setFacets(data)); + }, []); + const facetKeys = filter_options ? Object.keys(filter_options) : []; facetKeys.sort(); const [checkedFacets, setCheckedFacets] = useState([]) - const removeFacet = (facetToRemove) => { + const removeCheckedFacet = (facetToRemove) => { setCheckedFacets(checkedFacets.filter((facetKey) => facetKey !== facetToRemove)); }; @@ -14,7 +23,7 @@ const ViewGoalFacets = ({filter_options, handleSearch}) => { if (!checkedFacets.includes(facetKey)) { setCheckedFacets([...checkedFacets, facetKey]) } else { - removeFacet(facetKey); + removeCheckedFacet(facetKey); } } @@ -22,24 +31,28 @@ const ViewGoalFacets = ({filter_options, handleSearch}) => { handleSearch(null, checkedFacets) }, [checkedFacets, handleSearch]) + + if (!facets) return
Loading...
; + + return(
- {facetKeys.map(( facetKey ) => ( -
+ {facets.data.termTopics.nodes.map(( facetKey ) => ( +
handleCheck(facetKey)} - checked={checkedFacets.includes(facetKey)} + value={facetKey.name} + onChange={() => handleCheck(facetKey.name)} + checked={checkedFacets.includes(facetKey.name)} />
))} diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index 9051c367..625dc951 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -1,14 +1,11 @@ -import { useState, useEffect, useRef, FormEvent, useCallback } from "react"; +import { useState, FormEvent, useCallback } from "react"; import { Button } from "@trussworks/react-uswds"; import Masonry, {ResponsiveMasonry} from "react-responsive-masonry" import { NodeGoalProps, NodePlanProps, ViewFilter } from "lib/types"; import { NodeGoalCard } from "./node--goal--card"; import { NodePlanCard } from "./node--plan--card"; -import { ViewGoalSearchFacet } from "./view--goal-search--facet"; import { ViewGoalSearchFulltext } from "./view--goal-search--fulltext"; -import { ViewGoalSearchResults } from "./view--goal-search--results"; import ViewGoalFacets from "./view--goal-facets"; -import goalTotals from "./goal-totals"; interface ViewGoalSearch { goals: Array, diff --git a/src/frontend/lib/graphqlQueries.ts b/src/frontend/lib/graphqlQueries.ts index a268c805..afd7e95f 100644 --- a/src/frontend/lib/graphqlQueries.ts +++ b/src/frontend/lib/graphqlQueries.ts @@ -244,4 +244,15 @@ export const graphqlQueries = { } } }`, + taxonomyTopics: () => + ` + query getTopics { + termTopics(first: 100, sortKey: TITLE) { + nodes { + id + name + } + } +} + `, }; diff --git a/src/frontend/pages/api/get-facets.ts b/src/frontend/pages/api/get-facets.ts new file mode 100644 index 00000000..535919f0 --- /dev/null +++ b/src/frontend/pages/api/get-facets.ts @@ -0,0 +1,24 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { drupal } from "lib/drupal"; +import { graphqlQueries } from "lib/graphqlQueries"; + +type ResponseData = { + message: string; + data: any; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const graphqlUrl = drupal.buildUrl("/graphql"); + const response = await drupal.fetch(graphqlUrl.toString(), { + method: "POST", + withAuth: true, // Make authenticated requests using OAuth. + body: JSON.stringify({ + query: graphqlQueries.taxonomyTopics(), + }), + }); + const { data } = await response.json(); + res.status(200).json({ message: "Hello from Next.js!", data: data }); +} diff --git a/src/frontend/pages/index.tsx b/src/frontend/pages/index.tsx index 2e738e7f..5c090314 100644 --- a/src/frontend/pages/index.tsx +++ b/src/frontend/pages/index.tsx @@ -20,7 +20,7 @@ export const getStaticProps = async () => { method: "POST", withAuth: true, // Make authenticated requests using OAuth. body: JSON.stringify({ - query: graphqlQueries.goalsView("", []), + query: graphqlQueries.goalsView("", [], "53"), }), }); const { data } = await response.json(); From 6945c212c2d4ef70043dd3a470c16c1bf2022913 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 03:54:09 -0500 Subject: [PATCH 22/25] Working goal pictures, working facets. --- src/frontend/components/node--goal--card.tsx | 5 +++-- src/frontend/components/view--goal-facets.tsx | 7 ++----- src/frontend/components/view--goal-search.tsx | 3 +-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/frontend/components/node--goal--card.tsx b/src/frontend/components/node--goal--card.tsx index d3f53d5b..2d8bfa4b 100644 --- a/src/frontend/components/node--goal--card.tsx +++ b/src/frontend/components/node--goal--card.tsx @@ -14,6 +14,7 @@ export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) { const { title, body, path, plan, goalType,period, image } = goal; const { agency } = plan; const { acronym: agencyAcronym, logo: agencyLogo, title: agencyTitle } = agency; + const imageUrl = image?.mediaImage?.variations[0]?.url; return (
@@ -28,9 +29,9 @@ export function NodeGoalCard({ goal, ...props }: NodeGoalCardProps) {
- {image?.mediaImage?.url && ( + {imageUrl && ( {title} { +const ViewGoalFacets = ({handleSearch}) => { const [facets, setFacets] = useState(null); const url = `/api/get-facets`; @@ -10,9 +10,6 @@ const ViewGoalFacets = ({filter_options, handleSearch}) => { .then((data) => setFacets(data)); }, []); - const facetKeys = filter_options ? Object.keys(filter_options) : []; - facetKeys.sort(); - const [checkedFacets, setCheckedFacets] = useState([]) const removeCheckedFacet = (facetToRemove) => { @@ -31,7 +28,7 @@ const ViewGoalFacets = ({filter_options, handleSearch}) => { handleSearch(null, checkedFacets) }, [checkedFacets, handleSearch]) - + if (!facets) return
Loading...
; diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index 625dc951..1d29801c 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -50,7 +50,6 @@ export default function GoalsSearchView({ filters, goals, total, description }: } }, [fulltext, administration]) - const masonryBP = filtersOpen ? {350: 1, 750: 2, 1400: 3} : {350: 1, 750: 2, 1060: 3, 1400: 4}; return (
@@ -89,7 +88,7 @@ export default function GoalsSearchView({ filters, goals, total, description }:
- +
{displayGoals?.length ? ( From 0512a3f933debaf97fb65fc684d98b5db020fbf5 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 04:52:12 -0500 Subject: [PATCH 23/25] Selecting multiple facets adds to the results. --- src/frontend/components/view--goal-search.tsx | 4 ++-- src/frontend/pages/api/goal-search.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index 1d29801c..5ccdd2e0 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -32,7 +32,8 @@ export default function GoalsSearchView({ filters, goals, total, description }: if (e) { e.preventDefault(); } - const url = `/api/goal-search?fulltext=${fulltext}&facets=${facets}&administration=${administration}`; + const cleanFacets = facets.map((f) => encodeURIComponent(f)); + const url = `/api/goal-search?fulltext=${fulltext}&facets=${cleanFacets}&administration=${administration}`; setOffset(offsetAmount) try { const response = await fetch(url); @@ -41,7 +42,6 @@ export default function GoalsSearchView({ filters, goals, total, description }: } const { data } = await response.json(); - console.log(data); setDisplayGoals(data?.goalsGraphql1?.results ?? []); setTotalResults(data?.goalsGraphql1?.pageInfo.total); setFacets(data?.goalsGraphql1?.filters[1].options) diff --git a/src/frontend/pages/api/goal-search.ts b/src/frontend/pages/api/goal-search.ts index d9e2bea9..60729873 100644 --- a/src/frontend/pages/api/goal-search.ts +++ b/src/frontend/pages/api/goal-search.ts @@ -11,9 +11,17 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { + let facetsList; const graphqlUrl = drupal.buildUrl("/graphql"); const fulltext = req.query.fulltext ? req.query.fulltext : ""; const facets = req.query.facets ? req.query.facets : []; + + if (typeof facets === "string") { + facetsList = facets.split(","); + } else { + facetsList = facets; + } + const administration = req.query.administration ? req.query.administration : "53"; @@ -21,7 +29,7 @@ export default async function handler( method: "POST", withAuth: true, // Make authenticated requests using OAuth. body: JSON.stringify({ - query: graphqlQueries.goalsView(fulltext, facets, administration), + query: graphqlQueries.goalsView(fulltext, facetsList, administration), }), }); const { data } = await response.json(); From ca1419c7805bd96fa091dd7c021f99a04a34111e Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 06:19:59 -0500 Subject: [PATCH 24/25] Styled toggles, indicator data to goalView query. --- src/frontend/components/view--goal-search.tsx | 31 +++++++++++- src/frontend/lib/graphqlQueries.ts | 47 +++++++++++++++---- src/frontend/styles/style.scss | 46 ++++++++++++++++-- 3 files changed, 108 insertions(+), 16 deletions(-) diff --git a/src/frontend/components/view--goal-search.tsx b/src/frontend/components/view--goal-search.tsx index 5ccdd2e0..dc6b8c03 100644 --- a/src/frontend/components/view--goal-search.tsx +++ b/src/frontend/components/view--goal-search.tsx @@ -50,6 +50,8 @@ export default function GoalsSearchView({ filters, goals, total, description }: } }, [fulltext, administration]) + + const masonryBP = filtersOpen ? {350: 1, 750: 2, 1400: 3} : {350: 1, 750: 2, 1060: 3, 1400: 4}; return (
@@ -82,8 +84,33 @@ export default function GoalsSearchView({ filters, goals, total, description }:
-
- +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
diff --git a/src/frontend/lib/graphqlQueries.ts b/src/frontend/lib/graphqlQueries.ts index afd7e95f..671d65a4 100644 --- a/src/frontend/lib/graphqlQueries.ts +++ b/src/frontend/lib/graphqlQueries.ts @@ -153,14 +153,14 @@ export const graphqlQueries = { Topics: ${JSON.stringify(facets)}, Administration: ${administration} }) { - pageInfo { - total - } - filters { - options - value - } - description + pageInfo { + total + } + filters { + options + value + } + description results { ... on NodeGoal { id @@ -186,6 +186,33 @@ export const graphqlQueries = { } } } + objectives { + ... on NodeObjective { + id + title + indicators { + ... on StorageIndicator { + id + name + progress + dates + target + targetValues + names + values + measurements { + ... on StorageMeasurement{ + id + name + targetValue + value + status + } + } + } + } + } + } plan { ... on NodePlan { id @@ -215,8 +242,8 @@ export const graphqlQueries = { } } ... on NodePlan { - id title + id path agency { ... on NodeAgency { @@ -242,7 +269,7 @@ export const graphqlQueries = { } } } - } + } }`, taxonomyTopics: () => ` diff --git a/src/frontend/styles/style.scss b/src/frontend/styles/style.scss index 2dfd1e7b..99eab310 100644 --- a/src/frontend/styles/style.scss +++ b/src/frontend/styles/style.scss @@ -7,6 +7,8 @@ //// 3. Load custom Sass @forward "project-styles.scss"; +$goals-gray: rgba(202, 204, 216, 1); + .goal-card { min-width: 360px; max-width: 400px; @@ -49,10 +51,10 @@ .search-goals { &--container { - border-bottom: 1px solid rgba(202, 204, 216, 1); + border-bottom: 1px solid $goals-gray; } &--filter { - border: 1px solid rgba(202, 204, 216, 1); // Add outline with the specified color + border: 1px solid $goals-gray; } &--wrapper { position: relative; @@ -63,7 +65,7 @@ height: 2.5em; flex: 1; width: 100%; - border: 1px solid rgba(202, 204, 216, 1); + border: 1px solid $goals-gray; outline: none; } button { @@ -76,7 +78,7 @@ } } &--administration { - border: 1px solid rgba(202, 204, 216, 1); + border: 1px solid $goals-gray; /* Arrow */ appearance: none; @@ -85,4 +87,40 @@ background-position: right 0.5em top 50%; background-size: 0.65em auto; } + + &--toggle { + margin: 0 auto; + > li { + display: inline-block; + width: auto; + } + .active { + background: white; + } + } +} + +.hr-lines { + position: relative; + text-align: center; + &:before { + content: " "; + display: block; + height: 1px; + width: 500px; + position: absolute; + top: 50%; + left: -525px; + background: $goals-gray; + } + &::after { + content: " "; + height: 1px; + width: 500px; + background: $goals-gray; + display: block; + position: absolute; + top: 50%; + right: -500px; + } } From 380a1f0f57bedb4da06a7b2effb7fd5f92dfc7b6 Mon Sep 17 00:00:00 2001 From: Adrienne Date: Mon, 10 Feb 2025 13:12:37 -0500 Subject: [PATCH 25/25] Config updates and template updates. --- config/search_api.index.goals.yml | 2 ++ src/frontend/components/node--plan--card.tsx | 2 +- src/frontend/lib/types.ts | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config/search_api.index.goals.yml b/config/search_api.index.goals.yml index 8f876791..a1de1900 100644 --- a/config/search_api.index.goals.yml +++ b/config/search_api.index.goals.yml @@ -23,6 +23,7 @@ field_settings: fields: - 'entity:node/body' - 'entity:node/field_agency' + - 'entity:node/field_goals' - 'entity:node/title' field_administration: label: Administration @@ -66,6 +67,7 @@ processor_settings: - aggregated_field language_with_fallback: { } rendered_item: { } + reverse_entity_references: { } tokenizer: weights: preprocess_index: -6 diff --git a/src/frontend/components/node--plan--card.tsx b/src/frontend/components/node--plan--card.tsx index ea6a1944..0359962a 100644 --- a/src/frontend/components/node--plan--card.tsx +++ b/src/frontend/components/node--plan--card.tsx @@ -10,7 +10,7 @@ interface NodePlanCardProps { } export function NodePlanCard({ goal, ...props }: NodePlanCardProps) { - const { title, body, path, agency, goalType,period } = goal; + const { title, path, agency, period } = goal; const { acronym: agencyAcronym, logo: agencyLogo, title: agencyTitle } = agency; return (
diff --git a/src/frontend/lib/types.ts b/src/frontend/lib/types.ts index d9113595..525f00c0 100644 --- a/src/frontend/lib/types.ts +++ b/src/frontend/lib/types.ts @@ -4,6 +4,7 @@ export interface NodeGoalProps { body: { value: string; }; + image: any; path: string; topics: [ {