require 'erb' require 'helix_web_services_client/models' require 'helix_versioning_engine/change_service' require 'helix_versioning_engine/file_service' require 'projects/errors/illegal_project_structure' module Projects # Handles access to project metadata class ProjectService # Access to the p4 instance, typically initialized from the user's # connection attr_accessor :p4 attr_accessor :client_name attr_accessor :client_root # Application settings (from one of the Sinatra classes) # # Settings used: # # - project_path_template: defaults to '//.helix-web-services/.projects/<%= project.id %>.json' # attr_accessor :settings @@before_create = [] @@after_create = [] def initialize(p4: nil, client_name: nil, client_root: nil, settings: nil) @p4 = p4 @client_name = client_name @client_root = client_root @settings = settings end # Returns an array of projects meeting various filter criteria # # If no filter criteria are used, will basically call find_all. # # TODO this should collate a list of 'project files' and filter the list # of projects, in case def find(names: nil, owners: nil, extensions: nil, members: nil) ds = HelixWebServices::CacheData::Project.dataset.select_all(:projects) ds = ds.where('name in ?', names) if names.is_a?(Array) ds = ds.where('name = ?', names) if names.is_a?(String) ds = ds.where('owner in ?', owners) if owners.is_a?(Array) ds = ds.where('owner = ?', owners) if owners.is_a?(String) if extensions.is_a?(Array) ds = ds.where('iid in ' + '(select project_iid from extensions where content_type in ?)', extensions) elsif extensions.is_a?(String) ds = ds.where('iid in ' + '(select project_iid from extensions where content_type = ?)', extensions) end if members.is_a?(Array) ds = ds.where('iid in ' + '(select project_iid from members where user in ?)', members) .or('owner in ?', members) elsif members.is_a?(String) ds = ds.where('iid in ' + '(select project_iid from members where user = ?)', members) .or('owner = ?', members) end ds.all.map(&:to_model) end def find_by_id(id) HelixWebServices::CacheData::Project.dataset.where('id = ?', id).first.to_model end # Register a callback method called before each create method # # Each service instance can register other service callback types that # we use. # # Each callback actually takes an options hash: # # - p4 # - client_name # - client_root # - settings # # Example: # # ProjectService.before_create do |project, options| # my_service = MyService.new(p4: options.p4) # my_service.deal_with_it(project) # end # # If your application finds an issue, it should raise an # IllegalProjectStructure error (or subclass). def self.before_create @@before_create end def self.after_create @@after_create end def create(project) @@before_create.each do |handler| handler.call(project, {p4: p4, client_name: client_name, client_root: client_root, settings: settings}) end depot_path = json_depot_path(project) # TODO we may want to kick the project if this file is not deleted or missing json_metadata = file_service.list_file(depot_path) if json_metadata project.version = json_metadata.revision + 1 else project.version = 1 end set_defaults(project) json = JSON.generate(project) files_to_update = [{ depot_file: depot_path, content: json, action: 'upload', require_version: project.version - 1 }] change_service.submit(files: files_to_update, description: "Create project metadata #{project.name}") HelixWebServices::CacheData::Project.create_from_model(project) @@after_create.each do |handler| handler.call(project, {p4: p4, client_name: client_name, client_root: client_root, settings: settings}) end project end def update(project) depot_path = json_depot_path(project) json = JSON.generate(project) json_metadata = file_service.list_file(depot_path) if json_metadata project.version = json_metadata.revision + 1 else project.version = 1 end db_project = HelixWebServices::CacheData::Project.dataset.where(id: project.id).first raise "cached project doesn't exist #{project.id}" if db_project.nil? files_to_update = [{ depot_file: depot_path, content: json, action: 'upload', require_version: project.version - 1 }] change_service.submit(files: files_to_update, description: "Updating project #{project.name}") # Need the update file data since we can't really predict the revision json_metadata = file_service.list_file(depot_path) # Update DB project db_project.name = project.name if project.name db_project.version = json_metadata.revision db_project.description = project.description if project.description db_project.owner = project.owner if project.owner if project.members db_project.remove_all_members project.members.each do |member| db_member = HelixWebServices::CacheData::Member.new db_member.user = member db_project.add_member(db_member) db_project.save end end if project.branches project.branches.each do |branch| db_branch = HelixWebServices::CacheData::BranchRef.dataset.where(project_iid: db_project.iid, id: branch.id) .first add = false if db_branch.nil? db_branch = HelixWebServices::CacheData::BranchRef.new if db_branch.nil? add = true end db_branch.id = branch.id db_branch.name = branch.name if branch.name db_branch.stream = branch.stream if branch.stream db_project.add_branch(db_branch) if add db_branch.save if add if branch.view db_branch.remove_all_view_refs branch.view.each do |v| db_view = HelixWebServices::CacheData::ViewRef.new db_view.depot_path = v.depot_path db_view.view_path = v.view_path db_branch.add_view_ref(db_view) db_branch.save end end end end unless project.extensions.nil? or project.extensions.keys.empty? db_project.remove_all_extensions project.extensions.keys.each do |key| db_ext = HelixWebServices::CacheData::Extension.new db_ext.content_type = project.extensions[key].class.content_type db_ext.json = project.extensions[key].to_json db_project.add_extension(db_ext) end end db_project.save end # The depot path for the indicated project. # # Can be overridden by application settings. def json_depot_path(project) erb = '//.projects/<%= project.id %>.json' if settings && settings.project_path_template erb = settings.project_path_template end ERB.new(erb).result(project.instance_eval { binding }) end def change_service # This is an assertion. You should never hit it. if p4.nil? || client_name.nil? || client_root.nil? raise 'change_service accessed without setting p4, client_name, client_root' end Perforce::ChangeService.new(p4: p4, client_name: client_name, client_root: client_root) end def file_service if p4.nil? raise 'file_service accessed without setting p4' end Perforce::FileService.new(p4: p4) end def set_defaults(project) set_default_id(project) set_default_owner(project) end def set_default_id(project) unless project.id project.id = project.name.gsub(/[^a-zA-Z0-9_]/, '_').downcase end end def set_default_owner(project) unless project.owner project.owner = p4.user end end end end
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#13 | 15622 | tjuricek |
Move source code to 'source/' subdirectory of branch. build/ will remain where it is. |
||
#12 | 15099 | tjuricek | Revise project services to be our simple 'container' for other systems. | ||
#11 | 15098 | tjuricek |
Revised project services to GET-only forms. With Helix Sync revising to integrate purely with Helix Cloud, this is the only thing we can reasonably define. |
||
#10 | 15090 | tjuricek |
Update _proposed_ API for project services. This is *very likely* to change, and will not be implemented until reviewed. |
||
#9 | 15077 | tjuricek |
Add new 'model' technique, revised branch spec operations, test Auth::Middleware. The Ruby client now does *not* strictly type anything, but extends OpenStruct with helper methods to help deal with inconsistent data formats. See the OpenModel class documentation for more details. The Auth::Middleware class is also *finally* implemented as well. This does not take into account all possible variations of server behavior (yet), but that will happen in follow-up work. |
||
#8 | 15032 | tjuricek |
Starting config and doc revisions. System is now broken while revisions underway. Configuration of the p4d connection is now done via a single HWSSettings middleware object injected into the Rack env. The HWSP4Cleanup middleware now cleans up any p4 injected into the Rack env. The Auth::App class now mostly just contains one method to generate a p4 ticket. /auth/v1/login. Added yard documentation for the main project. Yard docs have been reconfigured to dump into build/ directories. This should probably be done with each release. Hm... The top level rake file contains a task, 'all:doc', to update our documentation. This should probably be run for each checkin. Hm... Specs are now using Rack::Test on top of a 'live' p4d. I'd suggest you still use the p4util mechanism, which now dumps to a /tmp folder, so we can safely add P4IGNORE rules back into your local .p4config file. Old 'perforce' application now called 'helix_versioning_engine'. Removing cache data. Helix Sync may be slow. It may also get axed. We'll see. |
||
#7 | 13941 | tjuricek |
Re-implemented the sync project methods at the HTTP level. The Qt API is missing the "members" concept, but it's likely not quite usable just yet. It's existing logic does work, however. |
||
#6 | 13939 | tjuricek |
First pass at sync project service. Includes a first pass at validating projects enabled for Sync. (There's only one branch check at the moment.) Does not include the HTTP layer. |
||
#5 | 13891 | tjuricek |
Added a 'member' concept to projects. members is the start of a basic project 'role' definition. In Helix sync, this is mostly just used to indicate this is a "my Project" in the UI and a project member can have the ability to leave the project or add someone else. Eventually, we expect "members" to mean more, like, is part of a group that has write access to the files of the project. But that sort of definition is very TBD. |
||
#4 | 13884 | tjuricek | Add Project extension classes models, and the ability for the Project to handle before- and after- create callbacks to other services. | ||
#3 | 13847 | tjuricek | Implement model conversion from the cache data project definitions, which gets basic CRU methods functioning in the converted ProjectService | ||
#2 | 13846 | tjuricek | Setup development rules for creating/initializing PostgreSQL, and updated some of the ProjectService specs. | ||
#1 | 13839 | tjuricek |
Conversion of the p4_project_service microservice to new monolithic system. This may not have an HTTP front end in the monolithic system. Project services are really just about how the core object model is structured. It's likely that each application will add their own wrinkles and extensions to the system, so it's unlikely we'll need a generic "project model". Exactly how extensions are registered and used is still a bit TBD at the moment. Previously they were to be registered webhooks, that model may change. Does not include tests yet. |