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

[wip] Remove Project.LiveStream #63

Merged
merged 39 commits into from
Feb 1, 2017
Merged

Conversation

justinswart
Copy link
Contributor

When we initially began integrating the LiveStream framework we were receiving some information about live streams along with the Project fetch JSON payload. All that this contained was the live stream eventId, a title, startDate, etc. We then, upon launching the LiveStreamContainerViewController or LiveStreamCountdownViewController, would perform an additional fetch for the LiveStreamEvent which would tell us everything else about the event.

This quickly turned out to be a bad idea and there was just too much information being duplicated between Project.LiveStream and LiveStreamEvent so the decision was taken to remove Project.LiveStream in its entirety and to fetch a list of LiveStreamEvents from a new endpoint based on the Project's ID. We perform this fetch when we land on a project page and once the list is returned we reload the project's subpage cells with any live streams that may have been returned.

The amount of files that this touched is a lot more than I'd anticipated! But it's much simpler this was and it should also prepare us for a lot the work that we would have needed to do for discovery.

Things I've done here:

  • Removed Project.LiveStream from models and tests that referenced it.
  • Removed fetching of LiveStreamEvent from LiveStreamEventDetailsViewModel.
  • Changed all initialisers that previously allowed an optional LiveStreamEvent to now expect this as non-optional.
  • Updated Koala tracking tests. @mbrandonw you'll want to confirm that I correctly understood how your tests were working around the timeout of fetching live streams and tracking that, etc. I believe it's all still working correctly.
  • Updated all view model tests that had been referencing Project.LiveStream.
  • Copied across the changes @mbrandonw had made to LiveStreamEvent for the live stream discovery work. This was necessary to parse the new endpoint @Jwomers had set up for us.

Things to review:

  • Are the tests still sound?
  • Should I add some more tests?
  • Is there a clever way to reduce the amount of emissions when the project page loads now that it's a combineLatest of project, freshProject and liveStreams?

Things still needed:

  • In testing I wasn't seeing any user subscription info, Firebase paths, OpenTok info, replay URLs, etc. from this endpoint. Will you still be adding those @Jwomers?
  • May still want to write more tests for ProjectPamphletViewModelTests and confirm that we're still satisfied withProjectPamphletContentDataSourceTests (these should be fine).
  • Kind've regret not changing LiveStreamEvent.startDate to a TimeInterval as I went through this 😐

)

self.configureChildViewControllersWithProjectAndLiveStreams =
Signal.combineLatest(project, liveStreamEvents, refTag)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the combineLatest that I mentioned in the PR. This emits quite a few times and each will cause a reload of the datasource and table view, perhaps we can buffer/throttle it in some way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think in this case we actually want all of those emissions! for once!

basically we want the most recent values of project and live streams for any emission and update the data source accordingly.

or, we could model it to emit only twice, once for the initial data and once we have both the newest project and newest live streams. however, for that we'll need to add a timeout to the live streams so that it doesnt prevent the project from showing if it fails. wanna discuss over slack how to do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@mbrandonw mbrandonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a first pass review of this. most of it looks great and is so much simpler. let's attack my comments and then i'll do one more pass and we'll get it merged!

@@ -126,7 +124,7 @@ public final class LiveStreamContainerViewController: UIViewController {

_ = self.loaderActivityIndicatorView
|> UIActivityIndicatorView.lens.activityIndicatorViewStyle .~ .white
|> UIActivityIndicatorView.lens.animating .~ true
|> UIActivityIndicatorView.lens.hidesWhenStopped .~ true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 i remember us talking about this. important to do only stylistic stuff in bindStyles since it can be called multiple times (like on rotation)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

return .replay
}

return .countdown
}

private func stateContext(forLiveStream liveStream: Project.LiveStream) -> LiveStreamStateContext {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boy does it warm my heart to see this code go away!

@@ -105,16 +114,17 @@ final class ProjectPamphletViewModelTests: TestCase {
self.setNavigationBarAnimated.assertValues([false, true, false])
}

// Tests that ref tags and referral credit cookies are tracked in koala and saved like we expect.
//Tests that ref tags and referral credit cookies are tracked in koala and saved like we expect.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 whatchu doin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woops!

@@ -213,22 +203,8 @@ LiveStreamCountdownViewModelInputs, LiveStreamCountdownViewModelOutputs {
public var outputs: LiveStreamCountdownViewModelOutputs { return self }
}

private func flipProjectLiveStreamToLive(project: Project, currentLiveStream: Project.LiveStream) ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

@@ -285,16 +238,3 @@ public final class LiveStreamEventDetailsViewModel: LiveStreamEventDetailsViewMo
public var inputs: LiveStreamEventDetailsViewModelInputs { return self }
public var outputs: LiveStreamEventDetailsViewModelOutputs { return self }
}

private func fetchEvent(forProject project: Project, liveStream: Project.LiveStream, event: LiveStreamEvent?)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

.map { $0.liveStreams }
.skipNil()
let trackLiveStreamEvents = liveStreamEvents
.skip(first: 1) // Skip first as we are prefixing with an empty array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice nice, im guess a test caught this right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yip!

}
}

public func == (lhs: LiveStreamEventsEnvelope, rhs: LiveStreamEventsEnvelope) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious if where this is needed...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah probably not necessary, remove it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah if it's not used, prob for the best


let urlString = "\(Secrets.LiveStreams.Api.base)/projects/\(projectId)\(uidString)"
guard let url = URL(string: urlString) else {
observer.send(error: .invalidEventId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof, so "invalidEventId" isn't quite the right error here

.flatMap { "?uid=\($0)" }
.coalesceWith("")

let urlString = "\(Secrets.LiveStreams.Api.base)/projects/\(projectId)\(uidString)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this we cant really test this function i'd like to do something a little safer around url construction. we can use URLComponents for the query params

let url = URL(string: Secrets.LiveStreams.Api.base)?.appendingPathComponent("projects/\(projectId)")
var components = url.flatMap { URLComponents(url: $0, resolvingAgainstBaseURL: false) }
components?.queryItems = uid.map { [URLQueryItem(name: "uid", value: "\($0)")] }

guard let url = components?.url ...

did that without the type checker, but something along those lines...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok cool, looks good!

@@ -132,7 +132,7 @@ internal final class LiveStreamViewModel: LiveStreamViewModelType, LiveStreamVie
isMaxOpenTokViewersReached,

liveStreamEvent
.map { event in event.stream.isRtmp || didEndNormally(event: event) }
.map { event in event.isRtmp ?? false || didEndNormally(event: event) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm a big fan of doing explicit event.isRtmp == .some(true) over coalescing for booleans

# Conflicts:
#	Screenshots/_64/Kickstarter_Framework_iOSTests.ProjectPamphletContentViewControllerTests/testNonBacker_LiveProject_WithLiveStreams_lang_es_device_pad@2x.png
#	Screenshots/_64/Kickstarter_Framework_iOSTests.ProjectPamphletContentViewControllerTests/testNonBacker_LiveProject_WithLiveStreams_lang_es_device_phone4_7inch@2x.png
@@ -214,14 +214,12 @@ LiveStreamContainerViewModelInputs, LiveStreamContainerViewModelOutputs {

let hideWhenReplay = Signal.merge(
project.mapConst(true),
event.map { !$0.liveNow },
self.showErrorAlert.mapConst(true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screenshot tests that were failing were because of this line - previously we would fetch the LiveStreamEvent asynchronously and if it failed we would keep some views hidden. Because this is now available immediately at initialisation this sort of thing is no longer necessary as errors are now only shown for live stream playback issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest we probably don't even need the project.mapConst(true) anymore either as we know immediately if it's live or a replay. I'll look at maybe removing that for simplification.

@justinswart justinswart changed the title Remove Project.LiveStream [wip ]Remove Project.LiveStream Jan 29, 2017
@justinswart justinswart changed the title [wip ]Remove Project.LiveStream [wip] Remove Project.LiveStream Jan 29, 2017
)

self.createAndConfigureLiveStreamViewController = Signal.combineLatest(project, event)
self.configureLiveStreamViewController = Signal.combineLatest(project, event.skip(first: 1))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the feeling on using .skip(first: 1) here? We know that we only want to configure the live stream view controller once the fresh event has been fetched and our tests confirm this so should be cool?

# Conflicts:
#	Library/ViewModels/ProjectPamphletSubpageCellViewModel.swift
#	Screenshots/_64/Kickstarter_Framework_iOSTests.ProjectPamphletContentViewControllerTests/testNonBacker_LiveProject_WithLiveStreams_lang_de_device_pad@2x.png
#	Screenshots/_64/Kickstarter_Framework_iOSTests.ProjectPamphletContentViewControllerTests/testNonBacker_LiveProject_WithLiveStreams_lang_de_device_phone4_7inch@2x.png
#	Screenshots/_64/Kickstarter_Framework_iOSTests.ProjectPamphletContentViewControllerTests/testNonBacker_LiveProject_WithLiveStreams_lang_en_device_pad@2x.png
#	Screenshots/_64/Kickstarter_Framework_iOSTests.ProjectPamphletContentViewControllerTests/testNonBacker_LiveProject_WithLiveStreams_lang_en_device_phone4_7inch@2x.png
@justinswart
Copy link
Contributor Author

These tests will pass once #67 is merged.

.map { URL(string: $0.photo.full) }
self.projectImageUrl = Signal.merge(
configData.mapConst(nil),
Signal.combineLatest(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbrandonw this was a weird one, even though configData is definitely emitting before project (because project is configData.map(first), I couldn't seem to get nil to emit first without merging in that combineLatest?

project.map { URL(string: $0.photo.full) },
configData.ignoreValues()
).map(first)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we can just re-express as:

self.projectImageUrl = Signal.merge(
  self.viewDidLoadProperty.signal.mapConst(nil),
  project.map { URL(string: $0.photo.full) }
)

?

also down to just remove the placeholder image in the storyboard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I initially had it that way (with viewDidLoad), and just tested it again, bizarrely:

screen shot 2017-01-31 at 9 26 00 pm

    self.projectImageUrl = Signal.merge(
      self.viewDidLoadProperty.signal.mapConst(nil),
      project.map { URL(string: $0.photo.full) }
    )

I'll debug a little more but will probably just remove it from the story board 😄

self.projectImageUrl = project
.map { URL(string: $0.photo.full) }
self.projectImageUrl = project.flatMap { project in
SignalProducer(value: URL(string: project.photo.full))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbrandonw ok so this fixes it and the tests pass. I know using a SignalProducer might not be the desired approach but it's handy to be able to prefix it with nil and makes what we're wanting to do pretty clear?

I could not get nil to emit first using viewDidLoad without a nested combineLatest which I also didn't like.

And I think having this testable is safer than only removing the image from the story board in case it was ever added back?

@justinswart justinswart merged commit eb74738 into master Feb 1, 2017
@justinswart justinswart deleted the remove_project_livestream branch February 1, 2017 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants