diff --git a/app/controllers/urls_controller.rb b/app/controllers/urls_controller.rb
index a8cad791..60d66f61 100644
--- a/app/controllers/urls_controller.rb
+++ b/app/controllers/urls_controller.rb
@@ -2,7 +2,7 @@
class UrlsController < ApplicationController
before_action :ensure_signed_in
before_action :set_url, only: %i[edit update destroy]
- before_action :set_url_friendly, only: [:show]
+ before_action :set_url_friendly, only: %i[show update_note]
before_action :set_group, only: %i[index create]
# GET /urls
@@ -111,6 +111,18 @@ def update
end
end
+ # PUT/PATCH /urls/1/note
+ def update_note
+ respond_to do |format|
+ if @url.update(url_params)
+ format.html { redirect_to url_path(@url.keyword), notice: 'Note saved.' }
+ else
+ flash[:error] = @url.errors.full_messages.join(', ')
+ format.html { redirect_to url_path(@url.keyword) }
+ end
+ end
+ end
+
# DELETE /urls/1
# DELETE /urls/1.json
def destroy
@@ -161,7 +173,7 @@ def set_url_friendly
# Never trust parameters from the scary internet,
# only permit the allowlist through.
def url_params
- params.require(:url).permit(:url, :keyword, :group_id, :modified_by)
+ params.require(:url).permit(:url, :keyword, :group_id, :modified_by, :note)
end
def set_group
diff --git a/app/datatables/url_datatable.rb b/app/datatables/url_datatable.rb
index 05a6270f..9da70c04 100644
--- a/app/datatables/url_datatable.rb
+++ b/app/datatables/url_datatable.rb
@@ -13,6 +13,7 @@ def view_columns
group_name: { source: 'Group.name' },
url: { source: 'Url.url' },
keyword: { source: 'Url.keyword' },
+ note: { source: 'Url.note' },
total_clicks: { source: 'Url.total_clicks', searchable: false },
created_at: { source: 'Url.created_at' },
# needed for actions like edit, delete, etc.
diff --git a/app/models/url.rb b/app/models/url.rb
index 3ca880d1..96a425ab 100644
--- a/app/models/url.rb
+++ b/app/models/url.rb
@@ -40,6 +40,7 @@ class Url < ApplicationRecord
multiline: true,
message: 'special characters are not permitted. Only letters, and numbers, dashes ("-") and underscores ("_")'
}
+ validates :note, length: { maximum: 1000 }, allow_blank: true
validate :check_for_valid_url
before_validation do
diff --git a/app/policies/url_policy.rb b/app/policies/url_policy.rb
index 24fd6ecc..d9c54d5f 100644
--- a/app/policies/url_policy.rb
+++ b/app/policies/url_policy.rb
@@ -23,6 +23,10 @@ def update?
user_has_access?
end
+ def update_note?
+ user_has_access?
+ end
+
def destroy?
user_has_access?
end
diff --git a/app/views/urls/_short_url_table_cell.html.erb b/app/views/urls/_short_url_table_cell.html.erb
index cb33f3f4..1f487ee8 100644
--- a/app/views/urls/_short_url_table_cell.html.erb
+++ b/app/views/urls/_short_url_table_cell.html.erb
@@ -1,4 +1,3 @@
-
<%= link_to(display_keyword_url(url.keyword), full_url(url), target: '_blank') %>
<%= f.hidden_field :group_id, value: @group.id if @group %>
-
+ <% if !is_edit %>
+
+ <% end %>
<% if is_edit %>
- <%= t('views.urls.sleek_form.edit.cancel')%>
+
+ <%= f.label :note, "Note", class: 'tw-text-xs tw-font-bold tw-absolute', style: "top: 0.5rem; left: 1rem;" %>
+ <%= f.text_area :note, class: "tw-w-full tw-border-none tw-text-sm tw-rounded-lg !tw-text-black", style: "padding-top: 1.5rem; padding-left: 1rem; border-radius: 0.25rem;", data: { cy: 'note-input' } %>
+
+
+
+
+
<% end %>
diff --git a/app/views/urls/show.html.erb b/app/views/urls/show.html.erb
index 72a9c5db..5ebd93df 100644
--- a/app/views/urls/show.html.erb
+++ b/app/views/urls/show.html.erb
@@ -86,6 +86,23 @@
+
+
+
+
Note
+
+ <%= form_with model: @url, url: note_url_path(@url.keyword), method: :patch, data: { cy: 'note-form' } do |form|%>
+ <%= form.label :note, "Note", class: 'sr-only' %>
+ <%= form.text_area :note, class: 'form-control', placeholder: 'Add a note', data: { cy: 'note-input' } %>
+
+ <%= button_tag 'Reset', type: 'reset', class: "tw-uppercase tw-text-xs tw-bg-neutral-100 tw-px-4 tw-py-2 tw-rounded hover:tw-bg-neutral-200 tw-transition" %>
+ <%= form.button "Save", class: "btn btn-primary" %>
+
+ <% end %>
+
+
+
+
diff --git a/config/routes.rb b/config/routes.rb
index b37ea179..d598c265 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -63,6 +63,10 @@
on: :collection,
to: "url_csvs#show_aggregated",
as: "csv"
+ member do
+ patch :note, to: 'urls#update_note'
+ end
+ patch :note, on: :member, to: 'urls#update_note'
end
# transfer_requests transfer_requests index get
@@ -130,9 +134,9 @@
# admin/urls/create admin::urls create put
resources :urls, only: %i[index edit show update destroy create] do
get "csv/:duration/:time_unit",
- on: :collection,
- to: "url_csvs#show",
- as: "csv"
+ on: :collection,
+ to: "url_csvs#show",
+ as: "csv"
end
# admin/audits/:search admin::urls index get
@@ -143,7 +147,7 @@
# admin/groups/:id
# admin/groups/:id/urls
# admin/groups/:id/members
- resources :groups, only: [:index, :show] do
+ resources :groups, only: %i[index show] do
resources :urls, only: [:index], controller: "group_urls"
resources :members, only: [:index], controller: "group_members"
end
diff --git a/cypress/e2e/urlNotes.cy.ts b/cypress/e2e/urlNotes.cy.ts
new file mode 100644
index 00000000..7ca63cee
--- /dev/null
+++ b/cypress/e2e/urlNotes.cy.ts
@@ -0,0 +1,64 @@
+describe("create zlink in collection", () => {
+ let url;
+ beforeEach(() => {
+ cy.app("clean");
+ cy.createUser("testuser").then((user) => {
+ url = cy.createUrl({
+ keyword: "cla",
+ url: "https://cla.umn.edu",
+ group_id: user.context_group_id,
+ });
+ });
+
+ cy.login("testuser");
+ });
+
+ context("url show page (aka stats page)", () => {
+ beforeEach(() => {
+ cy.visit("/shortener/urls/cla");
+ });
+
+ it("adds a note to a url", () => {
+ cy.get("[data-cy='note-form']").within(() => {
+ cy.get("[data-cy='note-input']").type("This is a note");
+ cy.contains("Save").click();
+ });
+
+ cy.contains("Note saved").should("be.visible");
+
+ // reload the page
+ cy.visit("/shortener/urls/cla");
+ // check that the note is there
+ cy.get("[data-cy='note-input']").should("contain", "This is a note");
+ });
+
+ it("limits the note contents to 1000 characters", () => {
+ cy.get("[data-cy='note-form']").within(() => {
+ cy.get("[data-cy='note-input']").type("a".repeat(1001), { delay: 0 });
+ cy.contains("Save").click();
+ });
+
+ cy.contains("Note is too long").should("be.visible");
+ });
+ });
+
+ context("url index page", () => {
+ beforeEach(() => {
+ cy.visit("/shortener/urls");
+ });
+
+ it("edits a note and shows it in the table", () => {
+ cy.get("#urls-table").contains("cla").closest("tr").as("claRow");
+ cy.get("@claRow").find(".dropdown").as("claDropdown");
+ cy.get("@claDropdown").find(".actions-dropdown-button").click();
+ cy.get("@claDropdown").contains("Edit").click();
+
+ cy.get("[data-cy='note-input']").type("edited note");
+
+ cy.contains("Submit").click();
+
+ // check that the note is in the table row
+ cy.get("@claRow").contains("edited note").should("be.visible");
+ });
+ });
+});
diff --git a/db/migrate/20240605133839_add_url_note.rb b/db/migrate/20240605133839_add_url_note.rb
new file mode 100644
index 00000000..c60a4905
--- /dev/null
+++ b/db/migrate/20240605133839_add_url_note.rb
@@ -0,0 +1,5 @@
+class AddUrlNote < ActiveRecord::Migration[7.1]
+ def change
+ add_column :urls, :note, :text, null: true, default: nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 771a648f..76c20f36 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.0].define(version: 2023_01_10_212461) do
+ActiveRecord::Schema[7.1].define(version: 2024_06_05_133839) do
create_table "clicks", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "country_code"
t.integer "url_id"
@@ -104,6 +104,7 @@
t.integer "modified_by"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
+ t.text "note"
t.index ["group_id"], name: "index_urls_on_group_id"
t.index ["keyword"], name: "index_urls_on_keyword", unique: true
end