How to store plugin settings at repo + project level?


#1

For my BB plugin, I’d like to store some settings at the repository level and at the project level. My plugin is not a hook, therefore I guess the SettingsBuilder of the RepositoryHookService is not the right choice, is it?
From my reading, I think ActiveObjects can be used but I don’t like that:

  • it’s relational and my data would better be modeled as a document (e.g. JSON structure)
  • with AO the data is not linked to BB objects that repo or project. If a repo is deleted, my plugin’s data will not be removed

What do you advise me to use? I just want to store a few URLs and properties per repo and project.

Sylvain


#2

Hi Sylvian,

You’re correct in assuming that RepositoryHookService is not the right choice. AO is generally the recommended option, but the two points you raised are valid. Two things to note though:

  • You can use a CLOB field to store your document by setting the string length to unlimited for that field (@StringLength(UNLIMITED)). The documentation says:

The value can be a positive integer or {@link #UNLIMITED}; an unlimited-length string is stored as the corresponding “long string” database type (TEXT, CLOB, etc. depending on the database provider).

  • Cleaning up the data if a repo is deleted can be done by adding an event listener which listens to the RepositoryDeletedEvent
    @EventListener
    public void onRepositoryDeleted(RepositoryDeletedEvent event) {
        delete(event.getRepository());
    }

Another option is to use plugin settings, though it is intended for storing settings at a global plugin-level, not at a repo level. That said, you can still find examples of this being used for repository settings. Note that you will still have to listen to the RepositoryDeletedEvent to remove old settings data.

My answer on a previous question about how to store data might also be of use to you.

Hope that helps!
Kristy


#3

I found Plugin com.atlassian.sal.api.pluginsettings.PluginSettings quite useful for both global and project/repo settings.

Global Settings

Global configurations for your app (add-on) can be stored/read via

com.atlassian.sal.api.pluginsettings.PluginSettingsFactory#createGlobalSettings

Project / Repo settings

com.atlassian.sal.api.pluginsettings.PluginSettingsFactory#createSettingsForKey means you can craft name spaces (keys) for your project and repo settings. You can create a namespace for

  • project setting using the projectKey
  • repo settings using the projectKey and repoId or repoSlug

I’m not sure the PluginSettings implementation is 100% cluster (data-center) safe


#4

Thanks Kristy for the useful reply, especially the onRepositoryDeleted listener I could implement.
In the meantime, I tried nevertheless the RepositoryHookService, implementing a no-op hook with configurable=false (so that it is not displayed in the list of hooks). So far it’s working and is simpler than AO. What are the drawbacks of using this way?


#5

Adding a no-op hook has a few drawbacks:

  • Extra complexity to your code: every time you change something you will have to consider the fact that there is a hook involved
  • Overhead on hook processing: Although the extra processing isn’t significant because the hook is no-op, it will still be added to the list of hooks that have to run and be called every time we would normally call a hook

I recommend using plugin settings. It is simpler than the hook settings and means that you’re not using the hook settings incorrectly. The example I linked in my initial answer is pretty much exactly what you want.

Create the settings for your plugin in the constructor of your class

pluginSettings = pluginSettingsFactory.createSettingsForKey("my.plugin.settings");

Then you use it just like a map:

pluginSettings.put("repo." + repository.getId() + ".settings", settingsObject));
pluginSettings.get("repo." + repository.getId() + ".settings");

And you can clean up the settings when the repo is deleted

@EventListener
public void onRepositoryDeleted(RepositoryDeletedEvent event) {
    pluginSettings.remove("repo." + event.getRepository().getId() + ".settings");
}

#6

This is good advice! A few tiny subtle extra tips, though (based on my experience maintaining my company’s free Control Freak for Bitbucket Server add-on):

  • @izymesdev recommended storing settings using repoId + projectKey. DON’T DO THIS! The repoId is already globally unique and stable across your entire instance, and repositories can be moved between projects. If you store settings with repoId + projectKey the settings will get lost/dropped whenever a repository is moved between project areas.

  • As @khughes showed in her example, you only need to store the settings by “repoId”.

  • If you want to store per-project settings, then you should use projectId (NOT projectKey, since that’s also mutable!).

  • Repository object also has a getProject() method. Grab the projectId like so: repo.getProject().getId()

  • Be sure to test against user’s personal areas (they behave like a type of project in your code).