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

Media: Clear icon site value and setting upon deleting associated media #10016

Merged
merged 4 commits into from
Jan 2, 2017

Conversation

aduth
Copy link
Contributor

@aduth aduth commented Dec 13, 2016

Closes #9939

This pull request seeks to clear site icon values (both on the site object itself and the site setting) when deleting a media item which is currently assigned as the site icon. Server-side, the option is automatically cleared when the media is deleted, so we only need to recreate this behavior in the client (related source).

Testing Instructions:

Testing will be made easier by use of Redux DevTools Chrome extension.

  1. Navigate to Site Settings
  2. Select a site
  3. Assign a site icon by clicking "Change Site Icon" (select image, "Continue", crop, "Done")
  • This will set the site setting value, but does not yet change front-end icon shown
  1. Click "Change Site Icon" again
  2. Choose the newly created cropped image
  3. Click Delete
  4. In developer tools Redux tab, note that...
  • Upon the MEDIA_DELETE dispatched action, the diff shows that the state.siteSettings.items[ siteId ].site_icon was set to null
  • A RESET_SITE_ICON action is also dispatched, and the diff shows that the state.sites.items[ siteId ].icon was removed (but only if you have a site icon already assigned via non-Calypso flow)

You can also verify that deleting any other media will dispatch MEDIA_DELETE with no effect, and will not dispatch RESET_SITE_ICON.

@aduth aduth added [Feature] Media The media screen in Calypso, general media management, or integration with third party media. Sites [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Dec 13, 2016
@aduth aduth added this to the Site Icon milestone Dec 13, 2016
@matticbot
Copy link
Contributor

@aduth aduth requested a review from youknowriad December 13, 2016 21:15
this.props.deleteMedia( site.ID, mediaIds );
if ( includes( mediaIds, siteIconId ) ) {
this.props.resetSiteIcon( site.ID );
}
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 bit concerned about this code, what if we allow media deletion from elsewhere, we'll face the exact same problem. Do we have the media URLs available here, that way we could use them to compare against the siteIcon attribute in the site reducer?

Or we could solve this differently, move this "extra" behavior to the deleteMedia action creator. You could call to requestSiteSettings action creator from there. I know it's not perfect, but maybe this could allow us to "centralize" this behavior, which seem not directly related to this component? Thoughts? I may be missing something tho :)

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, I had a hard time coming to a solution here. It's complicated by the fact that the data exists in many places (media objects themselves, a media ID as site setting, and a media's URL as the site icon property). But you're right that any part of the app which deletes a media would then need to be concerned with resetting the site icon, which is not great.

Similar to what I suggested at #10032 (comment), perhaps a middleware (the API middleware?) is the right place for these kinds of side effects. While we're not quite at the point where we want to substitute the API call for deleting media, from there we could at least check whether the media to be deleted matches the current site option.

Or, maybe since it's not related to network calls on its own, it could exist as a standalone middleware, or combined with other media middleware suggested in #9706.

cc @dmsnell in case you have thoughts on the role of middlewares / API middleware

Copy link
Member

Choose a reason for hiding this comment

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

@aduth middleware is probably a reasonable solution for this, but it would seem a bit odd with the deleteMedia action doing the deletion and leaving the site icon in middleware. maybe not. maybe it would actually be a quick and easy function to add to the wpcom data layer.

in essence, we should ask if deleting the media item currently set as the site icon always means that the icon should be reset. if this is the case, I would expect to have some siteIcon reducer somewhere in the state tree listening for mediaDelete actions and updating in Calypso-first by resetting it. in other words, deleteMedia would not be the genesis for resetting the site icon. this is by design in Redux. the siteIcon state/reducer is responsible for its own behavior and if the proposition holds, it should be listening for all relevant actions which affect it.

the middleware comes in as the data layer. we act as if Calypso is the raw source of truth. the site icon reducer resets its value when it see MEDIA_DELETE with its own media id. the middleware layer for the site icon (or site setting) endpoint also listens for this same action and will trigger an API call whenever one flies by. if the API call fails, it may very well reset the site icon to its previous id, or in this case, if the media is gone, the failure mode will do something else - set some default for example.

does this help?

export function deleteMedia( siteId, mediaIds ) {
return {
type: MEDIA_DELETE,
mediaIds: castArray( mediaIds ),
Copy link
Contributor

Choose a reason for hiding this comment

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

a new Lodash tip learned for me :)

@aduth
Copy link
Contributor Author

aduth commented Dec 15, 2016

A thought which might help with reconciling the icon formats is including the ID of the media item associated with the site icon on the site response, perhaps as site.icon.media_id. This seems like a reasonable addition anyways, as it would be useful as a pointer in case the client wants more information about the particular media item used as site icon (could include said pointer request in site.meta.links).

Then, as much as possible, we use the site icon ID as the source of truth, with a lookup of the associated media item if available in the current state, falling back to the site object's icon.img property. Ideally we would just work with the ID, rendering a <QueryMedia id={ id } /> component to fetch the associated media in order to get its URL, but since icons are shown in the site selector, this might be unreasonably network intensive.

I'm still working on an implementation to see whether this will pan out, but figured I'd throw the idea out there.

@dmsnell
Copy link
Member

dmsnell commented Dec 15, 2016

but since icons are shown in the site selector, this might be unreasonably network intensive.

as an aside, this would be a good use for the API middleware. no need to refetch icons within a certain time, maybe within any given session. the middleware could what which ones have been fetched and ignore new requests for the same one.

I've been thinking about a force protocol overall for cases where we want to refresh for sure, which I think would be well suited for some action meta

@aduth aduth force-pushed the update/media-delete-reset-site-icon branch from 4435341 to b68534c Compare December 19, 2016 20:33
@aduth
Copy link
Contributor Author

aduth commented Dec 19, 2016

Ok, took me a little longer than I'd hoped to implement the approach suggested in my previous comment, but is now reflected in the last handful of commits. It's a reasonable departure from the previous direction. Instead of assigning an icon URL to be tracked in site state, it's now assumed that the site items are aware of the media ID associated with the site icon by its icon.media_id property*. Until media is properly Reduxified, we receive the saved item into state manually after having cropped the selected image for icon. Then, when updating the site settings, the reducer for site updates its own state to use this ID. When <SiteIcon /> renders next, it tries to look up the icon URL using the site and media state. When deleting, the sites reducer only needs to check whether the IDs of the items being deleted includes the currently assigned icon ID.

* This is not yet passed by the API, and I plan to work on this next. Though for our purposes this works fairly well as-is, given fallbacks and icon updates within browser state. Only problem is if the site is received (e.g. polling) after the icon had been updated in the same session.

@aduth
Copy link
Contributor Author

aduth commented Dec 19, 2016

Also, yes, the changes are quite large, and there's a decent amount of overlap with related pull requests (particularly media state). Hoping with some merging / rebasing it will come down to a more reasonable size.

@aduth
Copy link
Contributor Author

aduth commented Dec 19, 2016

D2940-code

export default connect( ( state, { site, siteId, imgSize } ) => {
// Until sites state is completely within Redux, we provide compatibility
// in cases where site object is passed to use the icon.img property as URL
if ( site ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just asking, one could refactor the parent component and provide a "redux" site instead no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just asking, one could refactor the parent component and provide a "redux" site instead no?

In some cases, yes. But only for sites we know to be in state, which at the moment only includes the currently selected site. Since site icons are shown in the site selector, we can't yet use Redux state there.

case SITE_ICON_SET: {
const { siteId, iconUrl } = action;
case SITE_SETTINGS_UPDATE:
case SITE_SETTINGS_RECEIVE: {
Copy link
Contributor

Choose a reason for hiding this comment

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

So, If I understand properly we're receiving the site Icon ID from the site settings endpoint, I'm wondering why we're storing it in this reducer, instead of relying on the siteSettings state to handle it (like other settings)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nevermind, after review the API diff, I understand why you had 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.

So, If I understand properly we're receiving the site Icon ID from the site settings endpoint, I'm wondering why we're storing it in this reducer, instead of relying on the siteSettings state to handle it (like other settings)?
Nevermind, after review the API diff, I understand why you had to do that :)

Yeah, with these changes, we're treating site.icon.media_id as the preferred source of truth for the site icon, which means we want to reconcile when incoming settings are received. Now that you mention it though, this might need to go both ways: when we receive site(s), and we know those sites in the siteSettings reducer, we'll probably want to update the site icon value there (though this likely would have little effect since, as I mentioned, we want to prefer using it on the site state anyways).

Copy link
Member

Choose a reason for hiding this comment

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

we're treating site.icon.media_id as the preferred source of truth for the site icon, which means we want to reconcile when incoming settings are received

beautiful. I love seeing these changes happen

Copy link
Contributor

Choose a reason for hiding this comment

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

You mentioned in a chat that you prefer to keep the 'icon' attribute on the site endpoint to avoid extra requests. But on the same time, you're considering here site.icon.media_id as the preferred source of truth (which I agree with).

Maybe we should not have to care about the extra requests here (unless it's problematic, which I don't think it is), and maybe leave the DataLayer optimize this later.

Also, if the media_id is some general information about the site, we should add it to the site endpoint directly, don't you think?

Copy link
Member

Choose a reason for hiding this comment

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

@youknowriad there was a distinction made a long time ago between site and siteSettings on the API-side wherein site requests return quick data but settings request get data that is much slower to fetch on the server side. it's unlikely right now that this will change.

leave the DataLayer optimize this later

I agree with this and think it's a great place to introduce the HTTP request batching, but there are steps involved there, so maybe some clever logic in the API layer can get us there in the meantime

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also that /sites/%s/settings is read/write while /sites/%s is read-only.

// exclusively using the site ID
return {
site: getSite( state, siteId ),
iconUrl: getSiteIconUrl( state, siteId, imgSize )
Copy link
Contributor

Choose a reason for hiding this comment

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

This selector relies on two sub-state trees: sites and media, Shouldn't we add Queries to the component? probably a QuerySites since "in theory" the component could get any siteId as prop. I and guess QueryMedia is probably not available yet and receiveMedia is only called if we use the SiteIconSetting component, so I wonder how is it filled in that case?

Copy link
Contributor Author

@aduth aduth Dec 20, 2016

Choose a reason for hiding this comment

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

This selector relies on two sub-state trees: sites and media, Shouldn't we add Queries to the component?

You raise a good point here. In an earlier comment, I noted a worry about the number of requests from <SiteIcon /> components since they're used for each of the user's sites in the site selector.

If we were to render a <QuerySites />, maybe it should be limited to the condition that the site data is not yet known (and that it's passed as a site ID, i.e. Redux)?

Regarding <QueryMedia />, we benefit from the fallback behavior which uses the site's site.icon.img property (a URL string) if the media by ID is not known. The ID only changes when updating the site icon within Calypso, at which point we've received the media explicitly (in <SiteIconSetting />). I think this should be enough that it would never be inaccurate, though admittedly depends on a tangled set of assumptions.

Copy link
Member

Choose a reason for hiding this comment

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

ytihhrt

??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ytihhrt

??

😆

Corrected to "requests".

Copy link
Member

@dmsnell dmsnell Dec 20, 2016

Choose a reason for hiding this comment

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

If we were to render a , maybe it should be limited to the condition that the site data is not yet known (and that it's passed as a site ID, i.e. Redux)?

Why is this? Is it for data usage? If so, maybe we should be able to do a new fetch just for the site info data. This would be trivial in the new API data layer since we could trap some action such as SITE_ICON_REQUEST and make an API call presumably to the same site settings endpoint from where it comes but then limiting the response fields to only the site icon data.

Sounds like it's time sooner than later to introduce the HTTP layer which turns a sequence of rapid-fire HTTP requests into a BATCH request

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is this?

Mainly because otherwise every time I open my site selector, it would issue about 30 requests for each site shown, even if we already have data for those sites from /me/sites. We have this implied assumption in Calypso that sites data is expected to be universally requested via /me/sites, not specifically through any one query component. Not that this is very good, mind you, but the current situation nonetheless.

@youknowriad
Copy link
Contributor

While testing the API side, I noticed that for WPCOM Sites using Site Icon (and not blavatar) don't have the icon property on the /sites/%s endpoint. Should wa add a <QuerySiteSettings> to make sure this usecase is covered by this PR?

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

@aduth I took this for a spin again and It's working as expected. And as you said, the API diff in code-D2990 will add the icon property when relying on the site-icon setting.

@aduth aduth force-pushed the update/media-delete-reset-site-icon branch from ebc2890 to 97159f9 Compare December 23, 2016 15:15
@aduth
Copy link
Contributor Author

aduth commented Dec 23, 2016

Ah, sorry I was a bit late in pushing my rebase @youknowriad . Not too much has changed at a high level, but specifically:

  • Reorganized, squashed, and shuffled changes around in commit history
  • Opted to remove keyedReducer for now since I do not want to persist media (yet) and this is not currently possible with the utility. Instead reimplemented reducers manually keying by siteId.
  • Moved selectors to global selectors directory
  • Split off getSiteIconId into distinct selector. This is a tad granular for my liking, but I think is important to ensure we have consistent retrieval of site ID.

To expand on the last point, I had mentioned that we may need to add handlers to site settings to update the site_icon properties for sites it tracks, but in retrospect, if we are to treat sites as the preferred source for icon ID, this is unnecessary. Using a selector should discourage developers from trying to access the site setting's site_icon directly.

@@ -64,7 +66,21 @@ export const items = createReducer( {}, {
...state[ siteId ],
...settings
}
} )
} ),
[ MEDIA_DELETE ]: ( state, { siteId, mediaIds } ) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, given my note at #10016 (comment), maybe we don't even need to handle this.

Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, you're saying that we always rely on the site object to retrieve the siteIcon. Thus, we should not worry about resetting it on the siteSettings? Is this assumption correct? Maybe we should consider dropping the selector fallback then, since the site-settings value could be incorrect (even if in the current workflow, it can never happen since the site object will be available in this case) ?

Copy link
Contributor Author

@aduth aduth Dec 23, 2016

Choose a reason for hiding this comment

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

You raise a good point. The fallback exists in case we ever receive site settings before the site itself. But if that were ever to occur, we would still want to monitor for media deletions even if we only knew the site settings. But we still do not need to account for SITE_RECEIVE or SITES_RECEIVE in the site settings reducer because at that point we can assume that the fallback will no longer be used.

Because of this, I'll plan to drop 15cb3f5 in a rebase and revert back to this previous behavior.

@@ -0,0 +1,16 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Great to see selectors making their way to the global directory

@youknowriad
Copy link
Contributor

I tested after the rebase and it's still working properly

@aduth aduth force-pushed the update/media-delete-reset-site-icon branch from 15cb3f5 to 5ed530e Compare December 23, 2016 17:48
This was referenced Dec 28, 2016
@aduth aduth force-pushed the update/media-delete-reset-site-icon branch from 5ed530e to b9740a2 Compare January 2, 2017 15:13
@aduth aduth removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jan 2, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Media The media screen in Calypso, general media management, or integration with third party media. [Feature] Site Settings All other general site settings. Sites
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants