diff options
author | Jason Goodman <jgoodman@gitlab.com> | 2019-07-03 09:12:15 +0000 |
---|---|---|
committer | Nick Thomas <nick@gitlab.com> | 2019-07-03 09:12:15 +0000 |
commit | 7ecffe2987e0f3953489759d080fc263c5cb95c5 (patch) | |
tree | e9a01188b0b731b997a14f795dc44f22a0c7f708 | |
parent | d6391c650344af7018de8a3dc6a756b2db9e4f6e (diff) | |
download | gitlab-ce-7ecffe2987e0f3953489759d080fc263c5cb95c5.tar.gz |
Show upcoming status for releases
Add released_at field to releases API
Add released_at column to releases table
Return releases to the API sorted by released_at
24 files changed, 212 insertions, 24 deletions
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue index c82b65cd97b..0031ba04d78 100644 --- a/app/assets/javascripts/releases/components/release_block.vue +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -28,7 +28,7 @@ export default { computed: { releasedTimeAgo() { return sprintf(__('released %{time}'), { - time: this.timeFormated(this.release.created_at), + time: this.timeFormated(this.release.released_at), }); }, userImageAltDescription() { @@ -56,8 +56,8 @@ export default { <div class="card-body"> <h2 class="card-title mt-0"> {{ release.name }} - <gl-badge v-if="release.pre_release" variant="warning" class="align-middle">{{ - __('Pre-release') + <gl-badge v-if="release.upcoming_release" variant="warning" class="align-middle">{{ + __('Upcoming Release') }}</gl-badge> </h2> @@ -74,7 +74,7 @@ export default { <div class="append-right-4"> • - <span v-gl-tooltip.bottom :title="tooltipTitle(release.created_at)"> + <span v-gl-tooltip.bottom :title="tooltipTitle(release.released_at)"> {{ releasedTimeAgo }} </span> </div> diff --git a/app/models/release.rb b/app/models/release.rb index 7bbeb3c9976..459a7c29ad0 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -12,12 +12,16 @@ class Release < ApplicationRecord has_many :links, class_name: 'Releases::Link' + default_value_for :released_at, allows_nil: false do + Time.zone.now + end + accepts_nested_attributes_for :links, allow_destroy: true validates :description, :project, :tag, presence: true validates :name, presence: true, on: :create - scope :sorted, -> { order(created_at: :desc) } + scope :sorted, -> { order(released_at: :desc) } delegate :repository, to: :project @@ -44,6 +48,10 @@ class Release < ApplicationRecord end end + def upcoming_release? + released_at.present? && released_at > Time.zone.now + end + private def actual_sha diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb index ff6b696ca96..618d96717b8 100644 --- a/app/services/releases/concerns.rb +++ b/app/services/releases/concerns.rb @@ -22,6 +22,10 @@ module Releases params[:description] end + def released_at + params[:released_at] + end + def release strong_memoize(:release) do project.releases.find_by_tag(tag_name) diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index a271a7e5e49..5b13ac631ba 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -58,6 +58,7 @@ module Releases author: current_user, tag: tag.name, sha: tag.dereferenced_target.sha, + released_at: released_at, links_attributes: params.dig(:assets, 'links') || [] ) end diff --git a/changelogs/unreleased/pre-releases-38105a.yml b/changelogs/unreleased/pre-releases-38105a.yml new file mode 100644 index 00000000000..8b7cf6065d4 --- /dev/null +++ b/changelogs/unreleased/pre-releases-38105a.yml @@ -0,0 +1,5 @@ +--- +title: Show an Upcoming Status for Releases +merge_request: 29577 +author: +type: added diff --git a/db/migrate/20190628185000_add_released_at_to_releases_table.rb b/db/migrate/20190628185000_add_released_at_to_releases_table.rb new file mode 100644 index 00000000000..ae55aa434d9 --- /dev/null +++ b/db/migrate/20190628185000_add_released_at_to_releases_table.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddReleasedAtToReleasesTable < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column(:releases, :released_at, :datetime_with_timezone) + end +end diff --git a/db/migrate/20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb b/db/migrate/20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb new file mode 100644 index 00000000000..1e5d3e8f235 --- /dev/null +++ b/db/migrate/20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + update_column_in_batches(:releases, :released_at, Arel.sql('created_at')) + change_column_null(:releases, :released_at, false) + end + + def down + change_column_null(:releases, :released_at, true) + end +end diff --git a/db/schema.rb b/db/schema.rb index 143f6c7127e..32a25f643ce 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.define(version: 20190628145246) do +ActiveRecord::Schema.define(version: 20190628185004) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -2902,6 +2902,7 @@ ActiveRecord::Schema.define(version: 20190628145246) do t.integer "author_id" t.string "name" t.string "sha" + t.datetime_with_timezone "released_at", null: false t.index ["author_id"], name: "index_releases_on_author_id", using: :btree t.index ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree t.index ["project_id"], name: "index_releases_on_project_id", using: :btree diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d783591c238..b96903d4a8d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1186,8 +1186,10 @@ module API MarkupHelper.markdown_field(entity, :description) end expose :created_at + expose :released_at expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? } + expose :upcoming_release?, as: :upcoming_release expose :assets do expose :assets_count, as: :count do |release, _| diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 6b17f4317db..fdd8406388e 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -54,6 +54,7 @@ module API requires :url, type: String end end + optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.' end post ':id/releases' do authorize_create_release! @@ -77,6 +78,7 @@ module API requires :tag_name, type: String, desc: 'The name of the tag', as: :tag optional :name, type: String, desc: 'The name of the release' optional :description, type: String, desc: 'Release notes with markdown support' + optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.' end put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do authorize_update_release! diff --git a/lib/gitlab/legacy_github_import/release_formatter.rb b/lib/gitlab/legacy_github_import/release_formatter.rb index 746786b5a66..fdab6b512ea 100644 --- a/lib/gitlab/legacy_github_import/release_formatter.rb +++ b/lib/gitlab/legacy_github_import/release_formatter.rb @@ -10,6 +10,7 @@ module Gitlab name: raw_data.name, description: raw_data.body, created_at: raw_data.created_at, + released_at: raw_data.published_at, updated_at: raw_data.created_at } end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1a1fda4adef..def2d6c43d1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7574,9 +7574,6 @@ msgstr "" msgid "Please wait while we import the repository for you. Refresh at will." msgstr "" -msgid "Pre-release" -msgstr "" - msgid "Preferences" msgstr "" @@ -11378,6 +11375,9 @@ msgstr "" msgid "Upcoming" msgstr "" +msgid "Upcoming Release" +msgstr "" + msgid "Update" msgstr "" diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index cab6b4a811f..4cacc77c182 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -6,6 +6,7 @@ FactoryBot.define do description "Awesome release" project author + released_at { Time.zone.parse('2018-10-20T18:00:00Z') } trait :legacy do sha nil diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb index 317ffb6a2ff..725d7173bce 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -16,6 +16,7 @@ describe 'User views releases', :js do expect(page).to have_content(release.name) expect(page).to have_content(release.tag) + expect(page).not_to have_content('Upcoming Release') end context 'when there is a link as an asset' do @@ -43,4 +44,15 @@ describe 'User views releases', :js do end end end + + context 'with an upcoming release' do + let(:tomorrow) { Time.zone.now + 1.day } + let!(:release) { create(:release, project: project, released_at: tomorrow ) } + + it 'sees the upcoming tag' do + visit project_releases_path(project) + + expect(page).to have_content('Upcoming Release') + end + end end diff --git a/spec/finders/releases_finder_spec.rb b/spec/finders/releases_finder_spec.rb index 32ee15134a2..5ffb8c74bf5 100644 --- a/spec/finders/releases_finder_spec.rb +++ b/spec/finders/releases_finder_spec.rb @@ -12,8 +12,8 @@ describe ReleasesFinder do subject { described_class.new(project, user)} before do - v1_0_0.update_attribute(:created_at, 2.days.ago) - v1_1_0.update_attribute(:created_at, 1.day.ago) + v1_0_0.update_attribute(:released_at, 2.days.ago) + v1_1_0.update_attribute(:released_at, 1.day.ago) end describe '#execute' do @@ -30,7 +30,7 @@ describe ReleasesFinder do project.add_developer(user) end - it 'sorts by creation date' do + it 'sorts by release date' do releases = subject.execute expect(releases).to be_present diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json index 6ea0781c1ed..ec3fa59cdb1 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release.json +++ b/spec/fixtures/api/schemas/public_api/v4/release.json @@ -1,12 +1,14 @@ { "type": "object", - "required": ["name", "tag_name", "commit"], + "required": ["name", "tag_name", "commit", "released_at"], "properties": { "name": { "type": "string" }, "tag_name": { "type": "string" }, "description": { "type": "string" }, "description_html": { "type": "string" }, "created_at": { "type": "date" }, + "released_at": { "type": "date" }, + "upcoming_release": { "type": "boolean" }, "commit": { "oneOf": [{ "type": "null" }, { "$ref": "commit/basic.json" }] }, diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json index e78398ad1d5..0c1e8fd5fb3 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json +++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json @@ -1,11 +1,13 @@ { "type": "object", - "required": ["name"], + "required": ["name", "released_at"], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "description_html": { "type": "string" }, "created_at": { "type": "date" }, + "released_at": { "type": "date" }, + "upcoming_release": { "type": "boolean" }, "author": { "oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }] }, diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js index e98c665f99d..f761a18e326 100644 --- a/spec/javascripts/releases/components/release_block_spec.js +++ b/spec/javascripts/releases/components/release_block_spec.js @@ -14,7 +14,7 @@ describe('Release block', () => { description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', author_name: 'Release bot', author_email: 'release-bot@example.com', - created_at: '2012-05-28T05:00:00-07:00', + released_at: '2012-05-28T05:00:00-07:00', author: { avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', id: 482476, @@ -101,7 +101,7 @@ describe('Release block', () => { }); it('renders release date', () => { - expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at)); + expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at)); }); it('renders number of assets provided', () => { @@ -152,13 +152,13 @@ describe('Release block', () => { }); }); - describe('with pre_release flag', () => { + describe('with upcoming_release flag', () => { beforeEach(() => { - vm = factory(Object.assign({}, release, { pre_release: true })); + vm = factory(Object.assign({}, release, { upcoming_release: true })); }); - it('renders pre-release badge', () => { - expect(vm.$el.textContent).toContain('Pre-release'); + it('renders upcoming release badge', () => { + expect(vm.$el.textContent).toContain('Upcoming Release'); }); }); }); diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 2758023fb17..28b187c3676 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -123,6 +123,7 @@ Release: - project_id - created_at - updated_at +- released_at Releases::Link: - id - release_id diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index a0c664da185..9163019514b 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -132,6 +132,7 @@ describe Gitlab::LegacyGithubImport::Importer do body: 'Release v1.0.0', draft: false, created_at: created_at, + published_at: created_at, updated_at: updated_at, url: "#{api_root}/repos/octocat/Hello-World/releases/1" ) @@ -144,6 +145,7 @@ describe Gitlab::LegacyGithubImport::Importer do body: nil, draft: false, created_at: created_at, + published_at: created_at, updated_at: updated_at, url: "#{api_root}/repos/octocat/Hello-World/releases/2" ) diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb index c57b96fb00d..534cf219520 100644 --- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } + let(:published_at) { DateTime.strptime('2011-01-26T20:00:00Z') } let(:base_data) do { @@ -11,7 +12,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do name: 'First release', draft: false, created_at: created_at, - published_at: created_at, + published_at: published_at, body: 'Release v1.0.0' } end @@ -28,6 +29,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do name: 'First release', description: 'Release v1.0.0', created_at: created_at, + released_at: published_at, updated_at: created_at } diff --git a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb new file mode 100644 index 00000000000..9cae1daacea --- /dev/null +++ b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb') + +describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable, :migration do + let(:releases) { table(:releases) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + subject(:migration) { described_class.new } + + it 'fills released_at with the value of created_at' do + created_at_a = Time.zone.parse('2019-02-10T08:00:00Z') + created_at_b = Time.zone.parse('2019-03-10T18:00:00Z') + namespace = namespaces.create(name: 'foo', path: 'foo') + project = projects.create!(namespace_id: namespace.id) + release_a = releases.create!(project_id: project.id, created_at: created_at_a) + release_b = releases.create!(project_id: project.id, created_at: created_at_b) + + disable_migrations_output { migration.up } + + release_a.reload + release_b.reload + expect(release_a.released_at).to eq(created_at_a) + expect(release_b.released_at).to eq(created_at_b) + end +end diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 7c106ce6b85..e9d846e7291 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -64,4 +64,14 @@ RSpec.describe Release do is_expected.to all(be_a(Releases::Source)) end end + + describe '#upcoming_release?' do + context 'during the backfill migration when released_at could be nil' do + it 'handles a nil released_at value and returns false' do + allow(release).to receive(:released_at).and_return nil + + expect(release.upcoming_release?).to eq(false) + end + end + end end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 8603fa2a73d..206e898381d 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -24,7 +24,7 @@ describe API::Releases do project: project, tag: 'v0.1', author: maintainer, - created_at: 2.days.ago) + released_at: 2.days.ago) end let!(:release_2) do @@ -32,7 +32,7 @@ describe API::Releases do project: project, tag: 'v0.2', author: maintainer, - created_at: 1.day.ago) + released_at: 1.day.ago) end it 'returns 200 HTTP status' do @@ -41,7 +41,7 @@ describe API::Releases do expect(response).to have_gitlab_http_status(:ok) end - it 'returns releases ordered by created_at' do + it 'returns releases ordered by released_at' do get api("/projects/#{project.id}/releases", maintainer) expect(json_response.count).to eq(2) @@ -56,6 +56,26 @@ describe API::Releases do end end + it 'returns an upcoming_release status for a future release' do + tomorrow = Time.now.utc + 1.day + create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: tomorrow) + + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.first['upcoming_release']).to eq(true) + end + + it 'returns an upcoming_release status for a past release' do + yesterday = Time.now.utc - 1.day + create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: yesterday) + + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.first['upcoming_release']).to eq(false) + end + context 'when tag does not exist in git repository' do let!(:release) { create(:release, project: project, tag: 'v1.1.5') } @@ -316,6 +336,51 @@ describe API::Releases do expect(project.releases.last.description).to eq('Super nice release') end + it 'sets the released_at to the current time if the released_at parameter is not provided' do + now = Time.zone.parse('2015-08-25 06:00:00Z') + Timecop.freeze(now) do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq(now) + end + end + + it 'sets the released_at to the value in the parameters if specified' do + params = { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release', + released_at: '2019-03-20T10:00:00Z' + } + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2019-03-20T10:00:00Z') + end + + it 'assumes the utc timezone for released_at if the timezone is not provided' do + params = { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release', + released_at: '2019-03-25 10:00:00' + } + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2019-03-25T10:00:00Z') + end + + it 'allows specifying a released_at with a local time zone' do + params = { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release', + released_at: '2019-03-25T10:00:00+09:00' + } + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2019-03-25T01:00:00Z') + end + context 'when description is empty' do let(:params) do { @@ -540,6 +605,7 @@ describe API::Releases do project: project, tag: 'v0.1', name: 'New release', + released_at: '2018-03-01T22:00:00Z', description: 'Super nice release') end @@ -560,6 +626,7 @@ describe API::Releases do expect(project.releases.last.tag).to eq('v0.1') expect(project.releases.last.name).to eq('New release') + expect(project.releases.last.released_at).to eq('2018-03-01T22:00:00Z') end it 'matches response schema' do @@ -568,6 +635,14 @@ describe API::Releases do expect(response).to match_response_schema('public_api/v4/release') end + it 'updates released_at' do + params = { released_at: '2015-10-10T05:00:00Z' } + + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2015-10-10T05:00:00Z') + end + context 'when user tries to update sha' do let(:params) { { sha: 'xxx' } } |