Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release notes improvements #724

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions app/components/live_release/metadata_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@
<div class="flex flex-col gap-y-2 mb-6">
<div class="flex gap-x-4">
<%= render ButtonComponent.new(label: "Play Store metadata requirements",
scheme: :link,
type: :link_external,
options: "https://play.google.com/console/about/storelistings/#best-practices",
html_options: { class: "text-sm" },
authz: false,
size: :none,
arrow: :none) do |b|
scheme: :link,
type: :link_external,
options: "https://play.google.com/console/about/storelistings/#best-practices",
html_options: { class: "text-sm" },
authz: false,
size: :none,
arrow: :none) do |b|
b.with_icon("integrations/logo_google_play_store.png", size: :md, classes: "text-main")
end %>

<%= render ButtonComponent.new(label: "App Store Connect metadata requirements",
scheme: :link,
type: :link_external,
options: "https://help.apple.com/asc/appsspec/en.lproj/static.html",
html_options: { class: "text-sm" },
authz: false,
size: :none,
arrow: :none) do |b|
scheme: :link,
type: :link_external,
options: "https://help.apple.com/asc/appsspec/en.lproj/static.html",
html_options: { class: "text-sm" },
authz: false,
size: :none,
arrow: :none) do |b|
b.with_icon("integrations/logo_app_store.png", size: :md, classes: "text-main")
end %>
</div>
Expand Down Expand Up @@ -81,7 +81,11 @@
<% f.F.fields_for :android, android_metadata do |aF| %>
<div class="flex flex-col gap-y-4">
<%= aF.hidden_field :id %>
<div><%= aF.labeled_textarea :release_notes, "Release Notes" %></div>
<%= render partial: "shared/size_limited_textarea", locals: { form: aF,
obj_method: :release_notes,
label_text: "Release Notes",
max_length: android_max_length,
existing_value: android_metadata.release_notes } %>
</div>
<% end %>
<% elsif component.cross_platform? %>
Expand All @@ -94,8 +98,16 @@
<% f.F.fields_for :ios, ios_metadata do |iosF| %>
<div class="flex flex-col gap-y-4">
<%= iosF.hidden_field :id %>
<div><%= iosF.labeled_textarea :release_notes, "What's New?" %></div>
<div><%= iosF.labeled_textarea :promo_text, "Promo Text" %></div>
<%= render partial: "shared/size_limited_textarea", locals: { form: iosF,
obj_method: :release_notes,
label_text: "Release Notes",
max_length: ios_max_length,
existing_value: ios_metadata.release_notes } %>
<%= render partial: "shared/size_limited_textarea", locals: { form: iosF,
obj_method: :promo_text,
label_text: "Promo Text",
max_length: promo_text_max_length,
existing_value: ios_metadata.promo_text } %>
<div><%= iosF.labeled_textarea :keywords, "Keywords", readonly: true, disabled: true %></div>
<div><%= iosF.labeled_textarea :description, "Description", readonly: true, disabled: true %></div>
</div>
Expand Down
12 changes: 12 additions & 0 deletions app/components/live_release/metadata_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,16 @@ def no_locale_set
def editable?
@release.release_platform_runs.any?(&:metadata_editable?)
end

def android_max_length
ReleaseMetadata::ANDROID_NOTES_MAX_LENGTH
end

def ios_max_length
ReleaseMetadata::IOS_NOTES_MAX_LENGTH
end

def promo_text_max_length
ReleaseMetadata::PROMO_TEXT_MAX_LENGTH
end
end
26 changes: 26 additions & 0 deletions app/javascript/controllers/character_counter_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Controller } from "@hotwired/stimulus"

const ERROR_CLASS = "text-rose-700"

export default class extends Controller {
static targets = ["input", "counter"]
static values = {
maxLength: {type: Number, default: 500},
}

connect() {
this.update()
}

update() {
const value = this.inputTarget.value.length

if (value > this.maxLengthValue) {
this.counterTarget.classList.add(ERROR_CLASS)
} else {
this.counterTarget.classList.remove(ERROR_CLASS)
}

this.counterTarget.innerHTML = value
}
}
3 changes: 3 additions & 0 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ application.register("tabs", Tabs)

import { Popover } from "tailwindcss-stimulus-components"
application.register("popover", Popover)

import TextareaAutogrow from "stimulus-textarea-autogrow"
application.register("textarea-autogrow", TextareaAutogrow)
28 changes: 17 additions & 11 deletions app/models/release_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,29 @@ class ReleaseMetadata < ApplicationRecord

IOS_NOTES_MAX_LENGTH = 4000
ANDROID_NOTES_MAX_LENGTH = 500
PROMO_TEXT_MAX_LENGTH = 170
IOS_DENY_LIST = %w[<]
# NOTE: Refer to https://www.regular-expressions.info/unicode.html for supporting more unicode characters
PLAINTEXT_REGEX = /\A[~₹!@#$%^&*()_+\-=\[\]{};':"\\|`,.\/?\s\p{Alnum}\p{P}\p{Zs}\p{Emoji_Presentation}\p{M}\p{N}]+\z/
DEFAULT_LOCALES = ["en-US", "en-GB", "hi-IN", "en-IN", "id"]
DEFAULT_LOCALE = DEFAULT_LOCALES.first
IOS_PLAINTEXT_REGEX = /\A(?!.*#{Regexp.union(IOS_DENY_LIST)})[\p{L}\p{N}\p{P}\p{Sm}\p{Sc}\p{Zs}\p{M}\n]+\z/
ANDROID_PLAINTEXT_REGEX = /\A[\p{L}\p{N}\p{P}\p{Sm}\p{Sc}\p{Zs}\p{M}\p{Emoji_Presentation}\p{Extended_Pictographic}\n]+\z/
DEFAULT_LOCALE = "en-US"
DEFAULT_LANGUAGE = "English (United States)"
DEFAULT_RELEASE_NOTES = "The latest version contains bug fixes and performance improvements."

validates :release_notes,
format: {with: PLAINTEXT_REGEX, message: :no_special_characters, multiline: true}
format: {with: IOS_PLAINTEXT_REGEX, message: :no_special_characters_ios, denied_characters: IOS_DENY_LIST.join(", "), multiline: true},
if: :ios?
validates :release_notes,
format: {with: ANDROID_PLAINTEXT_REGEX, message: :no_special_characters_android, multiline: true},
if: :android?
validates :promo_text,
format: {with: PLAINTEXT_REGEX, message: :no_special_characters, allow_blank: true, multiline: true},
length: {maximum: 170}
format: {with: IOS_PLAINTEXT_REGEX, message: :no_special_characters, allow_blank: true, multiline: true},
length: {maximum: PROMO_TEXT_MAX_LENGTH}
validates :locale, uniqueness: {scope: :release_platform_run_id}
validate :notes_length

delegate :ios?, :android?, to: :release_platform_run

# NOTE: strip and normalize line endings across various OSes
normalizes :release_notes, with: ->(notes) { notes.strip.gsub("\r\n", "\n") }

Expand All @@ -52,10 +60,8 @@ def notes_length
end

def notes_max_length
case release_platform_run.platform
when "android" then ANDROID_NOTES_MAX_LENGTH
when "ios" then IOS_NOTES_MAX_LENGTH
else raise ArgumentError, "Invalid platform"
end
return ANDROID_NOTES_MAX_LENGTH if android?
return IOS_NOTES_MAX_LENGTH if ios?
raise ArgumentError, "Invalid platform"
end
end
10 changes: 10 additions & 0 deletions app/views/shared/_size_limited_textarea.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div data-controller="character-counter" data-character-counter-max-length-value="<%= max_length %>">
<%= form.labeled_textarea obj_method, label_text, data: { character_counter_target: "input",
action: "input->character-counter#update",
controller: "textarea-autogrow" } %>
<p class="text-xs text-secondary">
Characters –
<strong data-character-counter-target="counter"><%= existing_value&.length || 0 %></strong>
/ <%= max_length %>
</p>
</div>
1 change: 1 addition & 0 deletions config/importmap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@
pin "@sentry-internal/replay", to: "https://ga.jspm.io/npm:@sentry-internal/[email protected]/build/npm/esm/index.js"
pin "@sentry-internal/replay-canvas", to: "https://ga.jspm.io/npm:@sentry-internal/[email protected]/build/npm/esm/index.js"
pin "tom-select", to: "https://ga.jspm.io/npm:[email protected]/dist/js/tom-select.complete.js"
pin "stimulus-textarea-autogrow", to: "https://ga.jspm.io/npm:[email protected]/dist/stimulus-textarea-autogrow.mjs"
5 changes: 3 additions & 2 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,11 @@ en:
release_metadata:
attributes:
release_notes:
no_special_characters: "only allows letters, numbers, emojis, and some special characters"
no_special_characters_ios: "for iOS can only contain letters, numbers, punctuation, basic math symbols, currency symbols, and line breaks. The following characters are not allowed – %{denied_characters}"
no_special_characters_android: "for Android can only contain letters, numbers, punctuation, basic math symbols, currency symbols, emojis, and line breaks"
too_long: "is too long for %{platform} (maximum is %{max_length} characters)"
promo_text:
no_special_characters: "only allows letters, numbers, emojis, and some special characters"
no_special_characters: "can only contain letters, numbers, punctuation, basic math symbols, currency symbols, and line breaks. The following characters are not allowed – %{denied_characters}"
integration:
format: "%{message}"
attributes:
Expand Down
78 changes: 65 additions & 13 deletions spec/models/release_metadata_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,79 @@
require "rails_helper"

RSpec.describe ReleaseMetadata do
let(:locale) { "en-GB" }

it "has a valid factory" do
expect(build(:release_metadata)).to be_valid
end

it "allows emoji characters in notes" do
expect(build(:release_metadata, promo_text: "😀")).to be_valid
end
context "when iOS" do
let(:release_platform) { create(:release_platform, platform: :ios) }
let(:release_platform_run) { create(:release_platform_run, release_platform:) }

it "allows some special characters in notes" do
expect(build(:release_metadata, promo_text: "Money money money!! ₹100 off! $$ bills yo?! (#money)")).to be_valid
end
it "disallow emoji characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "➡️ something\n😀 💃🏽")).not_to be_valid
end

it "allows accented characters in notes" do
expect(build(:release_metadata, promo_text: "À la mode, les élèves sont bien à l'aise.")).to be_valid
end
it "allows currencies in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "Money money money!! ₹100 off! => $$ bills yo?! (#money)")).to be_valid
end

it "allows accented characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "À la mode, les élèves sont bien à l'aise.")).to be_valid
end

it "allows non-latin characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "दिल ढूँढता है फिर वही फ़ुरसत के रात दिन, बैठे रहे तसव्वुर-ए-जानाँ किये हुए।")).to be_valid
end

it "allows numbers in non-latin languages in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "१२३४५६७८९१०१११२१३, १३ करूँ गिन गिन के")).to be_valid
end

it "allows up to 4000 characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "a" * 4000)).to be_valid
end

it "allows non-latin characters in notes" do
expect(build(:release_metadata, promo_text: "दिल ढूँढता है फिर वही फ़ुरसत के रात दिन, बैठे रहे तसव्वुर-ए-जानाँ किये हुए।")).to be_valid
it "disallows more than 4000 characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "a" * 4001)).not_to be_valid
end

it "disallows '<' in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "<a>")).not_to be_valid
end
end

it "allows numbers in non-latin languages in notes" do
expect(build(:release_metadata, promo_text: "१२३४५६७८९१०१११२१३, १३ करूँ गिन गिन के")).to be_valid
context "when android" do
let(:release_platform) { create(:release_platform, platform: :android) }
let(:release_platform_run) { create(:release_platform_run, release_platform:) }

it "allows emoji characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "➡️ something\n😀 💃🏽")).to be_valid
end

it "allows currencies in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "Money money money!! ₹100 off! => $$ bills yo?! (#money)")).to be_valid
end

it "allows accented characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "À la mode, les élèves sont bien à l'aise.")).to be_valid
end

it "allows non-latin characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "दिल ढूँढता है फिर वही फ़ुरसत के रात दिन, बैठे रहे तसव्वुर-ए-जानाँ किये हुए।")).to be_valid
end

it "allows numbers in non-latin languages in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "१२३४५६७८९१०१११२१३, १३ करूँ गिन गिन के")).to be_valid
end

it "allows up to 500 characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "a" * 500)).to be_valid
end

it "disallows more than 500 characters in notes" do
expect(build(:release_metadata, locale:, release_platform_run:, release_notes: "a" * 501)).not_to be_valid
end
end
end
Loading