diff --git a/.rubocop.yml b/.rubocop.yml
index a95cc61eb..51a0b7630 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -61,8 +61,15 @@ Rails/ActionOrder:
Metrics/ClassLength:
Exclude:
- "test/**/*.rb"
+ - "app/models/partner.rb"
+Metrics/CyclomaticComplexity:
+ Exclude:
+ - app/controllers/application_controller.rb
+Metrics/PerceivedComplexity:
+ Exclude:
+ - app/controllers/application_controller.rb
Rails/RootPathnameMethods:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index a24d1d674..858ddb12d 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -372,6 +372,8 @@ Rails/SkipsModelValidations:
Exclude:
- 'lib/tasks/fixes/users.rake'
- 'db/migrate/20240125175508_partner_hidden_attribute_set_value.rb'
+ - 'db/migrate/20240411200641_partner_can_be_assigned_events_set_value.rb'
+ - 'db/migrate/20240422092628_set_calendar_checksum_date.rb'
# Offense count: 22
# This cop supports unsafe autocorrection (--autocorrect-all).
diff --git a/README.md b/README.md
index 677966ede..57289e998 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,24 @@
# PlaceCal
-## Introduction / Warning
+## Introduction
-PlaceCal is a large and very complicated app for collating organisation and event information from a large variety of sources. In the words of one developer: "I'm not sure where the interface between the app and the real world is here".
+PlaceCal is an online calendar which lists events and activities by and for members of local communities, curated around interests and locality.
+
+The codebase doesn't currently have enough seeds to create a dev environment suitable for developing from scratch. If you're interested in contributing to PlaceCal please get in touch at support@placecal.org
To get an idea of the project and what we're about, check out [the handbook](https://handbook.placecal.org/).
## Requirements
-To run PlaceCal locally you will need:
-
-- A Mac or a Linux machine (we don't support Windows at present)
-- GNU Compiler Collection (gcc) (for compiling ruby and gems)
-- Docker (optional) (for isolating and automating postgres management)
-- Postgres relational database. We are currently using v14.
- - Server
- - either installed for your distribution or as a docker image (with the correct open port -- see below)
- - Client
- - you will still need the local developer libraries for postgres
- - these are distribution specific so you need to find out what they are called to install them
- - `libpq-dev` (debian)
- - `postgresql-libs` (arch)
- - `dev-db/postgresql` (gentoo)
-- Ruby 3.1.2 - We recommend using a version manager for this such as `rvm` or `rbenv`. Current version we are using is in `.ruby-version`.
- - [rvm](https://rvm.io/)
- - [rbenv](https://github.com/rbenv/rbenv)
- - [ruby-build](https://github.com/rbenv/ruby-build)
- - [rbenv-gemset](https://github.com/jf/rbenv-gemset) (optional)
-- Node.js 16.x. We recommend using a version manager for this such as `nvm` or `nodenv`. Current version we are using is in `.node-version`.
- - [nvm](https://github.com/nvm-sh/nvm)
- - [nodenv](https://github.com/nodenv/nodenv)
-- Yarn 1.x
- - [yarn](https://classic.yarnpkg.com/en/docs/install)
-- ImageMagick for image manipulation
+To run PlaceCal locally you will need to install the following dependencies:
+
+- [Docker](https://docs.docker.com/get-docker/)
+- [Ruby 3.1.2](https://www.ruby-lang.org/en/news/2022/04/12/ruby-3-1-2-released/)
+- [Node.js](https://nodejs.org/en/download) 16.x & (optional) [nvm](https://github.com/nvm-sh/nvm) to manage it
+- [Yarn 1.x](https://classic.yarnpkg.com/lang/en/)
+- [ImageMagick](https://imagemagick.org/index.php) for image manipulation
- [Graphviz](https://voormedia.github.io/rails-erd/install.html) for documentation diagrams
-- Chrome/Chromium for system tests along with a matching version of [Chromedriver](https://chromedriver.chromium.org/)
+- [Chrome/Chromium](https://www.chromium.org/chromium-projects/) for system tests along with a matching version of [Chromedriver](https://chromedriver.chromium.org/)
## Quickstart with docker for GFSC devs
@@ -50,6 +34,15 @@ Local site is now running at `lvh.me:3000`
Some other useful commands for developing can be found in the makefile.
+## Troubleshooting
+
+If the make command fails and you can't work out why, here are some suggestions:
+
+- Do you have an older version of the database hanging around? Try running `rails db:drop:all`
+- Is the docker daemon running?
+- Is the port in use by another application or docker container? Try stopping or removing any conflicting containers
+- Are you using the correct node version? Try running `nvm use`
+
## Quickstart for everyone else
### Set up Postgresql locally with docker
@@ -74,61 +67,28 @@ Amongst other things, this will create an admin user for you:
- The admin interface is at `admin.lvh.me:3000`
- Access code docs through your local filesystem and update them with `bin/rails yard`
-## Testing
+## Testing, linting and formatting
-PlaceCal tests are written in minitest.
+PlaceCal tests are written in minitest. We use Rubocop to lint our Ruby code.
-Before running the tests please make sure your development environment is up to date (you can run `bin/update` to quickly do that).
+We use [Prettier](https://prettier.io/) to format everything it's able to parse. We run this automatically as part of a pre-commit hook.
-You can run the tests with:
+You can run the tests & rubocop with:
```sh
-make # will run all unit and system tests then lint check all code
-rails test # will run all unit tests
-rails test:system # will run system tests
+make test
```
-Note that the system tests can take a while to run and are quite resource-intensive. To perform more advanced usage like executing only a specific test or test file, see the [Rails documentation on testing](https://guides.rubyonrails.org/testing.html).
-
## Documentation for Developers
-The documentation for PlaceCal currently stored in notion and can be read [here](https://www.notion.so/gfsc/PlaceCal-developer-handbook-01649b69009340e3ae3035e9cf346f27). There is also a small amount of documentation sprinkled throughout the code itself and can be turned into HTML by running `rails yard`. If you are working with the code and are completely lost you can also try the GFSC discord server where you can prod a human for answers. Good Luck!
+The documentation for PlaceCal is currently stored in notion and can be read [here](https://handbook.placecal.org/placecal-developer-handbook). There is also a small amount of documentation sprinkled throughout the code itself and can be turned into HTML by running `rails yard`. If you are working with the code and are completely lost you can also try the [GFSC discord server](http://discord.gfsc.studio) where you can prod a human for answers. Good Luck!
### Generating new components
We use view_component to make components, and you can create a new one by running `rails g component `
+Previously this system used mountain view, and some of the components are still generated using this.
More info here: https://viewcomponent.org/guide/generators.html
-## Formatting
-
-We use [Prettier](https://prettier.io/) to format everything it's able to parse. It will run as a pre-commit hook and format your changes as you make commits so you shouldn't have to think about it much.
-
-If you do want to run it manually, you can:
-
-```sh
-bin/yarn run format
-```
-
-It's also run for you by the test runner.
-
-Note that we use tabs over spaces because [tabs are more accessible to people using braille displays](https://twitter.com/Rich_Harris/status/1541761871585464323).
-
-## Linting
-
-We use Rubocop to lint our Ruby code. Because of the time it can take to run, this is a manual step:
-
-```sh
-bin/bundle exec rubocop --autocorrect
-```
-
-It's also run for you by the test runner.
-
-## Contributing
-
-We welcome new contributors but strongly recommend you have a chat with us in [Geeks for Social Change's Discord server](http://discord.gfsc.studio) and say hi before you do. We will be happy to onboard you properly before you get stuck in.
-
## Donations
-If you'd like to support development, please consider sending us a one-off or regular donation on Ko-fi.
-
-[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/M4M43THUM)
+If you'd like to support development, please consider sending us a one-off or regular donation on Ko-fi. You can do this through the "support" button in GitHub at the top of this repo.
diff --git a/app/assets/stylesheets/base/grid.scss b/app/assets/stylesheets/base/grid.scss
index bbd40fa22..5123b4544 100644
--- a/app/assets/stylesheets/base/grid.scss
+++ b/app/assets/stylesheets/base/grid.scss
@@ -116,6 +116,24 @@ $column: 17.38317%;
}
}
+.three-col {
+ -moz-column-count: 3;
+ -webkit-column-count: 3;
+ column-count: 3;
+ -webkit-column-width: 20rem;
+ -moz-column-width: 20rem;
+ column-width: 20rem;
+ margin-top: 1.35rem !important;
+
+ li:first-of-type {
+ margin-top: -1.35rem;
+ }
+
+ a {
+ display: inline-block;
+ }
+}
+
@supports (display: grid) {
.two-col {
display: grid;
@@ -133,6 +151,22 @@ $column: 17.38317%;
}
}
+ .three-col {
+ display: grid;
+ grid-template-columns: 1fr;
+
+ @include for-tablet-landscape-up {
+ grid-template-columns: 1fr 1fr 1fr;
+ }
+
+ grid-gap: 1rem;
+ align-items: start;
+
+ li:first-of-type {
+ margin-top: 0;
+ }
+ }
+
.g--partner {
display: grid;
grid-template-columns: 1fr;
diff --git a/app/components/address/address_component.rb b/app/components/address/address_component.rb
index 5315e313c..7641d8257 100644
--- a/app/components/address/address_component.rb
+++ b/app/components/address/address_component.rb
@@ -12,6 +12,10 @@ def formatted_address
return address_lines.join(',
').html_safe
end
+ uri = URI.parse(raw_location)
+ "#{uri.hostname}".html_safe
+
+ rescue URI::InvalidURIError
raw_location
end
end
diff --git a/app/components/event/_event.html.erb b/app/components/event/_event.html.erb
index 8798b8484..8fd159d97 100644
--- a/app/components/event/_event.html.erb
+++ b/app/components/event/_event.html.erb
@@ -34,11 +34,11 @@
Online
<% end %>
- <% if place || first_address_line %>
+ <% if partner_at_location || first_address_line %>
- <% if place %>
- <%= link_to place, partner_path(place) %>
+ <% if partner_at_location %>
+ <%= link_to partner_at_location, partner_path(partner_at_location) %>
<% elsif first_address_line %>
<%= first_address_line %>
<% end %>
diff --git a/app/components/event/event_component.rb b/app/components/event/event_component.rb
index 833762fe5..e3148f00c 100644
--- a/app/components/event/event_component.rb
+++ b/app/components/event/event_component.rb
@@ -8,8 +8,7 @@ class EventComponent < MountainView::Presenter
include ActionView::Helpers::DateHelper
delegate :id, to: :event
-
- delegate :place, to: :event
+ delegate :partner_at_location, to: :event
def time
if event.dtend
diff --git a/app/components/event_list/_event_list.html.erb b/app/components/event_list/_event_list.html.erb
index 7a914539c..6327b5242 100644
--- a/app/components/event_list/_event_list.html.erb
+++ b/app/components/event_list/_event_list.html.erb
@@ -24,5 +24,5 @@
<% end %>
<% else %>
-
No events with this selection.
+
No events with this selection.<%= link_to 'skip to next date with events.', next_url(@next) if @next.present? %>
<% end %>
diff --git a/app/components/paginator/_paginator.html.erb b/app/components/paginator/_paginator.html.erb
index 1db5bed2a..6fa3a3956 100644
--- a/app/components/paginator/_paginator.html.erb
+++ b/app/components/paginator/_paginator.html.erb
@@ -23,13 +23,17 @@
- <%= radio_button_tag(:period, "day", period == 1.day) %>
+ <%= radio_button_tag(:period, "day", period == 'day') %>
<%= label_tag(:period_day, "Daily view") %>
- <%= radio_button_tag(:period, "week", period == 1.week) %>
+ <%= radio_button_tag(:period, "week", period == 'week') %>
<%= label_tag(:period_week, "Weekly view") %>
+
+ <%= radio_button_tag(:period, "future" , period == 'future') %>
+ <%= label_tag(:period_future, "Show all") %>
+
@@ -54,13 +58,15 @@
<% end %>
-
- <% paginator.each do |page| %>
- -
- <%= link_to page[:text], page[:link] %>
-
- <% end %>
-
+ <% if @period == 'day' || @period == 'week' %>
+
+ <% paginator.each do |page| %>
+ -
+ <%= link_to page[:text], page[:link] %>
+
+ <% end %>
+
+ <% end %>
diff --git a/app/components/paginator/paginator_component.rb b/app/components/paginator/paginator_component.rb
index 8956262ba..7eb45973e 100644
--- a/app/components/paginator/paginator_component.rb
+++ b/app/components/paginator/paginator_component.rb
@@ -13,11 +13,11 @@ def paginator
pages = []
# Create backward arrow link
pages << { text: back_arrow,
- link: create_event_url(pointer - period),
+ link: create_event_url(pointer - step),
css: 'paginator__arrow paginator__arrow--back js-back' }
# Create in-between links according to steps requested
(0..steps).each do |i|
- day = pointer + (period * i)
+ day = pointer + (step * i)
css = active?(day) ? 'active js-button' : 'js-button'
pages << { text: format_date(day),
link: create_event_url(day),
@@ -25,13 +25,13 @@ def paginator
end
# Create forwards arrow link
pages << { text: forward_arrow,
- link: create_event_url(pointer + period),
+ link: create_event_url(pointer + step),
css: 'paginator__arrow paginator__arrow--forwards js-forwards' }
end
# Paginator title
def title
- if period <= 1.day
+ if step <= 1.day
# Thursday 14 September, 2017
pointer.strftime('%A %e %B, %Y')
else
@@ -39,7 +39,7 @@ def title
t = pointer.strftime('%A %e %B')
t += ' - '
# FIXME: 1.day needs sorting when we add in month views
- t + (pointer + period - 1.day).strftime('%A %e %B %Y')
+ t + (pointer + step - 1.day).strftime('%A %e %B %Y')
end
end
@@ -49,7 +49,7 @@ def sort
end
# How far does each step take us?
- def period
+ def step
properties[:period] == 'week' ? 1.week : 1.day
end
@@ -67,7 +67,7 @@ def steps
# Which day are we doing our calculations based on?
def pointer
- if period == 1.week
+ if step == 1.week
# This should be set by the model, but just to be sure
properties[:pointer].beginning_of_week
else
@@ -77,7 +77,7 @@ def pointer
# Format date according to context
def format_date(date)
- if period <= 1.day
+ if step <= 1.day
todayify(date)
else
weekify(date)
@@ -101,7 +101,7 @@ def todayify(date)
# Format the button for a week of events
def weekify(date)
today = Date.today
- end_date = date + period - 1.day
+ end_date = date + step - 1.day
date_fmt = if date.month == end_date.month
"#{date.strftime('%e')} - #{end_date.strftime('%e %b')}"
else
@@ -124,7 +124,7 @@ def create_event_url(dt)
# URL params to add back in
def url_suffix
str = []
- str << 'period=week' if period == 1.week
+ str << "period=#{period}"
str << "sort=#{sort}" if sort
str << "repeating=#{repeating}" if repeating
"?#{str.join('&')}" if str.any?
diff --git a/app/controllers/admin/calendars_controller.rb b/app/controllers/admin/calendars_controller.rb
index 8fe60537d..9d6958902 100644
--- a/app/controllers/admin/calendars_controller.rb
+++ b/app/controllers/admin/calendars_controller.rb
@@ -42,8 +42,8 @@ def create
authorize @calendar
if @calendar.save
- flash[:success] = 'Successfully created new calendar'
redirect_to edit_admin_calendar_path(@calendar)
+ flash[:success] = 'New calendar created and queued for importing. Please check back in a few minutes.'
else
flash.now[:danger] = 'Calendar did not save'
render 'new', status: :unprocessable_entity
@@ -74,10 +74,8 @@ def destroy
end
def import
- date = Time.zone.parse(params[:starting_from])
force_import = true
- # CalendarImporterJob.perform_now @calendar.id, date, force_import
- @calendar.queue_for_import! force_import, date
+ @calendar.queue_for_import! force_import
flash[:success] = 'Calendar added to the import queue'
redirect_to edit_admin_calendar_path(@calendar)
@@ -109,7 +107,8 @@ def calendar_params
:importer_mode,
:public_contact_name,
:public_contact_phone,
- :public_contact_email
+ :public_contact_email,
+ :checksum_updated_at
)
end
end
diff --git a/app/controllers/admin/partners_controller.rb b/app/controllers/admin/partners_controller.rb
index ba9c6a691..7e89448da 100644
--- a/app/controllers/admin/partners_controller.rb
+++ b/app/controllers/admin/partners_controller.rb
@@ -46,7 +46,7 @@ def create
if @partner.save
format.html do
flash[:success] = 'Partner was successfully created.'
- redirect_to admin_partners_path
+ redirect_to edit_admin_partner_path(@partner)
end
format.json { render :show, status: :created, location: @partner }
@@ -71,8 +71,6 @@ def update
mutated_params = permitted_attributes(@partner)
- before = @partner.hidden
-
@partner.accessed_by_user = current_user
# prevent someone trying to add the same service_area twice by mistake and causing a crash
@@ -147,22 +145,6 @@ def lookup_name
render json: { name_available: found.nil? }
end
- def setup
- @partner = Partner.new
- authorize @partner
-
- render and return unless request.post?
-
- @partner.attributes = setup_params
- @partner.accessed_by_user = current_user
-
- if @partner.valid?
- redirect_to new_admin_partner_url(partner: setup_params)
- else
- render 'setup', status: :unprocessable_entity
- end
- end
-
private
def set_partner_tags_controller
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 26ee433c7..28efb4929 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -52,18 +52,16 @@ def filter_events(period, **args)
events = Event.all
- if site
- events = events.for_site(site)
- events = events.with_tags(site.tags) if site.tags.any?
- end
-
+ events = events.for_site(site) if site
events = events.in_place(place) if place
events = events.by_partner(partner) if partner
events = events.by_partner_or_place(partner_or_place) if partner_or_place
events = events.one_off_events_only if repeating == 'off'
events = events.one_off_events_first if repeating == 'last'
events =
- if period == 'week'
+ if period == 'future'
+ events.future(@today).includes(:place)
+ elsif period == 'week'
events.find_by_week(@current_day).includes(:place)
else
events.find_by_day(@current_day).includes(:place)
diff --git a/app/controllers/concerns/map_markers.rb b/app/controllers/concerns/map_markers.rb
index d6b0bbef8..7d1ff00d4 100644
--- a/app/controllers/concerns/map_markers.rb
+++ b/app/controllers/concerns/map_markers.rb
@@ -19,7 +19,7 @@ def get_map_markers(locations, addresses_only = false)
locations = locations.map do |loc|
next loc unless loc.is_a?(Event)
- loc.place || loc.address
+ loc.partner_at_location || loc.address
end
# Partners
diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb
index c193c9164..11f0d6502 100644
--- a/app/controllers/events_controller.rb
+++ b/app/controllers/events_controller.rb
@@ -14,15 +14,37 @@ class EventsController < ApplicationController
# GET /events
# GET /events.json
def index
- # Duration to view - default to day view
- @period = params[:period].to_s || 'day'
+ # Duration to view - default to future view
+ @period = params[:period] || 'future'
@repeating = params[:repeating] || 'on'
@events = filter_events(@period, repeating: @repeating, site: current_site)
+ # Duration to view - default to day view if there are too many future events
+ if params[:period].to_s == '' && @events.count > 200
+ @period = 'week'
+ @events = filter_events(@period, repeating: @repeating, site: current_site)
+ end
@title = current_site.name
# Sort criteria
@events = sort_events(@events, @sort)
@multiple_days = true
+ @next = if params[:year].present?
+ date = begin
+ Date.new(params[:year].to_i,
+ params[:month].to_i,
+ params[:day].to_i)
+ rescue Date::Error
+ Time.zone.today
+ end
+ Event.for_site(current_site).future(
+ date
+ ).first
+ else
+ Event.for_site(current_site).future(
+ Time.zone.today
+ ).first
+ end
+
respond_to do |format|
format.html do
if params[:simple].present?
@@ -34,10 +56,7 @@ def index
format.text
format.ics do
events = Event.all
- if @site
- events = events.for_site(@site)
- events = events.with_tags(@site.tags) if @site.tags.any?
- end
+ events = events.for_site(@site) if @site
# TODO: Add caching maybe Rails.cache.fetch(:ics, expires_in: 1.hour)?
ics_listing = events.ical_feed
cal = create_calendar(ics_listing)
@@ -52,8 +71,8 @@ def ical; end
# GET /events/1
# GET /events/1.json
def show
- if @event.place
- @map = get_map_markers([@event.place])
+ if @event.partner_at_location
+ @map = get_map_markers([@event.partner_at_location])
elsif @event.address
@map = get_map_markers([@event.address])
end
diff --git a/app/controllers/partners_controller.rb b/app/controllers/partners_controller.rb
index 862068b32..cd3f12780 100644
--- a/app/controllers/partners_controller.rb
+++ b/app/controllers/partners_controller.rb
@@ -30,13 +30,6 @@ def index
@map = get_map_markers(@partners, true) if @partners.detect(&:address)
end
- # # GET /places
- # # GET /places.json
- # def places_index
- # @places = Partner.event_hosts.for_site(current_site).order(:name)
- # @map = get_map_markers(@places) if @places.detect(&:address)
- # end
-
# GET /partners/1
# GET /partners/1.json
def show
diff --git a/app/controllers/users/auth_common.rb b/app/controllers/users/auth_common.rb
index b43778996..7ed5569b5 100644
--- a/app/controllers/users/auth_common.rb
+++ b/app/controllers/users/auth_common.rb
@@ -1,5 +1,23 @@
# frozen_string_literal: true
+class DeviseController
+ # monkey patching devise so it can handle the new default behaviour in rails 7
+ # redirects
+
+ original_redirect_to = instance_method(:redirect_to)
+ define_method(:redirect_to) do |options, response_options = {}|
+ if options.is_a?(Hash)
+ options[:allow_other_host] = true unless options.key?(:allow_other_host)
+
+ elsif response_options.is_a?(Hash)
+ response_options[:allow_other_host] = true unless response_options.key?(:allow_other_host)
+ end
+
+ original_redirect_to
+ .bind_call(self, options, response_options)
+ end
+end
+
module Users
module AuthCommon
def self.included(klass)
diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb
index 47668b919..68a8b3254 100644
--- a/app/controllers/users/passwords_controller.rb
+++ b/app/controllers/users/passwords_controller.rb
@@ -6,4 +6,11 @@ class Users::PasswordsController < Devise::PasswordsController
include Users::AuthCommon
before_action :set_site
+
+ # POST /resource/password
+ def create
+ resource_class.send_reset_password_instructions(resource_params)
+
+ redirect_to new_user_password_path, notice: 'If a PlaceCal account is associated with the submitted email address, password reset instructions have been sent.'
+ end
end
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index d7b98fd33..a88f282da 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -2,24 +2,6 @@
require_relative 'auth_common'
-class DeviseController
- # monkey patching devise so it can handle the new default behaviour in rails 7
- # redirects
-
- original_redirect_to = instance_method(:redirect_to)
- define_method(:redirect_to) do |options, response_options = {}|
- if options.is_a?(Hash)
- options[:allow_other_host] = true unless options.key?(:allow_other_host)
-
- elsif response_options.is_a?(Hash)
- response_options[:allow_other_host] = true unless response_options.key?(:allow_other_host)
- end
-
- original_redirect_to
- .bind_call(self, options, response_options)
- end
-end
-
class Users::SessionsController < Devise::SessionsController
include Users::AuthCommon
diff --git a/app/datatables/calendar_datatable.rb b/app/datatables/calendar_datatable.rb
index b41a7d458..329ed6641 100644
--- a/app/datatables/calendar_datatable.rb
+++ b/app/datatables/calendar_datatable.rb
@@ -18,7 +18,7 @@ def view_columns
events: { source: 'Calendar.events', searchable: false, orderable: false },
state: { source: 'Calendar.calendar_state', searchable: false, orderable: false },
last_import_at: { source: 'Calendar.last_import_at', searchable: false, orderable: false },
- updated_at: { source: 'Calendar.updated_at', searchable: false, orderable: false }
+ checksum_updated_at: { source: 'Calendar.checksum_updated_at', searchable: false, orderable: false }
}
end
@@ -32,7 +32,7 @@ def data
events: record.events&.count&.to_s || 0,
state: record.calendar_state,
last_import_at: json_datetime(record.last_import_at),
- updated_at: json_datetime(record.updated_at)
+ checksum_updated_at: json_datetime(record.checksum_updated_at)
}
end
end
diff --git a/app/graphql/types/event_type.rb b/app/graphql/types/event_type.rb
index 704f535cc..965367d34 100644
--- a/app/graphql/types/event_type.rb
+++ b/app/graphql/types/event_type.rb
@@ -33,7 +33,6 @@ class EventType < Types::BaseObject
description: 'The address where this event will take place'
field :organizer, PartnerType,
- method: :partner,
description: 'The organising partner of this event'
field :publisherUrl, String,
@@ -54,5 +53,12 @@ def onlineEventUrl
def onlineEventUrlType
object&.online_address&.link_type
end
+
+ def organizer
+ partner = object&.partner
+ return if partner.blank? || partner.hidden
+
+ partner
+ end
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 7f4dcd5fb..2509c78c8 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -22,15 +22,15 @@ def self.included(klass)
end
def partner(id:)
- Partner.find(id)
+ Partner.visible.find(id)
end
def partner_connection(**_args)
- Partner.all
+ Partner.visible.all
end
def partners_by_tag(tag_id:)
- Partner.with_tags(tag_id).order(:name)
+ Partner.visible.with_tags(tag_id).order(:name)
end
end
diff --git a/app/helpers/calendars_helper.rb b/app/helpers/calendars_helper.rb
index 640be3b22..51b3addf2 100644
--- a/app/helpers/calendars_helper.rb
+++ b/app/helpers/calendars_helper.rb
@@ -45,27 +45,25 @@ def strategy_label(val)
case val.second
when 'event'
'Event: ' \
- 'Get the location of this event from the address field on the source event. ' \
- 'This is for area calendars, or organisations with no solid base.'.html_safe
+ 'Use the address from each event. '\
+ 'If an address is invalid or it doesn\'t have one, the event will import with no location.'.html_safe
when 'place'
'Default location: ' \
- 'Every event is in one location (set below). The address field on the source calendar is ignored.'.html_safe
+ 'Always use the calendar\'s default location.'.html_safe
when 'room_number'
- 'Room Number: ' \
- 'Every event is on one location (set below), and the address field is used ' \
- 'to store a room number.'.html_safe
+ 'Room number: ' \
+ 'Get a room number from the event\'s address, '\
+ 'and overwrite the rest of the address with the calendar\'s default location.'.html_safe
when 'event_override'
- 'Event Override: ' \
- 'Every event is in one location (set below), unless the address field ' \
- 'is set to another location'.html_safe
+ 'Event where possible: ' \
+ 'Use the address from each event. '\
+ 'If the address is invalid or it doesn\'t have one, use the calendar\'s default location.'.html_safe
when 'no_location'
- 'No Location: ' \
- 'Events imported will have no location information set in PlaceCal, even if it is present on ' \
- 'the remote feed'.html_safe
+ 'No location: ' \
+ 'Discard any address information.'.html_safe
when 'online_only'
- 'Online Only: ' \
- 'Events imported will have no physical location information set in PlaceCal, even if it is present on ' \
- 'the remote feed, but will maintain the online information'.html_safe
+ 'Online only: ' \
+ 'Discard any address information but retain web links.'.html_safe
else
val
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index af5b1c949..d79663fc3 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -26,7 +26,7 @@ def online_link
online_address.url,
class: 'btn btn-primary').html_safe
elsif @event.publisher_url.blank?
- link_to('Visit the webpage for this stream',
+ link_to('Visit the webpage for this event',
online_address.url,
class: 'btn btn-primary').html_safe
end
@@ -35,4 +35,18 @@ def online_link
def html_to_plaintext(input)
Nokogiri::HTML.fragment(input).text
end
+
+ def next_url(next_event)
+ opts = {
+ year: next_event.dtstart.year,
+ month: next_event.dtstart.month,
+ day: next_event.dtstart.day,
+ anchor: 'paginator',
+ period: @period,
+ sort: @sort,
+ repeating: @repeating
+ }.keep_if { |_key, value| value.present? }
+
+ events_by_date_path(opts)
+ end
end
diff --git a/app/helpers/partners_helper.rb b/app/helpers/partners_helper.rb
index 3f77d8073..89282ffef 100644
--- a/app/helpers/partners_helper.rb
+++ b/app/helpers/partners_helper.rb
@@ -17,18 +17,15 @@ def options_for_service_area_neighbourhoods(for_partner)
end
end
- def options_for_partner_tags(partner = nil)
- options = policy_scope(Tag)
+ def options_for_partner_partnerships
+ options = policy_scope(Partnership)
.select(:name, :type, :id)
.order(:name)
- .map { |r| [r.name_with_type, r.id] }
- return options unless partner
-
- (options + partner&.tags&.map { |r| [r.name_with_type, r.id] }).uniq
+ .map { |r| [r.name, r.id] }
end
def permitted_options_for_partner_tags
- policy_scope(Tag).pluck(:id)
+ policy_scope(Partnership).pluck(:id)
end
def partner_service_area_text(partner)
@@ -67,7 +64,7 @@ def site_links
return if @sites.blank?
@sites
- .map { |site| link_to site.name, site.url }
+ .map { |site| link_to site.name, site.url, target: '_blank', rel: 'noopener' }
.join(', ')
.html_safe
end
diff --git a/app/jobs/calendar_importer/calendar_importer_task.rb b/app/jobs/calendar_importer/calendar_importer_task.rb
index 89f61bb69..6c6fe8c79 100644
--- a/app/jobs/calendar_importer/calendar_importer_task.rb
+++ b/app/jobs/calendar_importer/calendar_importer_task.rb
@@ -27,7 +27,7 @@ def run
purge_stale_events_from_calendar
end
- calendar.flag_complete_import_job! notices, calendar_source.checksum, parser::KEY
+ calendar.flag_complete_import_job! notices, parser::KEY
end
private
@@ -53,7 +53,7 @@ def calendar_source
end
def event_data_from_parser
- return [] if !force_import && calendar.last_checksum == calendar_source.checksum
+ return [] if !force_import && !calendar_source.checksum_changed
calendar_source.events.map do |event_data|
CalendarImporter::EventResolver.new(event_data, calendar, notices, from_date)
@@ -67,7 +67,6 @@ def process_event(parsed_event)
parsed_event.determine_online_location
parsed_event.determine_location_for_strategy
- # next if parsed_event.is_address_missing?
active_event_uids << parsed_event.uid
diff --git a/app/jobs/calendar_importer/event_resolver.rb b/app/jobs/calendar_importer/event_resolver.rb
index 087eba764..4923ed49f 100644
--- a/app/jobs/calendar_importer/event_resolver.rb
+++ b/app/jobs/calendar_importer/event_resolver.rb
@@ -1,13 +1,6 @@
# frozen_string_literal: true
class CalendarImporter::EventResolver
- WARNING1_MSG = 'Could not determine where this event is. Add an address to the location field of the source ' \
- 'calendar, or choose another import strategy with a default location'
- WARNING2_MSG = 'Could not determine where this event is. A default location is set but the importer is set ' \
- 'to ignore this. Add an address to the location field of the source calendar, or choose ' \
- 'another import strategy with a default location.'
- INFO1_MSG = 'This location was not recognised by PlaceCal, woulfd you like to add it?'
-
attr_reader :data, :uid, :notices, :calendar
class Problem < StandardError; end
@@ -28,10 +21,6 @@ def has_no_occurences?
occurences.count.zero?
end
- def is_address_missing?
- data.address_id.nil?
- end
-
def occurences
@occurences ||= data.occurrences_between(@from_date, Calendar.import_up_to)
end
@@ -59,7 +48,7 @@ def determine_location_for_strategy
if strategies.key?(calendar.strategy)
strategy = strategies[calendar.strategy]
- place, address = method(strategy).call(calendar.place)
+ partner, address = method(strategy).call(calendar.place)
else
# this shouldn't happen and should be fatal to the entire job
raise "Calendar import strategy unknown! (ID=#{calendar.id}, strategy=#{calendar.strategy})"
@@ -67,124 +56,62 @@ def determine_location_for_strategy
# NOTE: In this context, data is a calendar object. But this doesn't make sense because
# determine_online_location sees a CalendarImporter::Events::IcsEvent object?
- data.place_id = place.id if place
+ data.place_id = partner.id if partner
data.address_id = address&.id
data.partner_id = calendar.partner_id
end
- def event_strategy(place, address: nil)
+ def event_strategy(partner, address: nil)
if data.has_location?
- # place = 'attempt to match location'
- # address = 'calendar.place.address || location'
-
- # try to find the place
- place = Partner.fuzzy_find_by_location(event_location_components)
-
- # try to use that address
- address = place&.address
-
- # if no place, try to find an address
- # (This only executes if there is no place given)
- address ||= Address.search(data.location, event_location_components, data.postcode)
-
- # if we have an address, try to use the place that address points to
- # (Only runs if the fuzzy find and calendar.place are both nil)
- place ||= address&.partners&.first
-
- # NOTE: What happens if address has zero partners and the earlier assignments fail?
- # 'place' is nil in this instance
- # NOTE: Address.search can also return Nil if there are no event_location_components, or
- # if the address failed to save
- # Both of these will cause the event to fail validation with "No place or address could be created/found (etc)"
-
- elsif place.present?
- raise Problem, WARNING2_MSG
- end # no location
- # No longer an error if place is not present -- see #1198
- # Passthrough here
-
- [place, address]
+ address = Address.build_from_components(event_location_components, data.postcode)
+ partner = nil
+ end
+ [partner, address]
end
- def event_override_strategy(place, address: nil)
+ def event_override_strategy(partner, address: nil)
if data.has_location?
- # place = 'attempt to match location'
- # address = 'calendar.place.address || location'
- place = Partner.fuzzy_find_by_location(event_location_components)
- address = place&.address
- address ||= Address.search(data.location, event_location_components, data.postcode)
-
- raise Problem, INFO1_MSG if place.nil? && address.nil?
-
- # NOTE: Either one of 'place' or 'address' is unset here but not both
- # NOTE: place is possibly unset here - fuzzy_find_by_location can be nil
- # NOTE: address is possibly unset here - place might be nil or Address.search can return nil
- # In either case we will just drop this event on the floor
- elsif place.present? # no location
- address = place.address
- # place = 'calendar.place'
- # address = 'calendar.place.address'
- # place = calendar.place
-
- else # no place, no location
- raise Problem, WARNING1_MSG
+ address = Address.build_from_components(
+ event_location_components,
+ data.postcode
+ )
end
-
- [place, address]
+ if address.nil?
+ address = partner&.address
+ else
+ partner = nil
+ end
+ [partner, address]
end
- def place_strategy(_place, _address: nil)
- # Regardless of if the data has a location, we act the same
- # We assign address to the place's address if possible, and otherwise we exit
-
- # This should theoretically never run ! :) (At least, it's not accounted for in Kim's table)
- message = <<-TEXT
- You have selected the "Default Location" strategy to set events on this calendar's locations,
- but the "Default Location" field has not been set.
- Please edit the calendar and set a Default Location, or choose another strategy.
- TEXT
-
- raise Problem, message if calendar.place.nil?
-
+ def place_strategy(_partner, _address: nil)
[calendar.place, calendar.place.address]
-
# NOTE: calendar.place can be nil, in which case this event will be dropped on the floor
# (Likely what is happening with Velociposse?)
end
- def room_number_strategy(place, address: nil)
+ def room_number_strategy(partner, address: nil)
if data.has_location?
- if place.present?
- # place = 'calendar.place'
- # address = '#{location}, place.address'
-
- # xx place = calendar.place.address
- new_address = place.address.dup
+ if partner.present?
+ new_address = partner.address.dup
address = new_address.prepend_room_number(data.location)
address.save
-
- else # no place, yes location
+ else # no partner, yes location
raise Problem, 'N/A'
end
-
- elsif place.present? # no location
- address = place.address
- # place = 'calendar.place'
- # address = 'calendar.place.address'
- # xx place = calendar.place.address
-
+ elsif partner.present? # no location
+ address = partner.address
else # no place, no location
raise Problem, 'N/A'
end
-
- [place, address]
+ [partner, address]
end
- def no_location_strategy(_place, _address: nil)
+ def no_location_strategy(_partner, _address: nil)
[nil, nil]
end
- def online_only_strategy(_place, _address: nil)
+ def online_only_strategy(_partner, _address: nil)
[nil, nil]
end
@@ -218,10 +145,6 @@ def save_all_occurences
unless event.update(attributes)
notices << event.errors.full_messages.join(', ')
end
-
- if event.address_id.blank? && calendar.strategy == 'event'
- notices << "No place or address could be created or found for the event location: #{event.raw_location_from_source}"
- end
end
end
diff --git a/app/jobs/calendar_importer/events/ics_event.rb b/app/jobs/calendar_importer/events/ics_event.rb
index a398148f6..685a94b7e 100644
--- a/app/jobs/calendar_importer/events/ics_event.rb
+++ b/app/jobs/calendar_importer/events/ics_event.rb
@@ -50,42 +50,50 @@ def occurrences_between(from, to)
def online_event?
# Either return the google conference value, or find the link in the description
- link = @event.custom_properties.fetch 'x_google_conference', nil
+ link = @event.url
+ link ||= @event.custom_properties['x_google_conference']
+ link ||= maybe_location_is_link
link ||= find_event_link
- return unless link
+ link = link.first if link.is_a?(Array)
+ link = link.to_s
- # Then grab the first element of either the match object or the conference array
- # (The match object returns ICal Text, not a String, so we have to cast)
- # (We can't use .first here because the match object doesn't support it!)
- #
- online_address = OnlineAddress.find_or_create_by(url: link[0].to_s,
- link_type: have_direct_url_to_stream?(link[0].to_s))
+ return if link.blank?
+
+ online_address = OnlineAddress.find_or_create_by(url: link,
+ link_type: have_direct_url_to_stream?(link))
online_address.id
end
private
+ def maybe_location_is_link
+ return if location.blank?
+
+ URI.parse(location).to_s
+
+ rescue URI::InvalidURIError
+ # no URL found
+ end
+
def have_direct_url_to_stream?(link)
- # Oh my god why is ruby's iteration stuff so annoying
- # also TODO: find a different name than "value"
- domain = event_link_types.keys.find(proc {}) { |domain| link.include?(domain) }
+ domain = event_link_types.keys.find { |domain| link.include?(domain) }
return event_link_types[domain][:type] if domain
- # Because there is a type for each URL handled, this should never occur
- # However, in the future, those URLs will be edited, so we should guard against this
- raise MissingTypeForURL, "Type (direct/indirect) missing for URL #{link}"
+ 'indirect'
end
def find_event_link
link_regexes = event_link_types.values.pluck(:regex)
regex = Regexp.union link_regexes
- regex.match description
+ regex.match(description).to_a
end
def event_link_types
+ # this only detects "direct" link types now and everything else is "indirect"
+
http = %r{(http(s)?://)?} # - https:// or http:// or nothing
alphanum = /[A-Za-z0-9]+/ # - alphanumeric strings
subdomain = /(#{alphanum}\.)?/ # - matches the www. or us04web in the zoom link
@@ -104,8 +112,7 @@ def event_link_types
{
'meet.jit.si' => { regex: %r{#{http}#{subdomain}meet.jit.si/#{suffix}}, type: 'direct' },
'meet.google.com' => { regex: %r{#{http}#{subdomain}meet.google.com/#{suffix}}, type: 'direct' },
- 'zoom.us' => { regex: %r{#{http}#{subdomain}zoom.us/j/#{suffix}}, type: 'direct' },
- 'facebook.com' => { regex: %r{#{http}#{subdomain}facebook.com/events/#{suffix}}, type: 'indirect' }
+ 'zoom.us' => { regex: %r{#{http}#{subdomain}zoom.us/j/#{suffix}}, type: 'direct' }
}
end
end
diff --git a/app/jobs/calendar_importer/parsers/base.rb b/app/jobs/calendar_importer/parsers/base.rb
index 0c0bc20ce..6d1f8a142 100644
--- a/app/jobs/calendar_importer/parsers/base.rb
+++ b/app/jobs/calendar_importer/parsers/base.rb
@@ -11,7 +11,7 @@ class Base
PUBLIC = true
NAME = ''
KEY = ''
- Output = Struct.new(:events, :checksum)
+ Output = Struct.new(:events, :checksum_changed)
def self.handles_url?(calendar)
calendar.source =~ allowlist_pattern
@@ -31,10 +31,13 @@ def initialize(calendar, options = {})
def calendar_to_events
data = download_calendar
checksum = digest(data)
+ checksum_changed = @calendar.last_checksum != checksum
+ ## record if checksum has changed since last time we interacted with it.
+ ## if download_calendar fails it should raise an exception so this code won't run
+ @calendar.flag_checksum_change!(checksum) if checksum_changed
+ return Output.new([], checksum) if !@force_import && !checksum_changed
- return Output.new([], checksum) if !@force_import && (@calendar.last_checksum == checksum)
-
- Output.new(import_events_from(data), checksum)
+ Output.new(import_events_from(data), checksum_changed)
end
# @abstract Subclass is expect to implmement #download_calendar
diff --git a/app/jobs/calendar_importer/parsers/ics.rb b/app/jobs/calendar_importer/parsers/ics.rb
index f4ef28b89..b945da902 100644
--- a/app/jobs/calendar_importer/parsers/ics.rb
+++ b/app/jobs/calendar_importer/parsers/ics.rb
@@ -25,7 +25,6 @@ def self.allowlist_pattern
outlook: %r{^https?://outlook\.(office365|live)\.com/owa/calendar/.*},
webcal: %r{^webcal://},
mossley: %r{^https?://mossleycommunitycentre\.org\.uk},
- theproudtrust: %r{^https?://www\.theproudtrust\.org},
teamup: %r{^https?://ics\.teamup\.com/feed/.*},
consortium: %r{^https://www\.consortium\.lgbt/events/.*},
generic: %r{^https?://.*\.ics$}
@@ -36,7 +35,9 @@ def self.allowlist_pattern
def download_calendar
# Why are we doing this?
url = @url.gsub(%r{webcal://}, 'https://') # Remove the webcal:// and just use the part after it
- Base.read_http_source url
+ res = Base.read_http_source url
+ # sanatize google calendar to prevent each requesting resetting the checksum
+ res.split("\n").reject { |l| l.include? 'DTSTAMP' }.join("\n")
end
def import_events_from(data)
diff --git a/app/models/address.rb b/app/models/address.rb
index 483cc82b6..812dc4ed3 100644
--- a/app/models/address.rb
+++ b/app/models/address.rb
@@ -105,34 +105,6 @@ def geocode_with_ward
end
class << self
- # location - The raw location field
- # components - Array containing parts of an event's location field, excluding the postcode.
- def search(_location, components, postcode)
- return nil if components.empty?
-
- # try by street name string match
- address = Address.find_by('lower(street_address) IN (?)', components.map(&:downcase))
- return address if address
-
- ukpc = UKPostcode.parse(postcode.to_s)
-
- if ukpc.full_valid?
- address = Address.find_by(postcode: ukpc.to_s)
- return address if address
- end
-
- begin
- # now just create one
- Address.build_from_components(components, postcode)
- rescue ActiveRecord::RecordNotFound
- # This 'solution' makes it so if an address is provided to us that
- # does not map cleanly on to our neighbourhood table then we simply
- # consider that a failed look up. In practice we should do more to
- # normalize data both coming from Postcodes.io and our UK government
- # ward dataset
- end
- end
-
def build_from_components(components, postcode)
return if components.blank?
@@ -142,7 +114,7 @@ def build_from_components(components, postcode)
street_address3: components[2]&.strip,
postcode: postcode
)
- address if address.save
+ address.save ? address : nil
end
end
end
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index 4022c7be8..8a7559355 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -22,8 +22,11 @@ class Calendar < ApplicationRecord
validate :check_source_reachable
+ before_save :clear_status_on_source_change
before_save :update_notice_count
+ after_create :automatically_queue_calendar
+
# Output the calendar's name when it's requested as a string
alias_attribute :to_s, :name
@@ -31,7 +34,7 @@ class Calendar < ApplicationRecord
# @attr [Enumerable] :strategy
enumerize(
:strategy,
- in: %i[event place room_number event_override no_location online_only],
+ in: %i[event_override event place room_number no_location online_only],
default: :place,
scope: true
)
@@ -117,6 +120,10 @@ def update_notice_count
self.notice_count = (notices || []).count if notices_changed?
end
+ def automatically_queue_calendar
+ queue_for_import! false, DateTime.now
+ end
+
#
# calendar state mutators
#
@@ -129,12 +136,12 @@ def update_notice_count
# date to use as the start point
#
# @return nothing
- def queue_for_import!(force_import, from_date)
+ def queue_for_import!(force_import, from_date = Time.now)
transaction do
return if is_busy?
Calendar.record_timestamps = false
- update! calendar_state: :in_queue
+ update! calendar_state: :in_queue, notices: nil
CalendarImporterJob.perform_later id, from_date, force_import
@@ -151,7 +158,7 @@ def flag_start_import_job!
return unless calendar_state.in_queue?
Calendar.record_timestamps = false
- update! calendar_state: :in_worker
+ update! calendar_state: :in_worker, notices: nil
ensure
Calendar.record_timestamps = true
@@ -167,7 +174,7 @@ def flag_start_import_job!
# @param checksum [integer]
# integer checksum of retrieved source payload
# @return nothing
- def flag_complete_import_job!(notices, checksum, importer_used)
+ def flag_complete_import_job!(notices, importer_used)
transaction do
return unless calendar_state.in_worker?
@@ -176,7 +183,6 @@ def flag_complete_import_job!(notices, checksum, importer_used)
update!(
calendar_state: :idle,
notices: notices,
- last_checksum: checksum,
last_import_at: DateTime.current,
critical_error: nil,
importer_used: importer_used
@@ -187,6 +193,18 @@ def flag_complete_import_job!(notices, checksum, importer_used)
end
end
+ def flag_checksum_change!(checksum)
+ transaction do
+ Calendar.record_timestamps = false
+ update!(
+ last_checksum: checksum,
+ checksum_updated_at: DateTime.current
+ )
+ ensure
+ Calendar.record_timestamps = true
+ end
+ end
+
def flag_bad_source!(problem)
transaction do
return unless calendar_state.in_worker?
@@ -272,4 +290,12 @@ def check_source_reachable
rescue CalendarImporter::Exceptions::UnsupportedFeed => e
errors.add :source, 'Unable to autodetect calendar format, please pick an option from the list below'
end
+
+ def clear_status_on_source_change
+ return unless source_changed?
+
+ self.critical_error = nil
+ self.notices = nil
+ self.last_import_at = nil
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index b33b90083..f1d8f648e 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -41,6 +41,13 @@ class Event < ApplicationRecord
where('(DATE(dtstart) >= (?)) AND (DATE(dtstart) <= (?))', week_start, week_end)
}
+ # Find by day onwards
+ scope :future, lambda { |day|
+ day_start = day.midnight # 2024-04-01 00:00:00 +0100
+ where('dtstart >= ?',
+ day_start)
+ }
+
# For the API eventFilter find by neighbourhood
scope :for_neighbourhoods, lambda { |neighbourhoods|
neighbourhood_ids = neighbourhoods.map(&:id)
@@ -54,23 +61,37 @@ class Event < ApplicationRecord
}
scope :with_tags, lambda { |tags|
- tag_ids = tags.map(&:id)
-
joins(:partner)
.joins('left outer join partner_tags on partners.id = partner_tags.partner_id')
- .where('partner_tags.tag_id in (?)', tag_ids)
+ .where(
+ 'partner_tags.tag_id in (:tag_ids)',
+ tag_ids: tags.map(&:id)
+ )
}
# Filter by Site
scope :for_site, lambda { |site|
- site_neighbourhood_ids = site.owned_neighbourhoods.map(&:id)
-
- joins('left join addresses on events.address_id = addresses.id')
- .joins('left join partners on events.partner_id = partners.id')
- .joins('left join service_areas on partners.id = service_areas.partner_id')
- .where('(service_areas.neighbourhood_id in (?)) or (addresses.neighbourhood_id in (?))',
- site_neighbourhood_ids,
- site_neighbourhood_ids)
+ partners = Partner.for_site(site)
+
+ if site&.tags&.any?
+ left_joins(:address)
+ .where(
+ 'partner_id in (:partner_ids) OR '\
+ '(lower(addresses.street_address) in (:partner_names) AND '\
+ 'lower(addresses.postcode) in (:partner_postcodes))',
+ partner_ids: partners.map(&:id),
+ partner_names: partners.map { |p| p.name.downcase },
+ partner_postcodes: partners.map(&:address).keep_if(&:present?).map { |a| a.postcode.downcase }
+ )
+ else
+ left_joins(:address)
+ .where(
+ 'partner_id in (:partner_ids) OR '\
+ 'addresses.neighbourhood_id in (:neighbourhoods)',
+ neighbourhoods: site.owned_neighbourhoods.map(&:id),
+ partner_ids: partners.map(&:id)
+ )
+ end
}
# Filter by Place
@@ -139,15 +160,16 @@ def neighbourhood
end
def location
- use_address = address || partner&.address
- # (address if address.present?) ||
- # (partner.address if partner.present?)
-
+ use_address = address || partner_at_location&.address || partner&.address
return '' if use_address.nil?
use_address.to_s
end
+ def partner_at_location
+ @partner_at_location ||= place || Partner.find_from_event_address(address)
+ end
+
# TODO: plan this out on paper, currently half finished
# Who to contact if the event is wrong
def blame
diff --git a/app/models/partner.rb b/app/models/partner.rb
index bb40cd157..a5b5d62da 100644
--- a/app/models/partner.rb
+++ b/app/models/partner.rb
@@ -51,7 +51,17 @@ class Partner < ApplicationRecord
accepts_nested_attributes_for :calendars, allow_destroy: true
- accepts_nested_attributes_for :address, reject_if: ->(c) { c[:postcode].blank? && c[:street_address].blank? }
+ # If any of the address formfields are present we attempt to create an address
+ # this will trigger the validation
+ accepts_nested_attributes_for :address, reject_if: lambda { |c|
+ [c[:city],
+ c[:postcode],
+ c[:street_address],
+ c[:street_address2],
+ c[:street_address3]].all?(&:blank?)
+ }
+
+ validates_associated :address
accepts_nested_attributes_for :service_areas, allow_destroy: true
@@ -92,8 +102,6 @@ class Partner < ApplicationRecord
format: { with: EMAIL_REGEX, message: 'invalid email address' },
allow_blank: true
- validates_associated :address, if: ->(p) { p.address.present? }
-
validate :check_neighbourhood_access
validate :neighbourhood_admin_address_access, on: %i[create update]
@@ -157,14 +165,19 @@ class Partner < ApplicationRecord
return none if site_neighbourhood_ids.empty?
query
+ .visible
.left_joins(:address, :service_areas)
.where(
- 'NOT hidden AND (service_areas.neighbourhood_id in (:neighbourhood_ids) OR addresses.neighbourhood_id in (:neighbourhood_ids))',
+ '(service_areas.neighbourhood_id in (:neighbourhood_ids) OR addresses.neighbourhood_id in (:neighbourhood_ids))',
neighbourhood_ids: site_neighbourhood_ids
)
.distinct
}
+ scope :visible, lambda {
+ where(hidden: false)
+ }
+
scope :for_site_with_tag, lambda { |site, tag|
return none if tag.nil?
@@ -207,17 +220,6 @@ class Partner < ApplicationRecord
where.not(address_id: nil)
}
- # Get all Partners that have hosted an event in the last month or will host
- # an event in the future
- #
- # TODO? This might be an incredibly inefficient query. If so, add a column
- # to the Partner table, e.g. place_latest_dtstart, which can be updated on
- # import.
- scope :event_hosts, lambda {
- joins('JOIN events ON events.place_id = partners.id')
- .where('events.dtstart > ?', Date.today - 30).distinct
- }
-
# Get all Partners that manage at least one other Partner.
scope :managers, lambda {
joins('JOIN organisation_relationships o_r on o_r.partner_subject_id = partners.id')
@@ -372,8 +374,23 @@ def neighbourhood_name_for_site(badge_zoom_level)
end
end
- def self.fuzzy_find_by_location(components)
- Partner.find_by('lower(name) IN (?)', components.map(&:downcase))
+ def self.find_from_event_address(address)
+ address_components = [
+ address&.street_address || '',
+ address&.street_address2 || '',
+ address&.street_address3 || ''
+ ].reject(&:empty?)
+
+ if address_components.any? && address&.postcode
+ Partner.left_joins(:address)
+ .find_by(
+ 'can_be_assigned_events AND '\
+ 'lower(name) IN (:components) AND '\
+ 'lower(addresses.postcode) = (:postcode)',
+ components: address_components.map(&:downcase),
+ postcode: address.postcode.downcase
+ )
+ end
end
def self.neighbourhood_names_for_site(current_site, badge_zoom_level)
@@ -484,9 +501,10 @@ def opening_times_is_json_or_nil
end
def three_or_less_category_tags
- return if categories.count < 4
+ # we can't just use categories.count here because of STI, on create they won't exist yet
+ return if category_ids.count < 4
- errors.add :base, 'Partner.tags can contain a maximum of 3 Category tags'
+ errors.add :categories, 'Partners can have a maximum of 3 Category tags'
end
def partnership_admins_must_add_tag
diff --git a/app/models/partnership.rb b/app/models/partnership.rb
index 7f814ad0b..3fb4bfd9d 100644
--- a/app/models/partnership.rb
+++ b/app/models/partnership.rb
@@ -1,4 +1,9 @@
# frozen_string_literal: true
class Partnership < Tag
+ scope :users_partnerships, lambda { |user|
+ return Partnership.all if user.role == 'root' && !user.partnership_admin?
+
+ user.partnerships
+ }
end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index e1748a06a..b11047e64 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -34,6 +34,8 @@ class Tag < ApplicationRecord
}
validate :check_editable_fields
+ # TECHDEBT
+ # we should look to work this out of the system as we now cover this with a scope on Partnership
scope :users_tags, lambda { |user|
return Tag.all if user.role == 'root' && !user.partnership_admin?
diff --git a/app/models/user.rb b/app/models/user.rb
index 0f1a44f55..0194b55d6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -29,6 +29,7 @@ class User < ApplicationRecord
has_many :tags_users, dependent: :destroy
has_many :tags, through: :tags_users
+ has_many :partnerships, through: :tags_users, source: :tag, class_name: 'Partnership'
validates :email,
presence: true,
diff --git a/app/policies/partner_policy.rb b/app/policies/partner_policy.rb
index 2b92bda54..46e23df06 100644
--- a/app/policies/partner_policy.rb
+++ b/app/policies/partner_policy.rb
@@ -50,16 +50,17 @@ def permitted_attributes
:public_name, :public_email, :public_phone,
:partner_name, :partner_email, :partner_phone,
:address_id, :url, :facebook_link, :twitter_handle, :instagram_handle,
- :opening_times,
+ :opening_times, :can_be_assigned_events,
{ calendars_attributes: %i[id name source strategy place_id partner_id _destroy],
address_attributes: %i[id street_address street_address2 street_address3 city postcode],
service_areas_attributes: %i[id neighbourhood_id _destroy],
- tag_ids: [] }]
+ tag_ids: [], category_ids: [], facility_ids: [] }]
attrs << :slug if user.root?
attrs << :hidden if user.root? || user.neighbourhood_admin? || user.partnership_admin?
attrs << :hidden_reason if user.root? || user.neighbourhood_admin? || user.partnership_admin?
attrs << :hidden_blame_id if user.root? || user.neighbourhood_admin? || user.partnership_admin?
+ attrs << { partnership_ids: [] } if user.root? || user.partnership_admin?
attrs
end
diff --git a/app/policies/partnership_policy.rb b/app/policies/partnership_policy.rb
index 8b24fae63..c25ce16d1 100644
--- a/app/policies/partnership_policy.rb
+++ b/app/policies/partnership_policy.rb
@@ -1,4 +1,9 @@
# frozen_string_literal: true
class PartnershipPolicy < TagPolicy
+ class Scope < Scope
+ def resolve
+ Partnership.users_partnerships(user)
+ end
+ end
end
diff --git a/app/views/admin/calendars/_form.html.erb b/app/views/admin/calendars/_form.html.erb
index bd8443af6..cc06aeae4 100644
--- a/app/views/admin/calendars/_form.html.erb
+++ b/app/views/admin/calendars/_form.html.erb
@@ -1,14 +1,12 @@
-<% if @calendar.critical_error %>
-
- Error: <%= @calendar.critical_error %>
-
-<% end %>
-
-<%= link_to "View this calendar",
- admin_calendar_path(@calendar.id),
- class: "btn btn-sm btn-primary" unless @calendar.new_record? %>
+
+ <%= link_to "View this calendar",
+ admin_calendar_path(@calendar.id),
+ class: "btn btn-sm btn-primary" unless @calendar.new_record? %>
+<%= render('importer_overview', calendar: @calendar) %>
+
+
+
+
Public Contact Information
-
+
<%= f.button :submit, class: "btn btn-primary mr-3" %>
<% unless @calendar.new_record? %>
<%= link_to "Destroy Calendar", admin_calendar_path(@calendar), method: :delete, class: "btn btn-danger" %>
<% end %>
<% end %>
-
-<%= render('import') unless @calendar.new_record? %>
diff --git a/app/views/admin/calendars/_import.html.erb b/app/views/admin/calendars/_import.html.erb
deleted file mode 100644
index bdf544cd7..000000000
--- a/app/views/admin/calendars/_import.html.erb
+++ /dev/null
@@ -1,66 +0,0 @@
-
-Import Status for <%= @calendar.name %>
-
-<%= render 'status' %>
-
-<% if @calendar.can_be_requeued? %>
- <% if @calendar.calendar_state.bad_source? %>
- We're unable to read this calendar - the URL may be broken or there may be some invalid data in it.
Please check the calendar is set up correctly and try again.
-
- <% end %>
-
- <% if @calendar.last_import_at %>
- Last updated <%= time_ago_in_words(@calendar.last_import_at) %> ago (<%= @calendar.last_import_at %>)
- <% end %>
-
-
-
-
-
Update now
- <%= simple_form_for :import, { url: import_admin_calendar_path(@calendar) } do |f| %>
- <%= f.input :starting_from, input_html: { name: 'starting_from', value: Date.today.strftime('%Y-%m-%d') }, placeholder: 'YYYY-MM-DD', hint: 'What date should the importer start from?' %>
- <%= f.submit 'Queue for import now', class: 'btn btn-primary' %>
- <% end %>
-
-
-
-
Important Notices
- <% if @calendar.notices.present? %>
-
The following events could not be imported due to invalid data:
-
- <% @calendar.notices.each do |text| %>
- - <%= text %>
- <% end %>
-
- <% else %>
- No notices to show
- <% end %>
-
-
-
-
-<% elsif @calendar.is_busy? %>
-The calendar is currently being imported, please check back in a few minutes
-
-<% elsif @calendar.calendar_state.error? %>
-Recent Updates
-Unfortunately a critical error has occurred that means we cannot continue importing your calendar
-Error: <%= @calendar.critical_error %>
-
-<%= simple_form_for :import, { url: import_admin_calendar_path(@calendar) } do |f| %>
- <%= f.input :starting_from, input_html: { name: 'starting_from', value: Date.today.strftime('%Y-%m-%d') }, placeholder: 'YYYY-MM-DD', hint: 'What date should the importer start from?' %>
- <%= f.submit 'Reset and retry', class: 'btn btn-danger' %>
-<% end %>
-<% end %>
-
-
-
-
-
Recent Updates
- <% @versions&.each do |date, activities| %>
-
- <%= l(date, format: :datetime) %> - <%= display_time_since(date) %>
- <%= render 'shared/recent_import_activity', activities: activities %>
-
- <% end %>
-
diff --git a/app/views/admin/calendars/_importer_overview.html.erb b/app/views/admin/calendars/_importer_overview.html.erb
new file mode 100644
index 000000000..cf4ce8b10
--- /dev/null
+++ b/app/views/admin/calendars/_importer_overview.html.erb
@@ -0,0 +1,74 @@
+<% unless calendar.new_record? %>
+ <% retry_button = nil %>
+
+ Import status
+
+ <% if calendar.checksum_updated_at.present? && calendar.last_import_at.present? %>
+ <% if calendar.checksum_updated_at < 6.month.ago || calendar.last_import_at < 6.month.ago %>
+
+
Potential stale calender
+
Is this calender still active? It has not been updated in over 6 months
+
+ <% end %>
+ <% end %>
+
+ <% if calendar.calendar_state.idle? %>
+ <% retry_button = :idle %>
+ success
+ <% end %>
+
+ <% if calendar.calendar_state.in_queue? || calendar.calendar_state.in_worker? %>
+ loading
+ Please check back in a few minutes
+ <% end %>
+
+ <% if calendar.calendar_state.error? || calendar.calendar_state.bad_source? %>
+ <% retry_button = :error %>
+
+ error
+
+
+ Error: <%= calendar.critical_error %>
+
+ <% end %>
+
+
+ <% if calendar.checksum_updated_at.present? %>
+
+ Source data last changed <%= time_ago_in_words(calendar.checksum_updated_at) %> ago (<%= calendar.checksum_updated_at %>)
+
+ <% end %>
+
+ <% if calendar.last_import_at.present? %>
+
+ Last import ran <%= time_ago_in_words(calendar.last_import_at) %> ago (<%= calendar.last_import_at %>)
+
+ <% end %>
+
+ <% if calendar.notices.present? %>
+ <% retry_button = :error %>
+
+ Notices
+ The following events could not be imported due to invalid data:
+
+
+ <% calendar.notices.tally.each do |text, count| %>
+ - <%= text %> <% if count > 1 %>(<%= count %> times)<% end %>
+ <% end %>
+
+ Please check your calendar is set up correctly and try again.
+ <% end %>
+
+ <% if retry_button %>
+ <%= simple_form_for :import, { url: import_admin_calendar_path(calendar) } do |f| %>
+ <% if retry_button == :idle %>
+ <%= f.submit 'Re-import now', class: 'btn btn-primary' %>
+ <% elsif retry_button == :error %>
+ <%= f.submit 'Retry', class: 'btn btn-danger' %>
+ <% end %>
+ <% end %>
+ <% end %>
+
+<% end %>
+
+
diff --git a/app/views/admin/calendars/_status.html.erb b/app/views/admin/calendars/_status.html.erb
deleted file mode 100644
index 702d38f3b..000000000
--- a/app/views/admin/calendars/_status.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= @calendar.calendar_state %>
diff --git a/app/views/admin/calendars/index.html.erb b/app/views/admin/calendars/index.html.erb
index 032931d68..5fca3e514 100644
--- a/app/views/admin/calendars/index.html.erb
+++ b/app/views/admin/calendars/index.html.erb
@@ -11,7 +11,7 @@ var columns = [
return (type == "display") ? date.strtime : date.unixtime;
}
},
- {"data": "updated_at",
+ {"data": "checksum_updated_at",
"render": function (data, type, row) {
date = JSON.parse(data.replace(/"/g, '"'));
return (type == "display") ? date.strtime : date.unixtime;
@@ -24,8 +24,8 @@ var columns = [
<%= render partial: 'layouts/admin/datatable', locals: {
title: 'Calendars',
model: :calendars,
- column_titles: ['Name', 'Partners', 'Notices', 'Events', 'Status', 'Last imported', 'Last updated'],
- columns: %i[name partner notice_count events calendar_state last_import_at updated_at],
+ column_titles: ['Name', 'Partners', 'Notices', 'Events', 'Status', 'Last imported', 'Source updated'],
+ columns: %i[name partner notice_count events calendar_state last_import_at checksum_updated_at],
data: @calendars,
source: admin_calendars_path(format: :json),
new_link: new_admin_calendar_path
diff --git a/app/views/admin/calendars/show.html.erb b/app/views/admin/calendars/show.html.erb
index f8ca496cc..d0edcd13f 100644
--- a/app/views/admin/calendars/show.html.erb
+++ b/app/views/admin/calendars/show.html.erb
@@ -1,6 +1,8 @@
<%= @calendar.name %>
+<%= link_to "Edit this calendar", edit_admin_calendar_path(@calendar.id), class: "btn btn-primary btn-sm" %>
-<%= render 'status' %>
+<%= render 'importer_overview', calendar: @calendar %>
+
Published by <%= link_to @calendar.partner, edit_admin_partner_path(@calendar.partner) %>. Using <%= content_tag :tt, @calendar.strategy %> strategy to import <%= content_tag :tt, @calendar.events.count %> events. <%= calendar_last_imported(@calendar) %>.
@@ -12,7 +14,6 @@
<% end %>
-<%= link_to "Edit this calendar", edit_admin_calendar_path(@calendar.id), class: "btn btn-primary btn-sm" %>
@@ -32,22 +33,4 @@
<% end %>
-
-
-
Important Notices
- <% if @calendar.notices.present? %>
-
The following events could not be imported due to invalid data:
-
- <% @calendar.notices.each do |notice| %>
- -
- <%= content_tag :strong, notice["event"]["dtstart"].to_date.strftime('%e %b %Y') %>.
- <%= content_tag :em, notice["event"]["summary"] %>.
- <%= notice["errors"].join(", ")%>
-
- <% end %>
-
- <% else %>
- No notices to show
- <% end %>
-
diff --git a/app/views/admin/partners/_address_fields.html.erb b/app/views/admin/partners/_address_fields.html.erb
index 3bb6b1fc1..46ecf2893 100644
--- a/app/views/admin/partners/_address_fields.html.erb
+++ b/app/views/admin/partners/_address_fields.html.erb
@@ -8,7 +8,9 @@
<%= address_form.input :street_address,
class: "form-control address_1 address_field",
- label: 'Street address' %>
+ label: 'Street address',
+ required: false
+ %>
<%= address_form.input :street_address2,
class: "form-control address_2 address_field",
@@ -22,7 +24,9 @@
class: "form-control city address_field" %>
<%= address_form.input :postcode,
- class: "form-control postcode address_field" %>
+ class: "form-control postcode address_field",
+ required: false
+ %>
<% if partner.can_clear_address?(current_user) %>
diff --git a/app/views/admin/partners/_form.html.erb b/app/views/admin/partners/_form.html.erb
index f40486a20..6cbff49c6 100644
--- a/app/views/admin/partners/_form.html.erb
+++ b/app/views/admin/partners/_form.html.erb
@@ -143,16 +143,38 @@
Tags
- What partnerships, categories and facilities does this partner have or belong to?
- Partners may have up to 3 category tags.
- <%= f.association :tags,
- label: false,
- collection: options_for_partner_tags(@partner),
- input_html: { class: 'form-check', data: {
- controller: @partner_tags_controller,
- "partner-tags-permitted-tags-value": permitted_options_for_partner_tags
+ What other associations does this partner have apart from place.
+ <% if current_user.partnership_admin? || current_user.root? %>
+ <%= f.association :partnerships,
+ label: "Partnerships",
+ hint:"Which communities of interest should this partner appear on",
+ collection: options_for_partner_partnerships(),
+ input_html: { class: 'form-check', data: {
+ controller: 'partner-tags',
+ "partner-tags-permitted-tags-value": permitted_options_for_partner_tags
+ } } %>
+
+ <% end %>
+ <%= f.association :facilities,
+ label: "Facilities",
+ hint:"What infrastructure does this partner provide.",
+ collection: Facility.all,
+ input_html: { class: 'form-check', data: {
+ controller: 'select2',
} } %>
+ <%= f.association :categories,
+ label: "Categories",
+ hint:"Partners may have up to 3 category tags, the public will be able to filter by these",
+ collection: Category.all,
+ input_html: { class: 'form-check', data: {
+ controller: 'select2',
+ } } %>
+
+ Event matching
+ Can events imported from other partner's calendars be listed at this partner if their address matches?
+ <%= f.input :can_be_assigned_events, class: "form-control" %>
+
<% unless @partner.new_record? %>
<% if policy(@partner).permitted_attributes.include? :hidden %>
diff --git a/app/views/admin/partners/edit.html.erb b/app/views/admin/partners/edit.html.erb
index dad453391..c66dcf6fb 100644
--- a/app/views/admin/partners/edit.html.erb
+++ b/app/views/admin/partners/edit.html.erb
@@ -16,14 +16,10 @@
<% partners_sites_links = site_links %>
<% if partners_sites_links.present? %>
- This partner appears on the following sites: <%= site_links %>.
+ View this partner on: <%= site_links %> (opens in new tab).
<% else %>
- This partner does not appear on any sites. This usually means one of two things:
-
- - The address is outside any current PlaceCal instance's range due to where actual ward boundaries happen to fall. Please contact your PlaceCal organiser if this is the case.
- - It is inside a PlaceCal range, but you forgot to add a tag. Resave this partner with the appropriate tag.
- - If neither of these are the case, please contact support@placecal.org with the link to this page.
-
+
+ This partner does not appear on any PlaceCal sites. It could be missing a partnership tag, or based in a location that falls outside of any site's neighbourhoods. Contact your PlaceCal organiser or support@placecal.org to help fix this.
<% end %>
diff --git a/app/views/admin/partners/new.html.erb b/app/views/admin/partners/new.html.erb
index cc95ced64..4c7e0629b 100644
--- a/app/views/admin/partners/new.html.erb
+++ b/app/views/admin/partners/new.html.erb
@@ -3,4 +3,81 @@
-<%= render 'form', model: :partner %>
+
diff --git a/app/views/admin/partners/setup.html.erb b/app/views/admin/partners/setup.html.erb
deleted file mode 100644
index 6e1ba9bf4..000000000
--- a/app/views/admin/partners/setup.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-
New Partner: Step One
-
-<%= render_component "error", object: @partner %>
-
-<%= simple_form_for @partner, url: setup_admin_partners_url, method: :post do |f| %>
- <%= f.input :name %>
- <%= f.fields_for :address, @partner.address || Address.new do |a| %>
- <%= a.input :street_address %>
- <%= a.input :postcode %>
- <% end %>
-
-
-
-
- <%= f.submit "Next", class: "btn btn-primary h1" %>
-
-
-
-<% end %>
diff --git a/app/views/admin/sites/show.html.erb b/app/views/admin/sites/show.html.erb
index d91693ed8..231cc4b2a 100644
--- a/app/views/admin/sites/show.html.erb
+++ b/app/views/admin/sites/show.html.erb
@@ -9,6 +9,7 @@
Name |
State |
Last imported at |
+
Last source data change at |
@@ -17,8 +18,10 @@
<%= calendar.id %> |
<%= link_to calendar.name, edit_admin_calendar_path(calendar) %> |
<%= calendar.calendar_state %> |
- <%= calendar_last_imported(calendar) %> |
+ <%= time_ago_in_words(calendar.last_import_at) %> |
+ <%= time_ago_in_words(calendar.checksum_updated_at) %> |
<% end %>
+
diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb
index 589b814e9..ae7c201b0 100644
--- a/app/views/devise/passwords/new.html.erb
+++ b/app/views/devise/passwords/new.html.erb
@@ -1,21 +1,22 @@
Forgot your password?
-
+
+ Enter the email address associated with your PlaceCal account and we will send you an email with password reset instructions.
+
+
<%= form_for(resource, as: resource_name,
url: password_path(resource_name),
html: { method: :post, class: 'form', data: { turbo: 'false' } }) do |f| %>
- <%= render "devise/shared/error_messages", resource: resource %>
-
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
- <%= f.submit "Send me reset password instructions", class: "btn btn--big btn--home-3" %>
+ <%= f.submit "Submit", class: "btn btn--big btn--home-3" %>
<% end %>
diff --git a/app/views/events/index_simple.html.erb b/app/views/events/index_simple.html.erb
index f39217ca6..07d545c0f 100644
--- a/app/views/events/index_simple.html.erb
+++ b/app/views/events/index_simple.html.erb
@@ -6,7 +6,7 @@
<%= event.summary %>
<%= "#{event.date}, #{event.time}" %>
<%= event.description %>
- <%= "#{event.place}, #{event.location}" %>
+ <%= "#{event.partner_at_location}, #{event.location}" %>
<%= link_to "https://placecal.org#{event_path(event)}", "https://placecal.org#{event_path(event)}" %>
diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb
index f513e86da..947740d05 100644
--- a/app/views/events/show.html.erb
+++ b/app/views/events/show.html.erb
@@ -4,7 +4,7 @@
<%= render_component "event", context: :page, event: @event, primary_neighbourhood: @primary_neighbourhood %>
-
+
<%= @event.description_html.to_s.html_safe %>
<%= event_link(@event) %>
@@ -12,8 +12,8 @@
<%= online_link %>
-
-
+
+
<% if @event.partner %>
Contact information
@@ -26,7 +26,7 @@
<% end %>
-
+
Event address
<%= render_component "address",
@@ -35,6 +35,12 @@
%>
+
+
Event organiser
+
+ <%= link_to @event.partner, @event.partner %>
+
+
<%= render 'shared/map', points: @map, site: @current_site.slug, style: :multi %>
diff --git a/app/views/layouts/admin/_admin_navigation.html.erb b/app/views/layouts/admin/_admin_navigation.html.erb
index 800f83415..b78ab2d3e 100644
--- a/app/views/layouts/admin/_admin_navigation.html.erb
+++ b/app/views/layouts/admin/_admin_navigation.html.erb
@@ -14,6 +14,14 @@
<%= admin_nav_link('Tags', admin_tags_path, 'tags') if policy(Tag).index? %>
<% end -%>
+
+ <%= admin_nav_link('Handbook', 'https://handbook.placecal.org/', 'book')%>
+ <%= admin_nav_link('Discord', 'http://discord.gfsc.studio/', 'comment')%>
+ <%= admin_nav_link('Report a bug', 'https://github.com/geeksforsocialchange/PlaceCal/issues/new?assignees=&labels=&template=bug_report.md', 'bug')%>
+
support@placecal.org
+
<% if policy(Collection).index? || policy(Supporter).index? %>
- <%= link_to "Report a bug", "https://github.com/geeksforsocialchange/PlaceCal/issues/new?assignees=&labels=&template=bug_report.md" %>
-
-
<% build = ENV['GIT_REV'] ? ENV['GIT_REV'][0,7] : 'development' %>
-
Build: <%= link_to build, "https://github.com/geeksforsocialchange/PlaceCal/commit/#{build}" %>
+ Build: <%= link_to build, "https://github.com/geeksforsocialchange/PlaceCal/commit/#{build}" %>
diff --git a/app/views/pages/privacy.html.erb b/app/views/pages/privacy.html.erb
index 7e6ed892f..a66da9529 100644
--- a/app/views/pages/privacy.html.erb
+++ b/app/views/pages/privacy.html.erb
@@ -4,22 +4,75 @@
<%= render MaxWidthComponent.new do %>
-
At PlaceCal.org, one of our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information that is collected and recorded by PlaceCal.org and how we use it.
If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us through email at support@placecal.org.
-
Log Files
-
PlaceCal.org follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services’ analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users’ movement on the website, and gathering demographic information.
Cookies and Web Beacons
Like any other website, PlaceCal.org uses ‘cookies’. These cookies are used to store information including visitors’ preferences, and the pages on the website that the visitor accessed or visited. The information is used to optimize the users’ experience by customizing our web page content based on visitors’ browser type and/or other information.
+
One of our main priorities is the privacy of our visitors. This Privacy Policy document contains the types of information that are collected and recorded by placecal.org and placecal-staging.org, and how we use it.
+
If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us.
+
+
Our Contact Details
+
Name: Place Health Technology CIC
+
Address: 5 Ribston Street, Manchester, England, M15 5RH
+
Email: support@placecal.org
+
+
Personal Information
+
The type of personal information we collect
+
We currently collect and process the following information from organisations who sign-up to use PlaceCal’s services, and any individuals who represent them:
+
+ - Personal identifiers, contacts and characteristics (for example, name and contact details)
+ - Email addresses for organisations which may belong to identifiable individuals
+ - Phone numbers for organisations which may belong to identifiable individuals
+ - Addresses for organisations which may belong to identifiable individuals
+ - Addresses for events which may belong to identifiable individuals
+
+
We do not collect personal information from users who are browsing a PlaceCal site without an account.
+
+
How we get the personal information and why we have it
+
Most of the personal information we process is provided to us directly by you for the purpose of creating and hosting events on the PlaceCal platform.
+
We use the information that you have given us in order to share this information with those browsing the site and viewing organisations and their calendars.
+
Under the UK General Data Protection Regulation (UK GDPR), the lawful basis we rely on for processing this information is your consent.
+
You are able to remove your consent at any time. You can do this by contacting support@placecal.org
-
Third Party Privacy Policies
-
PlaceCal.org’s Privacy Policy does not apply to Plausible, which we use to track visits. Thus, we are advising you to consult the respective Privacy Policies of Plausible for more detailed information. It may include their practices and instructions about how to opt-out of certain options. Plausible's Data Policy is here: https://plausible.io/data-policy
Plausible does not use Cookies to track your data, nevertheless, you may choose to disable cookies through your individual browser options. To know more detailed information about cookie management with specific web browsers, it can be found at the browsers’ respective websites.
+
How we store your personal information
+
Your information is securely stored on our server in London, UK.
+
We keep personal identifiers, contacts and characteristics (as described in the "Type of information we collect" section above) for as long as this information is relevant – meaning as long as a partner, user or calendar this information is associated with is active. ‘Active’ in this sense refers to being a live account which can be logged into by the user, a partner with an associated calendar, or a linked calendar with a working source.
+
When a user requests to delete their account we will remove all personal identifying information associated with that user account. This does not automatically remove that same individual’s information from partners and calendars. For a user’s information to be removed from a partner or calendar, or for a partner or calendar that user manages to be deleted, this needs to be specified. Once a user account is deleted their information will be removed from PlaceCal and no longer be accessible by any user or staff.
+
Copies of this data may be created by employees of Geeks for Social Change Ltd and Place Health Technology CIC solely for development purposes. This information is destroyed when employees leave the company.
+
+
Your data protection rights
+
Under data protection law, you have rights including:
+
Your right of access - You have the right to ask us for copies of your personal information.
+
Your right to rectification - You have the right to ask us to rectify personal information you think is inaccurate. You also have the right to ask us to complete information you think is incomplete.
+
Your right to erasure - You have the right to ask us to erase your personal information in certain circumstances.
+
Your right to restriction of processing - You have the right to ask us to restrict the processing of your personal information in certain circumstances.
+
Your right to data portability - You have the right to ask that we transfer the personal information you gave us to another organisation, or to you, in certain circumstances.
+
You are not required to pay any charge for exercising your rights. If you make a request, we have one month to respond to you.
+
Please contact us at support@placecal.org if you wish to make a request.
+
+
How to complain
+
If you have any concerns about our use of your personal information, you can make a complaint to us at support@placecal.org
+
You can also complain to the ICO if you are unhappy with how we have used your data.
+
The ICO’s address:
+
Information Commissioner’s Office
+
Wycliffe House
+
Water Lane
+
Wilmslow
+
Cheshire
+
SK9 5AF
+
Helpline number: 0303 123 1113
+
ICO website: https://www.ico.org.uk
-
Children’s Information
-
Another part of our priority is adding protection for children while using the internet. We encourage parents and guardians to observe, participate in, and/or monitor and guide their online activity.
PlaceCal.org does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.
+
Web Tracking
+
+
Log Files
+
PlaceCal.org's hosting server follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this as a part of hosting services’ analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp. These are not linked to any information that is personally identifiable.
-
Online Privacy Policy Only
-
This privacy policy applies only to our online activities and is valid for visitors to our website with regards to the information that they shared and/or collect in PlaceCal.org. This policy is not applicable to any information collected offline or via channels other than this website.
+
PlaceCal uses an error logging service called Rollbar. This service logs errors that may occur while you are browsing the site. These are not linked to any information that is personally identifiable. The purpose of these logs is to ensure the security and operation of the website is as expected and alert us when it is not.
+
+
Cookies, Web Beacons, Analytics Data and Third Party Privacy Policies
+
+
PlaceCal doesn’t store first party cookies.
+
+
PlaceCal uses a third party service called Plausible which allows us to track site visits. You can consult Plausible’s Privacy Policy here: https://plausible.io/data-policy. Plausible does not use cookies to track your data, nor does it store any personal data or personally identifiable information (PII). We use the information provided by Plausible's logging to analyse usage of the site in order to improve it.
-
Consent
-
By using our website, you hereby consent to our Privacy Policy and agree to its Terms and Conditions.
<% end %>
-
\ No newline at end of file
+
diff --git a/app/views/shared/_events.text.erb b/app/views/shared/_events.text.erb
index f3462e517..646bac68a 100644
--- a/app/views/shared/_events.text.erb
+++ b/app/views/shared/_events.text.erb
@@ -12,7 +12,7 @@
#{sanitize(event.description, tags: [])}
) %>
-<%= event.place.to_s + ', ' if event.place %><%= event.location %>
+<%= event.partner_at_location.to_s + ', ' if event.partner_at_location %><%= event.location %>
<%= %(
https://placecal.org#{event_path(event)}
) %>
diff --git a/app/views/sites/default.html.erb b/app/views/sites/default.html.erb
index 0a17d7e38..07e799131 100644
--- a/app/views/sites/default.html.erb
+++ b/app/views/sites/default.html.erb
@@ -11,8 +11,7 @@
-
PlaceCal is working to make <%= @site.place_name %> a better connected neighbourhood.
-
We're working with <%= link_to 'local organisations', partners_path %> and <%= link_to 'places', places_path %> to create a great calendar of everything that's on in <%= @site.place_name %>.
+
We're working with <%= link_to 'local organisations', partners_path %> to create a great calendar of everything that's on in <%= @site.place_name %>.
<%= link_to "See what's on", events_path, class: "btn btn--lg btn--alt btn--mt" %>
diff --git a/config/routes.rb b/config/routes.rb
index 85c6b024c..9d573bdd1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -35,7 +35,6 @@
resources :neighbourhoods
resources :partners do
collection do
- match :setup, via: %i[get post]
get :lookup_name
end
member do
@@ -68,7 +67,7 @@
# Events
resources :events, only: %i[index show]
- get '/events/:year/:month/:day' => 'events#index', constraints: ymd
+ get '/events/:year/:month/:day' => 'events#index', constraints: ymd, as: :events_by_date
# Partners
resources :partners, only: %i[index show]
diff --git a/db/migrate/20240411192949_add_can_be_assigned_events_to_partner.rb b/db/migrate/20240411192949_add_can_be_assigned_events_to_partner.rb
new file mode 100644
index 000000000..f1801bdee
--- /dev/null
+++ b/db/migrate/20240411192949_add_can_be_assigned_events_to_partner.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddCanBeAssignedEventsToPartner < ActiveRecord::Migration[7.1]
+ def change
+ add_column :partners, :can_be_assigned_events, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20240411200641_partner_can_be_assigned_events_set_value.rb b/db/migrate/20240411200641_partner_can_be_assigned_events_set_value.rb
new file mode 100644
index 000000000..84590433c
--- /dev/null
+++ b/db/migrate/20240411200641_partner_can_be_assigned_events_set_value.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class PartnerCanBeAssignedEventsSetValue < ActiveRecord::Migration[7.1]
+ def up
+ Partner.where(can_be_assigned_events: nil).update_all(hidden: false)
+ end
+
+ def down; end
+end
diff --git a/db/migrate/20240422091648_calendar_checksum_date.rb b/db/migrate/20240422091648_calendar_checksum_date.rb
new file mode 100644
index 000000000..aa2ba83b0
--- /dev/null
+++ b/db/migrate/20240422091648_calendar_checksum_date.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class CalendarChecksumDate < ActiveRecord::Migration[7.1]
+ def up
+ add_column :calendars, :checksum_updated_at, :datetime
+ end
+end
diff --git a/db/migrate/20240422092628_set_calendar_checksum_date.rb b/db/migrate/20240422092628_set_calendar_checksum_date.rb
new file mode 100644
index 000000000..4e19fef8c
--- /dev/null
+++ b/db/migrate/20240422092628_set_calendar_checksum_date.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class SetCalendarChecksumDate < ActiveRecord::Migration[7.1]
+ def change
+ Calendar.update_all('checksum_updated_at=last_import_at')
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bb4fdd090..0a5905537 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_03_21_160911) do
+ActiveRecord::Schema[7.1].define(version: 2024_04_22_092628) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -82,6 +82,7 @@
t.string "calendar_state", default: "idle"
t.string "importer_mode", default: "auto"
t.string "importer_used"
+ t.datetime "checksum_updated_at"
t.index ["partner_id"], name: "index_calendars_on_partner_id"
t.index ["place_id"], name: "index_calendars_on_place_id"
end
@@ -236,6 +237,7 @@
t.integer "hidden_blame_id"
t.string "hidden_reason_html"
t.string "instagram_handle"
+ t.boolean "can_be_assigned_events", default: false
t.index ["address_id"], name: "index_partners_on_address_id"
t.index ["slug"], name: "index_partners_on_slug", unique: true
end
diff --git a/package.json b/package.json
index db8c7f488..68167abff 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"leaflet": "1.9.4",
"lodash": "4.17.21",
"popper.js": "1.16.1",
- "sass": "1.74.1",
+ "sass": "1.75.0",
"select2": "4.0.13"
},
"devDependencies": {
diff --git a/test/controllers/admin/calendars_controller_test.rb b/test/controllers/admin/calendars_controller_test.rb
index 240d46553..c4ca46c1a 100644
--- a/test/controllers/admin/calendars_controller_test.rb
+++ b/test/controllers/admin/calendars_controller_test.rb
@@ -14,6 +14,7 @@ class Admin::CalendarControllerTest < ActionDispatch::IntegrationTest
@neighbourhood_admin.neighbourhoods << @neighbourhood
@calendar = create(:calendar, partner: @partner, place: @partner)
+ @calendar.update!(last_import_at: 1.month.ago)
@citizen = create(:user)
@@ -118,7 +119,7 @@ class Admin::CalendarControllerTest < ActionDispatch::IntegrationTest
suppress_stdout do # The importer uses stdout to tell us progress when we run it locally. Avoid this in tests
VCR.use_cassette('Zion Centre Guide') do
- post import_admin_calendar_path(calendar), params: { starting_from: Date.today }
+ post import_admin_calendar_path(calendar)
assert_redirected_to edit_admin_calendar_path(calendar)
end
end
@@ -147,4 +148,28 @@ class Admin::CalendarControllerTest < ActionDispatch::IntegrationTest
assert_select 'ul#calendar-notices li', count: 3
end
+
+ test 'reporting calendar notices on admin show page' do
+ calendar = VCR.use_cassette(:calendar_for_outlook) do
+ create(:calendar,
+ source: 'https://outlook.office365.com/owa/calendar/8a1f38963ce347bab8cfe0d0d8c5ff16@thebiglifegroup.com/5c9fc0f3292e4f0a9af20e18aa6f17739803245039959967240/calendar.ics',
+ partner: @partner,
+ place: @partner)
+ end
+
+ calendar.calendar_state = 'idle'
+ calendar.notices = [
+ 'Notice 1',
+ 'Notice 2',
+ 'Notice 3'
+ ]
+ calendar.save!
+
+ sign_in @root
+
+ get admin_calendar_path(calendar)
+ assert_predicate response, :successful?
+
+ assert_select 'ul#calendar-notices li', count: 3
+ end
end
diff --git a/test/controllers/admin/partners_controller_test.rb b/test/controllers/admin/partners_controller_test.rb
index 39aa9530e..09702c2c4 100644
--- a/test/controllers/admin/partners_controller_test.rb
+++ b/test/controllers/admin/partners_controller_test.rb
@@ -181,50 +181,6 @@ class Admin::PartnersControllerTest < ActionDispatch::IntegrationTest
end
end
- # Setup Partner
-
- test 'neighbourhood_admin : can access setup' do
- sign_in @neighbourhood_admin
-
- get setup_admin_partners_url
- assert_response :success
- end
-
- test 'neighbourhood_admin : can setup new partner in ward' do
- sign_in @neighbourhood_admin
-
- params = {
- partner: {
- name: 'New Partner',
- address_attributes: {
- street_address: '123 Moss Ln E',
- postcode: 'M15 5DD'
- }
- }
- }
-
- post setup_admin_partners_url, params: params
- assert_redirected_to new_admin_partner_url(params)
- end
-
- test 'neighbourhood_admin : cannot setup new partner not in ward' do
- sign_in @neighbourhood_admin
-
- params = {
- partner: {
- name: 'New Partner',
- address_attributes: {
- street_address: 'Ashton-under-Lyne',
- postcode: 'OL6 8BH'
- }
- }
- }
-
- post setup_admin_partners_url, params: params
-
- assert_template :setup
- end
-
test 'neighbourhood_admin : can see all of a partners service areas' do
sign_in @neighbourhood_admin
get edit_admin_partner_url(@partner)
diff --git a/test/controllers/events_controller_test.rb b/test/controllers/events_controller_test.rb
index 95017ded5..eb6b5ce10 100644
--- a/test/controllers/events_controller_test.rb
+++ b/test/controllers/events_controller_test.rb
@@ -53,7 +53,6 @@ class EventsControllerTest < ActionDispatch::IntegrationTest
get from_site_slug(@site, events_path)
assert_response :success
- assert_select 'ol.events li', 2
end
test 'should get ics with configured subdomain' do
diff --git a/test/factories/calendar.rb b/test/factories/calendar.rb
index 4a7b5bb9e..c8f690180 100644
--- a/test/factories/calendar.rb
+++ b/test/factories/calendar.rb
@@ -3,6 +3,9 @@
FactoryBot.define do
factory(:calendar) do
name { 'Zion Centre' }
+ after :create do |cal|
+ cal.last_import_at = 1.month.ago
+ end
# VCR.use_cassette(:import_test_calendar) do
source { 'https://calendar.google.com/calendar/ical/mgemn0rmm44un8ucifb287coto%40group.calendar.google.com/public/basic.ics' }
@@ -10,6 +13,7 @@
public_contact_name { 'Public Calendar Name' }
public_contact_email { 'public@communitygroup.com' }
public_contact_phone { '0161 0000000' }
+ checksum_updated_at { 1.month.ago }
partner
diff --git a/test/factories/category.rb b/test/factories/category.rb
new file mode 100644
index 000000000..f73c52e8f
--- /dev/null
+++ b/test/factories/category.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :category do
+ sequence(:name) do |n|
+ "Hulme #{n}"
+ end
+
+ slug { name.parameterize }
+ description { 'I am a tag' }
+ type { 'Category' }
+ end
+end
diff --git a/test/factories/neighbourhood.rb b/test/factories/neighbourhood.rb
index 80f0d9703..b4c57969b 100644
--- a/test/factories/neighbourhood.rb
+++ b/test/factories/neighbourhood.rb
@@ -174,4 +174,24 @@
ward.save
end
end
+
+ factory :eventbrite_valid_address_hood, class: 'Neighbourhood' do
+ name { '' }
+ name_abbr { '' }
+ unit { '' }
+ unit_code_key { '' }
+ unit_code_value { 'E05013808' }
+ unit_name { '' }
+ release_date { '' }
+ end
+
+ factory :ldjson_valid_address_hood, class: 'Neighbourhood' do
+ name { '' }
+ name_abbr { '' }
+ unit { '' }
+ unit_code_key { '' }
+ unit_code_value { 'E05012263' }
+ unit_name { '' }
+ release_date { '' }
+ end
end
diff --git a/test/factories/partner.rb b/test/factories/partner.rb
index fb9e5545f..256e5d19c 100644
--- a/test/factories/partner.rb
+++ b/test/factories/partner.rb
@@ -15,6 +15,7 @@
partner_phone { '0161 0000001' }
hidden { false }
+ can_be_assigned_events { false }
summary { 'A cool garden centre' }
description { 'Our cool garden centre is a very cool and neat garden centre. Come to our events. Now.' }
diff --git a/test/factories/tag.rb b/test/factories/tag.rb
index 4c8f5a3c6..a75ef455f 100644
--- a/test/factories/tag.rb
+++ b/test/factories/tag.rb
@@ -14,11 +14,6 @@
system_tag { true }
end
- factory :category do
- sequence(:name) { |n| "Category Tag #{n}" }
- type { 'Category' }
- end
-
factory :partnership do
sequence(:name) { |n| "Partnership Tag #{n}" }
type { 'Partnership' }
diff --git a/test/fixtures/vcr_cassettes/Martin_Harris_Centre.yml b/test/fixtures/vcr_cassettes/Martin_Harris_Centre.yml
index 87447438e..9c21c4377 100644
--- a/test/fixtures/vcr_cassettes/Martin_Harris_Centre.yml
+++ b/test/fixtures/vcr_cassettes/Martin_Harris_Centre.yml
@@ -183,7 +183,7 @@ http_interactions:
(University of Leeds) \nThe event includes a screening of the film document
of KIDNAP (30 mins) and reflections on other relevant work, including Rideout's
Cell Project (2016) and Ali Matthews' What the Money Meant (2014).\n \nFor
- further information please contact the organiser, Stephen Scott-Bottoms: stephen.bottoms@manchester.ac.uk
+ further information please contact the organiser, Alpha Beta: alpha.beta@manchester.ac.uk
\n
confirmedHigher Education2018-07-2011:30:002018-07-2018:00:002018-07-2019:00:00Stephen
- Scott-Bottomsstephen.bottoms@manchester.ac.uk
+ Alpha Betaalpha.beta@manchester.ac.uk
confirmedHigher Education2018-07-2011:30:002018-07-2018:00:002018-07-2012:30:002018-07-2019:00:00Stephen
- Scott-Bottomsstephen.bottoms@manchester.ac.uk
+ xmlns:gpp=\"http://www.columbasystems.com/customers/gpp/xml/details/1-0/\">Alpha Beta
+ alpha.beta@manchester.ac.uk
'M15 5ZA',
+ 'quality' => 1,
+ 'eastings' => 383_348,
+ 'northings' => 396_672,
+ 'country' => 'England',
+ 'nhs_ha' => 'North West',
+ 'longitude' => -2.252301,
+ 'latitude' => 53.466521,
+ 'european_electoral_region' => 'North West',
+ 'primary_care_trust' => 'Manchester Teaching',
+ 'region' => 'North West',
+ 'lsoa' => 'Manchester 019D',
+ 'msoa' => 'Manchester 019',
+ 'incode' => '5ZA',
+ 'outcode' => 'M15',
+ 'parliamentary_constituency' => 'Manchester Central',
+ 'parliamentary_constituency_2024' => 'Manchester Rusholme',
+ 'admin_district' => 'Manchester',
+ 'parish' => 'Manchester, unparished area',
+ 'admin_county' => nil,
+ 'date_of_introduction' => '199706',
+ 'admin_ward' => 'Hulme',
+ 'ced' => nil,
+ 'ccg' => 'NHS Greater Manchester',
+ 'nuts' => 'Manchester',
+ 'pfa' => 'Greater Manchester',
+ 'codes' =>
+ { 'admin_district' => 'E08000003',
+ 'admin_county' => 'E99999999',
+ 'admin_ward' => 'E05011368',
+ 'parish' => 'E43000157',
+ 'parliamentary_constituency' => 'E14000807',
+ 'parliamentary_constituency_2024' => 'E14001353',
+ 'ccg' => 'E38000217',
+ 'ccg_id' => '14L',
+ 'ced' => 'E99999999',
+ 'nuts' => 'TLD33',
+ 'lsoa' => 'E01005214',
+ 'msoa' => 'E02001063',
+ 'lau2' => 'E08000003',
+ 'pfa' => 'E23000005' } }
+ ]
+)
+
Geocoder::Lookup::Test.add_stub(
'OL6 8BH', [
{ 'postcode' => 'OL6 8BH',
diff --git a/test/system/admin/partner_test.rb b/test/system/admin/partner_test.rb
index 7f39458e1..23acddfc9 100644
--- a/test/system/admin/partner_test.rb
+++ b/test/system/admin/partner_test.rb
@@ -11,7 +11,9 @@ class AdminPartnerTest < ApplicationSystemTestCase
create_default_site
@root_user = create :root, email: 'root@lvh.me'
@partner = create :ashton_partner
- @tag = create :tag
+ @partnership = create :partnership
+ @category = create :category
+ @facility = create :tag
@neighbourhood_one = neighbourhoods[1].to_s.tr('w', 'W')
@neighbourhood_two = neighbourhoods[2].to_s.tr('w', 'W')
@@ -40,9 +42,18 @@ class AdminPartnerTest < ApplicationSystemTestCase
assert_select2_single @neighbourhood_one, service_areas[0]
assert_select2_single @neighbourhood_two, service_areas[1]
- tags = select2_node 'partner_tags'
- select2 @tag.name, xpath: tags.path
- assert_select2_multiple [@tag.name_with_type], tags
+ partnerships = select2_node 'partner_partnerships'
+ select2 @partnership.name, xpath: partnerships.path
+ assert_select2_multiple [@partnership.name], partnerships
+
+ categories = select2_node 'partner_categories'
+ select2 @category.name, xpath: categories.path
+ assert_select2_multiple [@category.name], categories
+
+ facilities = select2_node 'partner_facilities'
+ select2 @facility.name, xpath: facilities.path
+ assert_select2_multiple [@facility.name], facilities
+
click_button 'Save Partner'
click_link 'Partners'
@@ -50,9 +61,19 @@ class AdminPartnerTest < ApplicationSystemTestCase
click_link @partner.name
- tags = select2_node 'partner_tags'
+ partnerships = select2_node 'partner_partnerships'
+ find_element_and_retry_if_not_found do
+ assert_select2_multiple [@partnership.name], partnerships
+ end
+
+ categories = select2_node 'partner_categories'
+ find_element_and_retry_if_not_found do
+ assert_select2_multiple [@category.name], categories
+ end
+
+ facilities = select2_node 'partner_facilities'
find_element_and_retry_if_not_found do
- assert_select2_multiple [@tag.name_with_type], tags
+ assert_select2_multiple [@facility.name], facilities
end
service_areas = all_cocoon_select2_nodes 'sites_neighbourhoods'
@@ -106,13 +127,13 @@ class AdminPartnerTest < ApplicationSystemTestCase
await_datatables
click_link @partner.name
- find :css, '.partner_tags', wait: 100
+ find :css, '.partner_partnerships', wait: 100
# very specific bug in the view template here: if opening times has malformed data
# it will cause problems for the javascript that runs the partner tags selector
# (in the browser). so we verify the select2 code has worked by seeing if it has
# correctly done its thing to the tag selector
- assert_selector '.partner_tags ul.select2-selection__rendered'
+ assert_selector '.partner_partnerships ul.select2-selection__rendered'
end
test 'adding dupplicate service_areas to an existing partner does not crash' do
diff --git a/yarn.lock b/yarn.lock
index 1b2dd3fe7..8aca6987c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -643,10 +643,10 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
-sass@1.74.1:
- version "1.74.1"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.74.1.tgz#686fc227d3707dd25cb2925e1db8e4562be29319"
- integrity sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==
+sass@1.75.0:
+ version "1.75.0"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.75.0.tgz#91bbe87fb02dfcc34e052ddd6ab80f60d392be6c"
+ integrity sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"