diff options
author | Jason Goodman <jgoodman@gitlab.com> | 2019-06-12 18:33:44 -0400 |
---|---|---|
committer | Jason Goodman <jgoodman@gitlab.com> | 2019-07-01 15:26:15 -0400 |
commit | d58ff74d8afe851dcec718cf61a6da18025a274a (patch) | |
tree | c2daa782b77c1642e42756c80342f991c4c8cea4 | |
parent | 7177f59fe67ba00bb137fbf3d6f8636232640344 (diff) | |
download | gitlab-ce-pre-releases-38105b.tar.gz |
Show upcoming status for releasespre-releases-38105b
Add released_at field to releases API
Add released_at column to releases table
Return releases to the API sorted by released_at
22 files changed, 190 insertions, 26 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..929dd6c9cde 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -17,7 +17,7 @@ class Release < ApplicationRecord 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 +44,10 @@ class Release < ApplicationRecord end end + def upcoming_release? + 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..7d3d1febde0 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 || Time.zone.now, 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 8876be1cb34..bbddf05c70b 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" @@ -2901,6 +2901,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..d20876777f7 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 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 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 556684679c4..0fe30aee21c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7544,9 +7544,6 @@ msgstr "" msgid "Please wait while we import the repository for you. Refresh at will." msgstr "" -msgid "Pre-release" -msgstr "" - msgid "Preferences" msgstr "" @@ -11333,6 +11330,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/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 a406c25b1d8..87922a87d34 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/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 8603fa2a73d..a3638015e01 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -24,7 +24,8 @@ describe API::Releases do project: project, tag: 'v0.1', author: maintainer, - created_at: 2.days.ago) + created_at: 2.days.ago, + released_at: 1.day.ago) end let!(:release_2) do @@ -32,7 +33,8 @@ describe API::Releases do project: project, tag: 'v0.2', author: maintainer, - created_at: 1.day.ago) + created_at: 1.day.ago, + released_at: 2.days.ago) end it 'returns 200 HTTP status' do @@ -41,12 +43,12 @@ 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) - expect(json_response.first['tag_name']).to eq(release_2.tag) - expect(json_response.second['tag_name']).to eq(release_1.tag) + expect(json_response.first['tag_name']).to eq(release_1.tag) + expect(json_response.second['tag_name']).to eq(release_2.tag) end it 'matches response schema' do @@ -56,6 +58,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 +338,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 +607,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 +628,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 +637,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' } } |