New Plugin API: Health Check

Hi folks,

One thing that you might find useful is the ability for RapidWeaver to analyse the contents of your Styled Text views, and report any issues with the contents (e.g. images without alt attributes etc). Starting in today’s build, when Health Check runs, it’ll call

- (NSArray *)attributedStringsRequiringStandardHealthChecks;

an example from our Styled Text plugin:

- (NSArray *)attributedStringsRequiringStandardHealthChecks
{
    if (self.styledTextView.textView.attributedString == nil) {
        return nil;
    }
    
    return @[self.styledTextView.textView.attributedString];
}`

Correspondingly, when the user clicks “Fix” for a Health Check error, we will call:

- (RWStyledTextView *)styledTextViewForAttributedString:(NSAttributedString *)attributedString;

From our blog plugin:

- (RWStyledTextView *)styledTextViewForAttributedString:(NSAttributedString *)attributedString
{
    RWBlogEntry *foundBlogEntry = nil;
    
    for (RWBlogEntry *blogEntry in self->_blogEntries) {
        if (attributedString == blogEntry.entry) {
            foundBlogEntry = blogEntry;
            break;
        }
    }
    
    if (!foundBlogEntry) {
        return nil;
    }
    
    NSUInteger index = [self->_blogEntries indexOfObject:foundBlogEntry];
    [self->entryTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
    
    return self->entryTextView;
}

This is, of course, optional. But we strongly recommend that your styled text areas opt in to this, so that users can improve their pages!

Cheers,

—Nik

1 Like

Handing out the text view beyond the boundaries of that view’s controller seems… really really scary. It really seems like a violation of all kinds of information hiding / API boundaries.

For instance, in your example… your HealthCheck code now has access to many blog posts – and could easily do things that would cause the text view delegate (the blog plugin) to do bad things. Without inside knowledge of how the blog plugin works your health-check code won’t really know what it can and cannot, when it can/cannot do it, etc.

Are there some limits to what the health-check api will do with these views? Would it not be more prudent to perhaps make a protocol where the health-check talked with the controller (the view delegate) – and let the controller work with it’s view in the normal way?

ALSO: self->_blogEntries alright, fess up, who’s the C++ coder? this syntax is :nerd_face:(nerderiffic)

1 Like

Hey Isaiah,

Thanks for sharing your concerns on the health check API. We’ve taken this onboard and revised the API as follows.

This API call has been removed

- (RWStyledTextView *)styledTextViewForAttributedString:(NSAttributedString *)attributedString;

And replaced with

- (void)showEditorForAttributedString:(id)attributedString attachment:(id)attachment;

So now, when a user clicks “Fix” for a Health Check error, we will call the new API method allowing the plugin do perform any routing / UI updates as required, and then ask the textview to present the editor for the attachment.

So a revised blog plugin implementation using this API would look like this:

- (void)showEditorForAttributedString:(id)attributedString attachment:(id)attachment
{
    RWBlogEntry *foundBlogEntry = nil;
    
    for (RWBlogEntry *blogEntry in self->_blogEntries) {
        if (attributedString == blogEntry.entry) {
            foundBlogEntry = blogEntry;
            break;
        }
    }
    
    if (!foundBlogEntry) {
        return;
    }
    
    NSUInteger index = [self->_blogEntries indexOfObject:foundBlogEntry];
    [self->entryTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
    
    [self->entryTextView.textView showEditorForAttachment:attachment plugin:self];
}

PS. I actually have no idea who wrote the C++ code… way before my time LOL

Looks good. the C++ code was probably Simon Taylor from many, many moons ago :wink:

Much improved API.

Is there a 10,000ft. view of what/how this system works? I mean, I think I can pretty easily implement this protocol, but – from a customer perspective – what’s happening here? How do you guys see this working – from a customer viewpoint – with some of the 3rd party plugins?

Ok, so essentially the health check API works as follows.

The user clicks the health check button. At this point the health checker will perform a number of site and page checks. The site checks are implemented by us and consist of the following.

  • WebClip icon exists
  • Favicon exists
  • Logo has an appropriate alt tag
  • Banner (if supported) has an appropriate alt tag
  • CSS is consolidated
  • Sitemap is enabled
  • Plugins are up to date
  • Check extra files are minified

We also have a number of standard page checks that are run on every page.

  • Browser title exists
  • Page name is descriptive
  • Metatags contain a valid description

In addition to these page checks, we have the health checker interface so page plugins can perform their own checks. For this you have 2 options.

  1. Implement performHealthCheckWithErrorReportingBlock:.
  • This API allows you to perform your own checks, reporting any issues along with a block to call if the user clicks the “Fix” button.
  1. Implement attributedStringsRequiringStandardHealthChecks: and showEditorForAttributedString:attachment:.
  • This API allows you to ask RW to perform standard checks on attributed strings and to present the appropriate editor if the user clicks “Fix”.

When using option 2, RW performs four standard checks on attributed strings.

  • Image prefix is valid
  • Image has missing alt tag
  • Filename and alt tag are different
  • Links point to a valid page

That’s great info. Lots of details. Thanks!

But… I still kind of have the same question… I understand the headers just fine – I’m just not sure what I’m supposed to do with them.

Let me rephrase. And distill it down to something really specific.

How should Stacks use this?

I can’t find traces of these new APIs in RWPluginKit repository/wiki on github nor in RWKit’s headers.

Any hints?

Thanks.

Just to be clear, what’s the official source of information about this matter? Framework headers? RWKit Sample Project? RWKit wiki? This thread? :sob:

Complete method signatures should be something like:

- (void)performHealthCheckWithErrorReportingBlock:(void (^)(void))completionHandler;
- (NSArray *)attributedStringsRequiringStandardHealthChecks;
- (void)showEditorForAttributedString:(id)attributedString attachment:(id)attachment;

Is this correct?

  1. performHealthCheckWithErrorReportingBlock: seems rather useful, but has only been fleetingly mentioned.

  2. I’ve tried implementing these methods but none of them is called performing a health check (I’m on RW7 17082b).

Thanks.

We’ll look at updating the documentation on GitHub.

In the meantime, @tpbradley should be able to help you with this :+1:t3:

First up, apologies for the confusion here! I’ve just corrected an issue in RWPluginKit so you should have access to the correct header files now.

In order to use the health check API, please add the RWPluginHealthCheckProtocol protocol to your RWAbstractPlugin subclass.

After doing so, you may implement some or all of the protocol’s methods. From the RWPluginHealthCheckProtocol header file:

// Perform standard health checks on NSAttributedStrings
- (NSArray *)attributedStringsRequiringStandardHealthChecks;
- (void)showEditorForAttributedString:(id)attributedString attachment:(id)attachment;

// Perform custom health checks
- (void)performHealthCheckWithErrorReportingBlock:(void(^)(NSString *errorTitle, NSString *errorDescription, void(^repairBlock)()))reportError;

You could for example implement the performHealthCheckWithErrorReportingBlock: method to perform all your health checks. This method will be called once on the plugin associated with each page. When called, your plugin should perform any checks necessary and call the reportError block for each problem found. The reportError block will add one additional item in the health check UI in the current page group, showing the title and description along with a “Fix” button. When the “Fix” button is pressed, the repairBlock is called allowing you to perform any automated repairs or UI updates.

For example, say I’ve created a very simple (fictitious) example plugin that returns the HTML for a copyright notice. I implement the performHealthCheckWithErrorReportingBlock: method and add 2 checks, one for company name and the other for the year like this:

- (void)performHealthCheckWithErrorReportingBlock:(void (^)(NSString *errorTitle, NSString *errorDescription, void (^repairBlock)()))reportError
{
	// At this point I examine everything in my plugin to see if I can detect any errors. For instance, this plugin generates copyright notices so it could check if the company name exists. Also, it could check that the year is correct.
	
	// Check company name
	if (self.companyName == nil || [self.companyName isEqualToString:@""]) {
		
		// call reportError to present an error in the health check list
		reportError(@"Empty Company Name", @"You should enter your company name", ^(){
			
			// User has clicked "fix", lets set a company name
			self.companyName = @"Animated Gifs 4U Ltd";
			
			// You could also set focus to the company name field here in the UI
		});
	}
	
	// Check for a valid year
	if (self.year == nil || [self.year integerValue] != 2016) {
		
		// call reportError to present an error in the health check list
		reportError(@"Incorrect year", @"You live in 2016 now ;)", ^(){
			
			// User has clicked "fix", lets correct the year
			self.year = @(2016);
			
		});
	}
}

When running the health checker, I now see the following.

Hope this cleans things up a little :wink:

Cheers
Tom

Thanks for the useful info. I’ll try this soon in order to make our plugins health check-aware.

Question: can we safely link against RW7’s RMKit/RWKit and still build a RW6 compatible plugin?

It’s possible it will work just fine. We’ve mostly added and deprecated stuff but I can’t say for certain if something has been removed or is incompatible. Give it a try and see if anything breaks :wink:

can we safely link against RW7’s RMKit/RWKit and still build a RW6 compatible plugin

dynamic linking works by name and the already loaded frameworks will always be chosen before searching for new ones in the framework-search-path

that means that you can link against the most recent R*Kit frameworks – but must be careful to check to see if recent methods/classes in the new frameworks are implemented before using them – basically the exact same rules that we use in linking to the OS X 10.11 SDK while maintaining compatibility back to 10.9.

I’m sorry to pester you about this matter, but can’t find the mentioned RWPluginHealthCheckProtocol in latest RW7 beta Framework nor in Github, so I assume this thread is the only source of information so far and this was added to a yet to be released version of the framework.

Thanks for the info. Let me rephrase my doubt: still wondering if linking a stable release of my plugins to beta release of still-evolving RM’s framework is a good idea.

Hey Gibo,

Can you confirm for me that your looking at the 7.x branch of the RWPluginKit repository?

https://github.com/realmacsoftware/RWPluginKit/tree/7.x

IMHO, it’s never a good idea to link against a beta anything - having said that, you should be ok as the libraries loaded at runtime in RW6 will be the RW6 release versions. You could run into trouble if we changed a method’s parameter, say from a dictionary to a string. Your plugin would be sending a string, which RW7 library would expect, but the RW6 library will still be expecting a dictionary. I’m pretty sure we’ve not changed anything like that (we try hard not to) but I can’t say a definite yes or no.

Cheers
Tom

Got it. Thanks for you support!

EDIT:

I was blindly trusting Github online search…
https://github.com/realmacsoftware/RWPluginKit/search?utf8=✓&q=RWPluginHealthCheckProtocol

Again, thanks for your help.

Hah, yeah… a super useful tool that only seems to search master

Your plugin would be sending a string, which RW7 library would expect, but the RW6 library will still be expecting a dictionary. I’m pretty sure we’ve not changed anything like that (we try hard not to) but I can’t say a definite yes or no.

Breaking the ABI compatibility is a no-no in RapidWeaver. Plugins, including some that are very very very widely used – rely on ABI compatibility in order to be installable on multiple versions.

There is a PLIST variable called RWPluginAPIVersion – I would expect that if you intend to break ABI compatibility the you would increment that variable and provide a mechanism for users to install the right version of a plugin for each version of their app.

That said, there have been very few backward ABI breaking changes in RW – even going back to 2005. Hopefully this tradition will carry on. :slight_smile:

Isaiah

1 Like

Isaiah, you’re spot on! The only reason I say I can’t confirm absolute compatibility is that I’ve only been working on RapidWeaver since August last year. I’m positive I’ve not changed anything that would cause issues and I don’t think the other devs would have either :wink: