Timeline based Manual Entry form (#330)
This commit is contained in:
parent
7a77dcdcd6
commit
d23af018e7
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ func (gr *GormRepository) Close() error {
|
||||||
// User
|
// User
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// <editor-fold desc="User">
|
||||||
func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) error {
|
func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) error {
|
||||||
if err := user.HashPassword(user.Password); err != nil {
|
if err := user.HashPassword(user.Password); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -53,6 +55,17 @@ func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//create Fasten source credential for this user.
|
||||||
|
fastenUserCred := models.SourceCredential{
|
||||||
|
UserID: user.ID,
|
||||||
|
SourceType: sourcePkg.SourceTypeFasten,
|
||||||
|
}
|
||||||
|
fastenUserCredResp := gr.GormClient.Create(&fastenUserCred)
|
||||||
|
if fastenUserCredResp.Error != nil {
|
||||||
|
return fastenUserCredResp.Error
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (gr *GormRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
|
func (gr *GormRepository) GetUserByUsername(ctx context.Context, username string) (*models.User, error) {
|
||||||
|
@ -94,10 +107,13 @@ func (gr *GormRepository) GetCurrentUser(ctx context.Context) (*models.User, err
|
||||||
return ¤tUser, nil
|
return ¤tUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Glossary
|
// Glossary
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// <editor-fold desc="Glossary">
|
||||||
func (gr *GormRepository) CreateGlossaryEntry(ctx context.Context, glossaryEntry *models.Glossary) error {
|
func (gr *GormRepository) CreateGlossaryEntry(ctx context.Context, glossaryEntry *models.Glossary) error {
|
||||||
record := gr.GormClient.WithContext(ctx).Create(glossaryEntry)
|
record := gr.GormClient.WithContext(ctx).Create(glossaryEntry)
|
||||||
if record.Error != nil {
|
if record.Error != nil {
|
||||||
|
@ -114,6 +130,8 @@ func (gr *GormRepository) GetGlossaryEntry(ctx context.Context, code string, cod
|
||||||
return &foundGlossaryEntry, result.Error
|
return &foundGlossaryEntry, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Summary
|
// Summary
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -180,6 +198,8 @@ func (gr *GormRepository) GetSummary(ctx context.Context) (*models.Summary, erro
|
||||||
// Resource
|
// Resource
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// <editor-fold desc="Resource">
|
||||||
|
|
||||||
// This function will create a new resource if it does not exist, or update an existing resource if it does exist.
|
// This function will create a new resource if it does not exist, or update an existing resource if it does exist.
|
||||||
// It will also create associations between fhir resources
|
// It will also create associations between fhir resources
|
||||||
// This function is called directly by fasten-sources
|
// This function is called directly by fasten-sources
|
||||||
|
@ -210,12 +230,35 @@ func (gr *GormRepository) UpsertRawResource(ctx context.Context, sourceCredentia
|
||||||
//note: these associations are not reciprocal, (i.e. if Procedure references Location, Location may not reference Procedure)
|
//note: these associations are not reciprocal, (i.e. if Procedure references Location, Location may not reference Procedure)
|
||||||
if rawResource.ReferencedResources != nil && len(rawResource.ReferencedResources) > 0 {
|
if rawResource.ReferencedResources != nil && len(rawResource.ReferencedResources) > 0 {
|
||||||
for _, referencedResource := range rawResource.ReferencedResources {
|
for _, referencedResource := range rawResource.ReferencedResources {
|
||||||
|
|
||||||
|
var relatedResource *models.ResourceBase
|
||||||
|
|
||||||
|
if strings.HasPrefix(referencedResource, sourcePkg.FASTENHEALTH_URN_PREFIX) {
|
||||||
|
gr.Logger.Infof("parsing external urn:fastenhealth-fhir reference: %v", referencedResource)
|
||||||
|
|
||||||
|
targetSourceId, targetResourceType, targetResourceId, err := sourcePkg.ParseReferenceUri(&referencedResource)
|
||||||
|
if err != nil {
|
||||||
|
gr.Logger.Warnf("could not parse urn:fastenhealth-fhir reference: %v", referencedResource)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = gr.UpsertRawResourceAssociation(
|
||||||
|
ctx,
|
||||||
|
source.ID.String(),
|
||||||
|
wrappedResourceModel.SourceResourceType,
|
||||||
|
wrappedResourceModel.SourceResourceID,
|
||||||
|
targetSourceId,
|
||||||
|
targetResourceType,
|
||||||
|
targetResourceId,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
parts := strings.Split(referencedResource, "/")
|
parts := strings.Split(referencedResource, "/")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
relatedResource = &models.ResourceBase{
|
||||||
relatedResource := &models.ResourceBase{
|
|
||||||
OriginBase: models.OriginBase{
|
OriginBase: models.OriginBase{
|
||||||
SourceID: source.ID,
|
SourceID: source.ID,
|
||||||
SourceResourceType: parts[0],
|
SourceResourceType: parts[0],
|
||||||
|
@ -237,11 +280,50 @@ func (gr *GormRepository) UpsertRawResource(ctx context.Context, sourceCredentia
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return gr.UpsertResource(ctx, wrappedResourceModel)
|
return gr.UpsertResource(ctx, wrappedResourceModel)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gr *GormRepository) UpsertRawResourceAssociation(
|
||||||
|
ctx context.Context,
|
||||||
|
sourceId string,
|
||||||
|
sourceResourceType string,
|
||||||
|
sourceResourceId string,
|
||||||
|
targetSourceId string,
|
||||||
|
targetResourceType string,
|
||||||
|
targetResourceId string,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
if sourceId == targetSourceId && sourceResourceType == targetResourceType && sourceResourceId == targetResourceId {
|
||||||
|
gr.Logger.Warnf("cannot create self-referential association, ignoring")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var sourceCredential *models.SourceCredential
|
||||||
|
var targetSourceCredential *models.SourceCredential
|
||||||
|
var err error
|
||||||
|
if sourceId == targetSourceId {
|
||||||
|
sourceCredential, err = gr.GetSource(ctx, sourceId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetSourceCredential = sourceCredential
|
||||||
|
} else {
|
||||||
|
sourceCredential, err = gr.GetSource(ctx, sourceId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetSourceCredential, err = gr.GetSource(ctx, targetSourceId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//SECURITY: sourceCredential and targetSourceCredential are guaranteed to be owned by the same user, and will be confirmed within the addAssociation function
|
||||||
|
return gr.AddResourceAssociation(ctx, sourceCredential, sourceResourceType, sourceResourceId, targetSourceCredential, targetResourceType, targetResourceId)
|
||||||
|
}
|
||||||
|
|
||||||
// UpsertResource
|
// UpsertResource
|
||||||
// this method will upsert a resource, however it will not create associations.
|
// this method will upsert a resource, however it will not create associations.
|
||||||
// UPSERT operation
|
// UPSERT operation
|
||||||
|
@ -448,10 +530,14 @@ func (gr *GormRepository) GetPatientForSources(ctx context.Context) ([]models.Re
|
||||||
return wrappedResourceModels, results.Error
|
return wrappedResourceModels, results.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Resource Associations
|
// Resource Associations
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//<editor-fold desc="Resource Associations">
|
||||||
|
|
||||||
// verifyAssociationPermission ensure that the sources are "owned" by the same user, and that the user is the current user
|
// verifyAssociationPermission ensure that the sources are "owned" by the same user, and that the user is the current user
|
||||||
func (gr *GormRepository) verifyAssociationPermission(ctx context.Context, sourceUserID uuid.UUID, relatedSourceUserID uuid.UUID) error {
|
func (gr *GormRepository) verifyAssociationPermission(ctx context.Context, sourceUserID uuid.UUID, relatedSourceUserID uuid.UUID) error {
|
||||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||||
|
@ -542,11 +628,14 @@ func (gr *GormRepository) FindResourceAssociationsByTypeAndId(ctx context.Contex
|
||||||
ResourceBaseSourceID: source.ID,
|
ResourceBaseSourceID: source.ID,
|
||||||
ResourceBaseSourceResourceType: resourceType,
|
ResourceBaseSourceResourceType: resourceType,
|
||||||
ResourceBaseSourceResourceID: resourceId,
|
ResourceBaseSourceResourceID: resourceId,
|
||||||
|
RelatedResourceUserID: currentUser.ID,
|
||||||
}).
|
}).
|
||||||
Find(&relatedResources)
|
Find(&relatedResources)
|
||||||
return relatedResources, result.Error
|
return relatedResources, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Resource Composition (Grouping)
|
// Resource Composition (Grouping)
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -565,6 +654,8 @@ func (gr *GormRepository) FindResourceAssociationsByTypeAndId(ctx context.Contex
|
||||||
// - add AddResourceAssociation for all resources linked to the Composition resource
|
// - add AddResourceAssociation for all resources linked to the Composition resource
|
||||||
// - store the Composition resource
|
// - store the Composition resource
|
||||||
// TODO: determine if we should be using a List Resource instead of a Composition resource
|
// TODO: determine if we should be using a List Resource instead of a Composition resource
|
||||||
|
//
|
||||||
|
// Deprecated: This method has been deprecated. It has been replaced in favor of Fasten SourceCredential & associations
|
||||||
func (gr *GormRepository) AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error {
|
func (gr *GormRepository) AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error {
|
||||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||||
if currentUserErr != nil {
|
if currentUserErr != nil {
|
||||||
|
@ -718,6 +809,8 @@ func (gr *GormRepository) AddResourceComposition(ctx context.Context, compositio
|
||||||
// SourceCredential
|
// SourceCredential
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//<editor-fold desc="SourceCredential">
|
||||||
|
|
||||||
func (gr *GormRepository) CreateSource(ctx context.Context, sourceCreds *models.SourceCredential) error {
|
func (gr *GormRepository) CreateSource(ctx context.Context, sourceCreds *models.SourceCredential) error {
|
||||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||||
if currentUserErr != nil {
|
if currentUserErr != nil {
|
||||||
|
@ -846,10 +939,10 @@ func (gr *GormRepository) GetSourceSummary(ctx context.Context, sourceId string)
|
||||||
Table(patientTableName).
|
Table(patientTableName).
|
||||||
First(&wrappedPatientResourceModel)
|
First(&wrappedPatientResourceModel)
|
||||||
|
|
||||||
if patientResults.Error != nil {
|
//some sources may not have a patient resource (including the Fasten source)
|
||||||
return nil, patientResults.Error
|
if patientResults.Error == nil {
|
||||||
}
|
|
||||||
sourceSummary.Patient = &wrappedPatientResourceModel
|
sourceSummary.Patient = &wrappedPatientResourceModel
|
||||||
|
}
|
||||||
|
|
||||||
return sourceSummary, nil
|
return sourceSummary, nil
|
||||||
}
|
}
|
||||||
|
@ -925,10 +1018,13 @@ func (gr *GormRepository) DeleteSource(ctx context.Context, sourceId string) (in
|
||||||
return rowsEffected, results.Error
|
return rowsEffected, results.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Background Job
|
// Background Job
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// <editor-fold desc="Background Job & Checkpoints">
|
||||||
func (gr *GormRepository) CreateBackgroundJob(ctx context.Context, backgroundJob *models.BackgroundJob) error {
|
func (gr *GormRepository) CreateBackgroundJob(ctx context.Context, backgroundJob *models.BackgroundJob) error {
|
||||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||||
if currentUserErr != nil {
|
if currentUserErr != nil {
|
||||||
|
@ -1117,6 +1213,8 @@ func (gr *GormRepository) CancelAllLockedBackgroundJobsAndFail() error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Utilities
|
// Utilities
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
|
||||||
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
|
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
|
||||||
|
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
||||||
"github.com/go-gormigrate/gormigrate/v2"
|
"github.com/go-gormigrate/gormigrate/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +17,7 @@ func (gr *GormRepository) Migrate() error {
|
||||||
gormMigrateOptions := gormigrate.DefaultOptions
|
gormMigrateOptions := gormigrate.DefaultOptions
|
||||||
gormMigrateOptions.UseTransaction = true
|
gormMigrateOptions.UseTransaction = true
|
||||||
|
|
||||||
//use echo $(date '+%Y%m%d%H%M%S') to generate new ID's
|
//use "echo $(date '+%Y%m%d%H%M%S')" to generate new ID's
|
||||||
m := gormigrate.New(gr.GormClient, gormMigrateOptions, []*gormigrate.Migration{
|
m := gormigrate.New(gr.GormClient, gormMigrateOptions, []*gormigrate.Migration{
|
||||||
{
|
{
|
||||||
ID: "20231017112246", // base database models //TODO: figure out how to version these correctly (SourceCredential is complicated)
|
ID: "20231017112246", // base database models //TODO: figure out how to version these correctly (SourceCredential is complicated)
|
||||||
|
@ -37,6 +40,31 @@ func (gr *GormRepository) Migrate() error {
|
||||||
return databaseModel.Migrate(tx)
|
return databaseModel.Migrate(tx)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "20231201122541", // Adding Fasten Source Credential for each user
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
|
||||||
|
users := []models.User{}
|
||||||
|
results := tx.Find(&users)
|
||||||
|
if results.Error != nil {
|
||||||
|
return results.Error
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
tx.Logger.Info(context.Background(), fmt.Sprintf("Creating Fasten Source Credential for user: %s", user.ID))
|
||||||
|
|
||||||
|
fastenUserCred := models.SourceCredential{
|
||||||
|
UserID: user.ID,
|
||||||
|
SourceType: sourcePkg.SourceTypeFasten,
|
||||||
|
}
|
||||||
|
fastenUserCredCreateResp := tx.Create(&fastenUserCred)
|
||||||
|
if fastenUserCredCreateResp.Error != nil {
|
||||||
|
tx.Logger.Error(context.Background(), fmt.Sprintf("An error occurred creating Fasten Source Credential for user: %s", user.ID))
|
||||||
|
return fastenUserCredCreateResp.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
|
|
|
@ -1021,7 +1021,7 @@ func (suite *RepositoryTestSuite) TestGetSummary() {
|
||||||
{"count": int64(16), "resource_type": "Procedure"},
|
{"count": int64(16), "resource_type": "Procedure"},
|
||||||
}, sourceSummary.ResourceTypeCounts)
|
}, sourceSummary.ResourceTypeCounts)
|
||||||
|
|
||||||
require.Equal(suite.T(), 2, len(sourceSummary.Sources))
|
require.Equal(suite.T(), 3, len(sourceSummary.Sources))
|
||||||
require.Equal(suite.T(), 2, len(sourceSummary.Patients))
|
require.Equal(suite.T(), 2, len(sourceSummary.Patients))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ type DatabaseRepository interface {
|
||||||
RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
|
RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
|
||||||
FindResourceAssociationsByTypeAndId(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string) ([]models.RelatedResource, error)
|
FindResourceAssociationsByTypeAndId(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string) ([]models.RelatedResource, error)
|
||||||
GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, error)
|
GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, error)
|
||||||
|
|
||||||
|
// Deprecated:This method has been deprecated. It has been replaced in favor of Fasten SourceCredential & associations
|
||||||
AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error
|
AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error
|
||||||
//UpsertProfile(context.Context, *models.Profile) error
|
//UpsertProfile(context.Context, *models.Profile) error
|
||||||
//UpsertOrganziation(context.Context, *models.Organization) error
|
//UpsertOrganziation(context.Context, *models.Organization) error
|
||||||
|
@ -55,6 +57,15 @@ type DatabaseRepository interface {
|
||||||
PopulateDefaultUserSettings(ctx context.Context, userId uuid.UUID) error
|
PopulateDefaultUserSettings(ctx context.Context, userId uuid.UUID) error
|
||||||
|
|
||||||
//used by fasten-sources Clients
|
//used by fasten-sources Clients
|
||||||
UpsertRawResource(ctx context.Context, sourceCredentials sourcePkg.SourceCredential, rawResource sourcePkg.RawResourceFhir) (bool, error)
|
|
||||||
BackgroundJobCheckpoint(ctx context.Context, checkpointData map[string]interface{}, errorData map[string]interface{})
|
BackgroundJobCheckpoint(ctx context.Context, checkpointData map[string]interface{}, errorData map[string]interface{})
|
||||||
|
UpsertRawResource(ctx context.Context, sourceCredentials sourcePkg.SourceCredential, rawResource sourcePkg.RawResourceFhir) (bool, error)
|
||||||
|
UpsertRawResourceAssociation(
|
||||||
|
ctx context.Context,
|
||||||
|
sourceId string,
|
||||||
|
sourceResourceType string,
|
||||||
|
sourceResourceId string,
|
||||||
|
targetSourceId string,
|
||||||
|
targetResourceType string,
|
||||||
|
targetResourceId string,
|
||||||
|
) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,9 @@ type SourceCredential struct {
|
||||||
func (s *SourceCredential) GetSourceType() sourcesPkg.SourceType {
|
func (s *SourceCredential) GetSourceType() sourcesPkg.SourceType {
|
||||||
return s.SourceType
|
return s.SourceType
|
||||||
}
|
}
|
||||||
|
func (s *SourceCredential) GetSourceId() string {
|
||||||
|
return s.ID.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SourceCredential) GetClientId() string {
|
func (s *SourceCredential) GetClientId() string {
|
||||||
return s.ClientId
|
return s.ClientId
|
||||||
|
|
|
@ -17,14 +17,65 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackgroundJobSyncResources is a background job that syncs all FHIR resource for a given source
|
// This function is used to sync resources from a source (via a callback function). The BackgroundJobSyncResourcesWrapper contains the logic for registering the background job tracking the sync.
|
||||||
|
func BackgroundJobSyncResources(
|
||||||
|
parentContext context.Context,
|
||||||
|
logger *logrus.Entry,
|
||||||
|
databaseRepo database.DatabaseRepository,
|
||||||
|
sourceCred *models.SourceCredential,
|
||||||
|
) (sourceModels.UpsertSummary, error) {
|
||||||
|
return BackgroundJobSyncResourcesWrapper(
|
||||||
|
parentContext,
|
||||||
|
logger,
|
||||||
|
databaseRepo,
|
||||||
|
sourceCred,
|
||||||
|
func(
|
||||||
|
_backgroundJobContext context.Context,
|
||||||
|
_logger *logrus.Entry,
|
||||||
|
_databaseRepo database.DatabaseRepository,
|
||||||
|
_sourceCred *models.SourceCredential,
|
||||||
|
) (sourceModels.SourceClient, sourceModels.UpsertSummary, error) {
|
||||||
|
// after creating the client, we should do a bulk import
|
||||||
|
sourceClient, err := factory.GetSourceClient(sourcePkg.GetFastenLighthouseEnv(), _sourceCred.SourceType, _backgroundJobContext, _logger, _sourceCred)
|
||||||
|
if err != nil {
|
||||||
|
resultErr := fmt.Errorf("an error occurred while initializing hub client using source credential: %w", err)
|
||||||
|
_logger.Errorln(resultErr)
|
||||||
|
return nil, sourceModels.UpsertSummary{}, resultErr
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := sourceClient.SyncAll(_databaseRepo)
|
||||||
|
if err != nil {
|
||||||
|
resultErr := fmt.Errorf("an error occurred while bulk importing resources from source: %w", err)
|
||||||
|
_logger.Errorln(resultErr)
|
||||||
|
return sourceClient, summary, resultErr
|
||||||
|
}
|
||||||
|
return sourceClient, summary, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackgroundJobSyncResourcesWrapper is a background job that syncs all FHIR resource for a given source
|
||||||
// It is a blocking function that will return only when the sync is complete or has failed
|
// It is a blocking function that will return only when the sync is complete or has failed
|
||||||
// It will create a background job and associate it with the source
|
// It will create a background job and associate it with the source
|
||||||
// It will also update the access token and refresh token if they have been updated
|
// It will also update the access token and refresh token if they have been updated
|
||||||
// It will return the sync summary and error if any
|
// It will return the sync summary and error if any
|
||||||
|
//
|
||||||
|
// It's a wrapper function that takes a callback function as an argument.
|
||||||
|
// The callback function is the actual sync operation that will be run in the background (regular source or manual source)
|
||||||
|
//
|
||||||
// TODO: run in background thread, or use https://gobyexample.com/tickers
|
// TODO: run in background thread, or use https://gobyexample.com/tickers
|
||||||
// TODO: use goroutine to truely run in the background (how will that work with DatabaseRepository, is that thread safe?) Mutex needed?
|
// TODO: use goroutine to truely run in the background (how will that work with DatabaseRepository, is that thread safe?) Mutex needed?
|
||||||
func BackgroundJobSyncResources(parentContext context.Context, logger *logrus.Entry, databaseRepo database.DatabaseRepository, sourceCred *models.SourceCredential) (sourceModels.UpsertSummary, error) {
|
func BackgroundJobSyncResourcesWrapper(
|
||||||
|
parentContext context.Context,
|
||||||
|
logger *logrus.Entry,
|
||||||
|
databaseRepo database.DatabaseRepository,
|
||||||
|
sourceCred *models.SourceCredential,
|
||||||
|
callbackFn func(
|
||||||
|
_backgroundJobContext context.Context,
|
||||||
|
_logger *logrus.Entry,
|
||||||
|
_databaseRepo database.DatabaseRepository,
|
||||||
|
_sourceCred *models.SourceCredential,
|
||||||
|
) (sourceModels.SourceClient, sourceModels.UpsertSummary, error),
|
||||||
|
) (sourceModels.UpsertSummary, error) {
|
||||||
var resultErr error
|
var resultErr error
|
||||||
var backgroundJob *models.BackgroundJob
|
var backgroundJob *models.BackgroundJob
|
||||||
|
|
||||||
|
@ -54,14 +105,6 @@ func BackgroundJobSyncResources(parentContext context.Context, logger *logrus.En
|
||||||
//we can safely ignore this error, because we'll be updating the status of the background job again later
|
//we can safely ignore this error, because we'll be updating the status of the background job again later
|
||||||
}
|
}
|
||||||
|
|
||||||
// after creating the client, we should do a bulk import
|
|
||||||
sourceClient, err := factory.GetSourceClient(sourcePkg.GetFastenLighthouseEnv(), sourceCred.SourceType, backgroundJobContext, logger, sourceCred)
|
|
||||||
if err != nil {
|
|
||||||
resultErr = fmt.Errorf("an error occurred while initializing hub client using source credential: %w", err)
|
|
||||||
logger.Errorln(resultErr)
|
|
||||||
return sourceModels.UpsertSummary{}, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// BEGIN FINALIZER
|
// BEGIN FINALIZER
|
||||||
defer func() {
|
defer func() {
|
||||||
//finalizer function - update the sync status to completed (or failed depending on the error status)
|
//finalizer function - update the sync status to completed (or failed depending on the error status)
|
||||||
|
@ -124,10 +167,11 @@ func BackgroundJobSyncResources(parentContext context.Context, logger *logrus.En
|
||||||
}()
|
}()
|
||||||
// END FINALIZER
|
// END FINALIZER
|
||||||
|
|
||||||
summary, err := sourceClient.SyncAll(databaseRepo)
|
var sourceClient sourceModels.SourceClient
|
||||||
if err != nil {
|
var summary sourceModels.UpsertSummary
|
||||||
resultErr = fmt.Errorf("an error occurred while bulk importing resources from source: %w", err)
|
sourceClient, summary, resultErr = callbackFn(backgroundJobContext, logger, databaseRepo, sourceCred)
|
||||||
logger.Errorln(resultErr)
|
if resultErr != nil {
|
||||||
|
logger.Errorln("An error occurred while syncing resources, ignoring", resultErr)
|
||||||
return summary, resultErr
|
return summary, resultErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ func GetResourceFhir(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": wrappedResourceModel})
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": wrappedResourceModel})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated - using Manual Resource Wizard instead
|
||||||
func CreateResourceComposition(c *gin.Context) {
|
func CreateResourceComposition(c *gin.Context) {
|
||||||
|
|
||||||
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fastenhealth/fasten-onprem/backend/pkg"
|
||||||
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
|
||||||
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/event_bus"
|
||||||
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
|
||||||
|
"github.com/fastenhealth/fasten-sources/clients/factory"
|
||||||
|
sourceModels "github.com/fastenhealth/fasten-sources/clients/models"
|
||||||
|
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mimics functionality in CreateManualSource
|
||||||
|
func CreateRelatedResources(c *gin.Context) {
|
||||||
|
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
||||||
|
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
|
||||||
|
eventBus := c.MustGet(pkg.ContextKeyTypeEventBusServer).(event_bus.Interface)
|
||||||
|
|
||||||
|
// store the bundle file locally
|
||||||
|
bundleFile, err := storeFileLocally(c)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//step 2: find a reference to the Fasten source for this user
|
||||||
|
sourceCredentials, err := databaseRepo.GetSources(c)
|
||||||
|
var fastenSourceCredential *models.SourceCredential
|
||||||
|
for _, sourceCredential := range sourceCredentials {
|
||||||
|
if sourceCredential.SourceType == sourcePkg.SourceTypeFasten {
|
||||||
|
fastenSourceCredential = &sourceCredential
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fastenSourceCredential == nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not find Fasten source for this user"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := BackgroundJobSyncResourcesWrapper(
|
||||||
|
c,
|
||||||
|
logger,
|
||||||
|
databaseRepo,
|
||||||
|
fastenSourceCredential,
|
||||||
|
func(
|
||||||
|
_backgroundJobContext context.Context,
|
||||||
|
_logger *logrus.Entry,
|
||||||
|
_databaseRepo database.DatabaseRepository,
|
||||||
|
_sourceCred *models.SourceCredential,
|
||||||
|
) (sourceModels.SourceClient, sourceModels.UpsertSummary, error) {
|
||||||
|
|
||||||
|
//step 3: create a "fasten" client, which we can use to parse resources to add to the database
|
||||||
|
fastenSourceClient, err := factory.GetSourceClient(sourcePkg.GetFastenLighthouseEnv(), sourcePkg.SourceTypeFasten, _backgroundJobContext, _logger, _sourceCred)
|
||||||
|
if err != nil {
|
||||||
|
resultErr := fmt.Errorf("could not create Fasten source client")
|
||||||
|
_logger.Errorln(resultErr)
|
||||||
|
return fastenSourceClient, sourceModels.UpsertSummary{}, resultErr
|
||||||
|
}
|
||||||
|
|
||||||
|
//step 4: parse the resources from the bundle
|
||||||
|
summary, err := fastenSourceClient.SyncAllBundle(_databaseRepo, bundleFile, sourcePkg.FhirVersion401)
|
||||||
|
if err != nil {
|
||||||
|
resultErr := fmt.Errorf("an error occurred while processing bundle: %v", err)
|
||||||
|
_logger.Errorln(resultErr)
|
||||||
|
return fastenSourceClient, summary, resultErr
|
||||||
|
}
|
||||||
|
return fastenSourceClient, summary, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//step 7 notify the event bus of the new resources
|
||||||
|
currentUser, _ := databaseRepo.GetCurrentUser(c)
|
||||||
|
err = eventBus.PublishMessage(
|
||||||
|
models.NewEventSourceComplete(
|
||||||
|
currentUser.ID.String(),
|
||||||
|
fastenSourceCredential.ID.String(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("ignoring: an error occurred while publishing sync complete event: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": summary, "source": fastenSourceCredential})
|
||||||
|
|
||||||
|
}
|
|
@ -1,18 +1,21 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fastenhealth/fasten-onprem/backend/pkg"
|
"github.com/fastenhealth/fasten-onprem/backend/pkg"
|
||||||
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
|
||||||
"github.com/fastenhealth/fasten-onprem/backend/pkg/event_bus"
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/event_bus"
|
||||||
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
|
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
|
||||||
"github.com/fastenhealth/fasten-sources/clients/factory"
|
"github.com/fastenhealth/fasten-sources/clients/factory"
|
||||||
|
sourceModels "github.com/fastenhealth/fasten-sources/clients/models"
|
||||||
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateReconnectSource(c *gin.Context) {
|
func CreateReconnectSource(c *gin.Context) {
|
||||||
|
@ -122,30 +125,17 @@ func SourceSync(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true, "source": sourceCred, "data": summary})
|
c.JSON(http.StatusOK, gin.H{"success": true, "source": sourceCred, "data": summary})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mimics functionality in CreateRelatedResources
|
||||||
|
// mimics functionality in SourceSync
|
||||||
func CreateManualSource(c *gin.Context) {
|
func CreateManualSource(c *gin.Context) {
|
||||||
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
||||||
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
|
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
|
||||||
eventBus := c.MustGet(pkg.ContextKeyTypeEventBusServer).(event_bus.Interface)
|
eventBus := c.MustGet(pkg.ContextKeyTypeEventBusServer).(event_bus.Interface)
|
||||||
|
|
||||||
// single file
|
// store the bundle file locally
|
||||||
file, err := c.FormFile("file")
|
bundleFile, err := storeFileLocally(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not extract file from form"})
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Uploaded filename: %s", file.Filename)
|
|
||||||
|
|
||||||
// create a temporary file to store this uploaded file
|
|
||||||
bundleFile, err := ioutil.TempFile("", file.Filename)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not create temp file"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload the file to specific bundleFile.
|
|
||||||
err = c.SaveUploadedFile(file, bundleFile.Name())
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not save temp file"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,23 +168,40 @@ func CreateManualSource(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
manualSourceClient, err := factory.GetSourceClient(sourcePkg.GetFastenLighthouseEnv(), sourcePkg.SourceTypeManual, c, logger, &manualSourceCredential)
|
summary, err := BackgroundJobSyncResourcesWrapper(
|
||||||
|
c,
|
||||||
|
logger,
|
||||||
|
databaseRepo,
|
||||||
|
&manualSourceCredential,
|
||||||
|
func(
|
||||||
|
_backgroundJobContext context.Context,
|
||||||
|
_logger *logrus.Entry,
|
||||||
|
_databaseRepo database.DatabaseRepository,
|
||||||
|
_sourceCred *models.SourceCredential,
|
||||||
|
) (sourceModels.SourceClient, sourceModels.UpsertSummary, error) {
|
||||||
|
manualSourceClient, err := factory.GetSourceClient(sourcePkg.GetFastenLighthouseEnv(), sourcePkg.SourceTypeManual, _backgroundJobContext, _logger, _sourceCred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorln("An error occurred while initializing hub client using manual source with credential", err)
|
resultErr := fmt.Errorf("an error occurred while initializing hub client using manual source with credential: %w", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
logger.Errorln(resultErr)
|
||||||
return
|
return manualSourceClient, sourceModels.UpsertSummary{}, resultErr
|
||||||
}
|
}
|
||||||
|
|
||||||
summary, err := manualSourceClient.SyncAllBundle(databaseRepo, bundleFile, bundleType)
|
summary, err := manualSourceClient.SyncAllBundle(_databaseRepo, bundleFile, bundleType)
|
||||||
|
if err != nil {
|
||||||
|
resultErr := fmt.Errorf("an error occurred while processing bundle: %w", err)
|
||||||
|
logger.Errorln(resultErr)
|
||||||
|
return manualSourceClient, sourceModels.UpsertSummary{}, resultErr
|
||||||
|
}
|
||||||
|
return manualSourceClient, summary, nil
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorln("An error occurred while processing bundle", err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//publish event
|
//publish event
|
||||||
currentUser, _ := databaseRepo.GetCurrentUser(c)
|
currentUser, _ := databaseRepo.GetCurrentUser(c)
|
||||||
|
|
||||||
err = eventBus.PublishMessage(
|
err = eventBus.PublishMessage(
|
||||||
models.NewEventSourceComplete(
|
models.NewEventSourceComplete(
|
||||||
currentUser.ID.String(),
|
currentUser.ID.String(),
|
||||||
|
@ -260,3 +267,29 @@ func DeleteSource(c *gin.Context) {
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": rowsEffected})
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": rowsEffected})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
func storeFileLocally(c *gin.Context) (*os.File, error) {
|
||||||
|
// single file
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
//c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not extract file from form"})
|
||||||
|
return nil, fmt.Errorf("could not extract file from form")
|
||||||
|
}
|
||||||
|
fmt.Printf("Uploaded filename: %s", file.Filename)
|
||||||
|
|
||||||
|
// create a temporary file to store this uploaded file
|
||||||
|
bundleFile, err := ioutil.TempFile("", file.Filename)
|
||||||
|
if err != nil {
|
||||||
|
//c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not create temp file"})
|
||||||
|
return nil, fmt.Errorf("could not create temp file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload the file to specific bundleFile.
|
||||||
|
err = c.SaveUploadedFile(file, bundleFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
//c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not save temp file"})
|
||||||
|
return nil, fmt.Errorf("could not save temp file")
|
||||||
|
}
|
||||||
|
return bundleFile, nil
|
||||||
|
}
|
||||||
|
|
|
@ -74,7 +74,9 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
|
||||||
secure.GET("/resource/fhir", handler.ListResourceFhir)
|
secure.GET("/resource/fhir", handler.ListResourceFhir)
|
||||||
secure.POST("/resource/graph/:graphType", handler.GetResourceFhirGraph)
|
secure.POST("/resource/graph/:graphType", handler.GetResourceFhirGraph)
|
||||||
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
|
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
|
||||||
|
|
||||||
secure.POST("/resource/composition", handler.CreateResourceComposition)
|
secure.POST("/resource/composition", handler.CreateResourceComposition)
|
||||||
|
secure.POST("/resource/related", handler.CreateRelatedResources)
|
||||||
|
|
||||||
secure.GET("/dashboards", handler.GetDashboard)
|
secure.GET("/dashboards", handler.GetDashboard)
|
||||||
secure.POST("/dashboards", handler.AddDashboardLocation)
|
secure.POST("/dashboards", handler.AddDashboardLocation)
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
"ngx-highlightjs": "^7.0.1",
|
"ngx-highlightjs": "^7.0.1",
|
||||||
"ngx-infinite-scroll": "^14.0.0",
|
"ngx-infinite-scroll": "^14.0.0",
|
||||||
"ngx-moment": "^6.0.2",
|
"ngx-moment": "^6.0.2",
|
||||||
|
"parse-full-name": "^1.2.6",
|
||||||
"rxjs": "~6.5.4",
|
"rxjs": "~6.5.4",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||||
import {NavigationEnd, Router} from '@angular/router';
|
import {NavigationEnd, Router} from '@angular/router';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {ToastService} from './services/toast.service';
|
import {ToastService} from './services/toast.service';
|
||||||
|
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
@ -15,7 +16,11 @@ export class AppComponent implements OnInit {
|
||||||
showHeader:boolean = false;
|
showHeader:boolean = false;
|
||||||
showFooter:boolean = true;
|
showFooter:boolean = true;
|
||||||
|
|
||||||
constructor(private router: Router, private toastService: ToastService) {}
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private modalService: NgbModal
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
|
@ -25,17 +30,21 @@ export class AppComponent implements OnInit {
|
||||||
document.querySelector('body').appendChild(navbarBackdrop);
|
document.querySelector('body').appendChild(navbarBackdrop);
|
||||||
|
|
||||||
//determine if we should show the header
|
//determine if we should show the header
|
||||||
this.router.events.subscribe(event => this.modifyHeader(event));
|
this.router.events.subscribe(event => this.routerEvent(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyHeader(event) {
|
routerEvent(event) {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
|
//modify header
|
||||||
if (event.url?.startsWith('/auth') || event.url?.startsWith('/desktop')) {
|
if (event.url?.startsWith('/auth') || event.url?.startsWith('/desktop')) {
|
||||||
this.showHeader = false;
|
this.showHeader = false;
|
||||||
} else {
|
} else {
|
||||||
// console.log("NU")
|
// console.log("NU")
|
||||||
this.showHeader = true;
|
this.showHeader = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close all open modals when route change
|
||||||
|
this.modalService.dismissAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ import { ExploreComponent } from './pages/explore/explore.component';
|
||||||
import {DirectivesModule} from './directives/directives.module';
|
import {DirectivesModule} from './directives/directives.module';
|
||||||
import { DesktopCallbackComponent } from './pages/desktop-callback/desktop-callback.component';
|
import { DesktopCallbackComponent } from './pages/desktop-callback/desktop-callback.component';
|
||||||
import { BackgroundJobsComponent } from './pages/background-jobs/background-jobs.component';
|
import { BackgroundJobsComponent } from './pages/background-jobs/background-jobs.component';
|
||||||
|
import {FhirCardModule} from './components/fhir-card/fhir-card.module';
|
||||||
|
import {FhirDatatableModule} from './components/fhir-datatable/fhir-datatable.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -64,6 +66,8 @@ import { BackgroundJobsComponent } from './pages/background-jobs/background-jobs
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
FhirCardModule,
|
||||||
|
FhirDatatableModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {BadgeComponent} from "./badge.component";
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<BadgeComponent> = {
|
const meta: Meta<BadgeComponent> = {
|
||||||
title: 'Fhir/Common/Badge',
|
title: 'Fhir Card/Common/Badge',
|
||||||
component: BadgeComponent,
|
component: BadgeComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -5,7 +5,7 @@ import {FastenDisplayModel} from "../../../../../lib/models/fasten/fasten-displa
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<TableComponent> = {
|
const meta: Meta<TableComponent> = {
|
||||||
title: 'Fhir/Common/Table',
|
title: 'Fhir Card/Common/Table',
|
||||||
component: TableComponent,
|
component: TableComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -8,7 +8,7 @@ import {BinaryModel} from "../../../../../lib/models/resources/binary-model";
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<BinaryTextComponent> = {
|
const meta: Meta<BinaryTextComponent> = {
|
||||||
title: 'Fhir/Datatypes/BinaryText',
|
title: 'Fhir Card/Datatypes/BinaryText',
|
||||||
component: BinaryTextComponent,
|
component: BinaryTextComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -6,7 +6,7 @@ import {DicomComponent} from "./dicom.component";
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<DicomComponent> = {
|
const meta: Meta<DicomComponent> = {
|
||||||
title: 'Fhir/Datatypes/Dicom',
|
title: 'Fhir Card/Datatypes/Dicom',
|
||||||
component: DicomComponent,
|
component: DicomComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -8,7 +8,7 @@ import {HtmlComponent} from "./html.component";
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<HtmlComponent> = {
|
const meta: Meta<HtmlComponent> = {
|
||||||
title: 'Fhir/Datatypes/Html',
|
title: 'Fhir Card/Datatypes/Html',
|
||||||
component: HtmlComponent,
|
component: HtmlComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -6,7 +6,7 @@ import {ImgComponent} from "./img.component";
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<ImgComponent> = {
|
const meta: Meta<ImgComponent> = {
|
||||||
title: 'Fhir/Datatypes/Img',
|
title: 'Fhir Card/Datatypes/Img',
|
||||||
component: ImgComponent,
|
component: ImgComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/angular';
|
||||||
|
import {fhirVersions} from "../../../../../lib/models/constants";
|
||||||
|
import R4Example1Json from "../../../../../lib/fixtures/r4/resources/binary/exampleMarkdown.json";
|
||||||
|
import {BinaryModel} from "../../../../../lib/models/resources/binary-model";
|
||||||
|
import {MarkdownComponent} from "./markdown.component";
|
||||||
|
|
||||||
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
|
const meta: Meta<MarkdownComponent> = {
|
||||||
|
title: 'Fhir Card/Datatypes/Markdown',
|
||||||
|
component: MarkdownComponent,
|
||||||
|
decorators: [
|
||||||
|
// moduleMetadata({
|
||||||
|
// imports: [AppModule]
|
||||||
|
// })
|
||||||
|
// applicationConfig({
|
||||||
|
// providers: [importProvidersFrom(AppModule)],
|
||||||
|
// }),
|
||||||
|
],
|
||||||
|
tags: ['autodocs'],
|
||||||
|
render: (args: MarkdownComponent) => ({
|
||||||
|
props: {
|
||||||
|
backgroundColor: null,
|
||||||
|
...args,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
argTypes: {
|
||||||
|
displayModel: {
|
||||||
|
control: 'object',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<MarkdownComponent>;
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
let aiDisplayModel1 = new BinaryModel(R4Example1Json, fhirVersions.R4)
|
||||||
|
aiDisplayModel1.source_id = '123-456-789'
|
||||||
|
aiDisplayModel1.source_resource_id = '123-456-789'
|
||||||
|
export const R4Example1: Story = {
|
||||||
|
args: {
|
||||||
|
displayModel: aiDisplayModel1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {PdfComponent} from "./pdf.component";
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<PdfComponent> = {
|
const meta: Meta<PdfComponent> = {
|
||||||
title: 'Fhir/Datatypes/Pdf',
|
title: 'Fhir Card/Datatypes/Pdf',
|
||||||
component: PdfComponent,
|
component: PdfComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {BadgeComponent} from './common/badge/badge.component';
|
||||||
|
import {TableComponent} from './common/table/table.component';
|
||||||
|
import {BinaryTextComponent} from './datatypes/binary-text/binary-text.component';
|
||||||
|
import {CodableConceptComponent} from './datatypes/codable-concept/codable-concept.component';
|
||||||
|
import {CodingComponent} from './datatypes/coding/coding.component';
|
||||||
|
import {DicomComponent} from './datatypes/dicom/dicom.component';
|
||||||
|
import {HtmlComponent} from './datatypes/html/html.component';
|
||||||
|
import {ImgComponent} from './datatypes/img/img.component';
|
||||||
|
import {MarkdownComponent} from './datatypes/markdown/markdown.component';
|
||||||
|
import {PdfComponent} from './datatypes/pdf/pdf.component';
|
||||||
|
import {AllergyIntoleranceComponent} from './resources/allergy-intolerance/allergy-intolerance.component';
|
||||||
|
import {BinaryComponent} from './resources/binary/binary.component';
|
||||||
|
import {DiagnosticReportComponent} from './resources/diagnostic-report/diagnostic-report.component';
|
||||||
|
import {DocumentReferenceComponent} from './resources/document-reference/document-reference.component';
|
||||||
|
import {FallbackComponent} from './resources/fallback/fallback.component';
|
||||||
|
import {ImmunizationComponent} from './resources/immunization/immunization.component';
|
||||||
|
import {LocationComponent} from './resources/location/location.component';
|
||||||
|
import {MediaComponent} from './resources/media/media.component';
|
||||||
|
import {MedicationComponent} from './resources/medication/medication.component';
|
||||||
|
import {MedicationRequestComponent} from './resources/medication-request/medication-request.component';
|
||||||
|
import {ObservationComponent} from './resources/observation/observation.component';
|
||||||
|
import {OrganizationComponent} from './resources/organization/organization.component';
|
||||||
|
import {PractitionerComponent} from './resources/practitioner/practitioner.component';
|
||||||
|
import {ProcedureComponent} from './resources/procedure/procedure.component';
|
||||||
|
import {FhirCardComponent} from './fhir-card/fhir-card.component';
|
||||||
|
import {FhirCardOutletDirective} from './fhir-card/fhir-card-outlet.directive';
|
||||||
|
import { EncounterComponent } from './resources/encounter/encounter.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
//common
|
||||||
|
CommonModule,
|
||||||
|
BadgeComponent,
|
||||||
|
//datatypes
|
||||||
|
TableComponent,
|
||||||
|
BinaryTextComponent,
|
||||||
|
CodableConceptComponent,
|
||||||
|
CodingComponent,
|
||||||
|
DicomComponent,
|
||||||
|
HtmlComponent,
|
||||||
|
ImgComponent,
|
||||||
|
MarkdownComponent,
|
||||||
|
PdfComponent,
|
||||||
|
//resources
|
||||||
|
AllergyIntoleranceComponent,
|
||||||
|
BinaryComponent,
|
||||||
|
DiagnosticReportComponent,
|
||||||
|
DocumentReferenceComponent,
|
||||||
|
EncounterComponent,
|
||||||
|
FallbackComponent,
|
||||||
|
ImmunizationComponent,
|
||||||
|
LocationComponent,
|
||||||
|
MediaComponent,
|
||||||
|
MedicationComponent,
|
||||||
|
MedicationRequestComponent,
|
||||||
|
ObservationComponent,
|
||||||
|
OrganizationComponent,
|
||||||
|
PractitionerComponent,
|
||||||
|
ProcedureComponent,
|
||||||
|
|
||||||
|
],
|
||||||
|
//TODO: every component in here should be migrated to a standalone component
|
||||||
|
declarations: [
|
||||||
|
FhirCardComponent,
|
||||||
|
FhirCardOutletDirective,
|
||||||
|
|
||||||
|
],
|
||||||
|
exports:[
|
||||||
|
//common
|
||||||
|
BadgeComponent,
|
||||||
|
TableComponent,
|
||||||
|
//datatypes
|
||||||
|
BinaryTextComponent,
|
||||||
|
CodableConceptComponent,
|
||||||
|
CodingComponent,
|
||||||
|
DicomComponent,
|
||||||
|
HtmlComponent,
|
||||||
|
ImgComponent,
|
||||||
|
MarkdownComponent,
|
||||||
|
PdfComponent,
|
||||||
|
//resources
|
||||||
|
AllergyIntoleranceComponent,
|
||||||
|
BinaryComponent,
|
||||||
|
DiagnosticReportComponent,
|
||||||
|
DocumentReferenceComponent,
|
||||||
|
EncounterComponent,
|
||||||
|
FallbackComponent,
|
||||||
|
ImmunizationComponent,
|
||||||
|
LocationComponent,
|
||||||
|
MediaComponent,
|
||||||
|
MedicationComponent,
|
||||||
|
MedicationRequestComponent,
|
||||||
|
ObservationComponent,
|
||||||
|
OrganizationComponent,
|
||||||
|
PractitionerComponent,
|
||||||
|
ProcedureComponent,
|
||||||
|
|
||||||
|
FhirCardComponent,
|
||||||
|
FhirCardOutletDirective,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class FhirCardModule { }
|
|
@ -1,9 +1,10 @@
|
||||||
import {FastenDisplayModel} from '../../../../lib/models/fasten/fasten-display-model';
|
import {FastenDisplayModel} from '../../../../lib/models/fasten/fasten-display-model';
|
||||||
|
|
||||||
//all Fhir Resource components must implement this Interface
|
//all Fhir Resource components must implement this Interface
|
||||||
export interface FhirResourceComponentInterface {
|
export interface FhirCardComponentInterface {
|
||||||
displayModel: FastenDisplayModel;
|
displayModel: FastenDisplayModel;
|
||||||
showDetails: boolean;
|
showDetails: boolean;
|
||||||
|
isCollapsed: boolean;
|
||||||
|
|
||||||
//these are used to populate the description of the resource. May not be available for all resources
|
//these are used to populate the description of the resource. May not be available for all resources
|
||||||
resourceCode?: string;
|
resourceCode?: string;
|
|
@ -1,4 +1,4 @@
|
||||||
import { FhirResourceOutletDirective } from './fhir-resource-outlet.directive';
|
import { FhirCardOutletDirective } from './fhir-card-outlet.directive';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ComponentFactory, ComponentRef,
|
ComponentFactory, ComponentRef,
|
||||||
|
@ -66,11 +66,11 @@ class TestViewContainerRef extends ViewContainerRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
describe('FhirResourceOutletDirective', () => {
|
describe('FhirCardOutletDirective', () => {
|
||||||
|
|
||||||
|
|
||||||
it('should create an instance', () => {
|
it('should create an instance', () => {
|
||||||
const directive = new FhirResourceOutletDirective(new TestViewContainerRef());
|
const directive = new FhirCardOutletDirective(new TestViewContainerRef());
|
||||||
expect(directive).toBeTruthy();
|
expect(directive).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,9 +1,9 @@
|
||||||
import {Directive, ViewContainerRef} from '@angular/core';
|
import {Directive, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[resourceListOutlet]'
|
selector: '[fhirCardOutlet]'
|
||||||
})
|
})
|
||||||
export class ResourceListOutletDirective {
|
export class FhirCardOutletDirective {
|
||||||
|
|
||||||
constructor(public viewContainerRef: ViewContainerRef) { }
|
constructor(public viewContainerRef: ViewContainerRef) { }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<ng-template fhirCardOutlet></ng-template>
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FhirCardComponent } from './fhir-card.component';
|
||||||
|
import {FhirCardOutletDirective} from './fhir-card-outlet.directive';
|
||||||
|
|
||||||
|
describe('FhirResourceComponent', () => {
|
||||||
|
let component: FhirCardComponent;
|
||||||
|
let fixture: ComponentFixture<FhirCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ FhirCardComponent, FhirCardOutletDirective ],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(FhirCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,12 +9,12 @@ import {
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {BinaryModel} from '../../../../lib/models/resources/binary-model';
|
import {BinaryModel} from '../../../../lib/models/resources/binary-model';
|
||||||
import {FhirResourceOutletDirective} from './fhir-resource-outlet.directive';
|
import {FhirCardOutletDirective} from './fhir-card-outlet.directive';
|
||||||
|
|
||||||
import {ResourceType} from '../../../../lib/models/constants';
|
import {ResourceType} from '../../../../lib/models/constants';
|
||||||
import {FallbackComponent} from '../resources/fallback/fallback.component';
|
import {FallbackComponent} from '../resources/fallback/fallback.component';
|
||||||
import {BinaryComponent} from '../resources/binary/binary.component';
|
import {BinaryComponent} from '../resources/binary/binary.component';
|
||||||
import {FhirResourceComponentInterface} from './fhir-resource-component-interface';
|
import {FhirCardComponentInterface} from './fhir-card-component-interface';
|
||||||
import {ImmunizationComponent} from '../resources/immunization/immunization.component';
|
import {ImmunizationComponent} from '../resources/immunization/immunization.component';
|
||||||
import {AllergyIntoleranceComponent} from '../resources/allergy-intolerance/allergy-intolerance.component';
|
import {AllergyIntoleranceComponent} from '../resources/allergy-intolerance/allergy-intolerance.component';
|
||||||
import {MedicationComponent} from '../resources/medication/medication.component';
|
import {MedicationComponent} from '../resources/medication/medication.component';
|
||||||
|
@ -28,21 +28,23 @@ import {MediaComponent} from '../resources/media/media.component';
|
||||||
import {LocationComponent} from '../resources/location/location.component';
|
import {LocationComponent} from '../resources/location/location.component';
|
||||||
import {OrganizationComponent} from '../resources/organization/organization.component';
|
import {OrganizationComponent} from '../resources/organization/organization.component';
|
||||||
import {ObservationComponent} from '../resources/observation/observation.component';
|
import {ObservationComponent} from '../resources/observation/observation.component';
|
||||||
|
import {EncounterComponent} from '../resources/encounter/encounter.component';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'fhir-resource',
|
selector: 'fhir-card',
|
||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
templateUrl: './fhir-resource.component.html',
|
templateUrl: './fhir-card.component.html',
|
||||||
styleUrls: ['./fhir-resource.component.scss']
|
styleUrls: ['./fhir-card.component.scss']
|
||||||
})
|
})
|
||||||
export class FhirResourceComponent implements OnInit, OnChanges {
|
export class FhirCardComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
@Input() displayModel: FastenDisplayModel
|
@Input() displayModel: FastenDisplayModel
|
||||||
@Input() showDetails: boolean = true
|
@Input() showDetails: boolean = true
|
||||||
|
@Input() isCollapsed: boolean = false
|
||||||
|
|
||||||
//location to dynamically load the displayModel
|
//location to dynamically load the displayModel
|
||||||
@ViewChild(FhirResourceOutletDirective, {static: true}) fhirResourceOutlet!: FhirResourceOutletDirective;
|
@ViewChild(FhirCardOutletDirective, {static: true}) fhirCardOutlet!: FhirCardOutletDirective;
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
|
@ -55,21 +57,22 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
loadComponent() {
|
loadComponent() {
|
||||||
//clear the current outlet
|
//clear the current outlet
|
||||||
const viewContainerRef = this.fhirResourceOutlet.viewContainerRef;
|
const viewContainerRef = this.fhirCardOutlet.viewContainerRef;
|
||||||
viewContainerRef.clear();
|
viewContainerRef.clear();
|
||||||
|
|
||||||
let componentType = this.typeLookup(this.displayModel?.source_resource_type)
|
let componentType = this.typeLookup(this.displayModel?.source_resource_type)
|
||||||
if(componentType != null){
|
if(componentType != null){
|
||||||
console.log("Attempting to create fhir display component", this.displayModel, componentType)
|
console.log("Attempting to create fhir display component", this.displayModel, componentType)
|
||||||
const componentRef = viewContainerRef.createComponent<FhirResourceComponentInterface>(componentType);
|
const componentRef = viewContainerRef.createComponent<FhirCardComponentInterface>(componentType);
|
||||||
componentRef.instance.displayModel = this.displayModel;
|
componentRef.instance.displayModel = this.displayModel;
|
||||||
componentRef.instance.showDetails = this.showDetails;
|
componentRef.instance.showDetails = this.showDetails;
|
||||||
|
componentRef.instance.isCollapsed = this.isCollapsed;
|
||||||
componentRef.instance.markForCheck()
|
componentRef.instance.markForCheck()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typeLookup(resourceType: ResourceType): Type<FhirResourceComponentInterface> {
|
typeLookup(resourceType: ResourceType): Type<FhirCardComponentInterface> {
|
||||||
if(!resourceType){
|
if(!resourceType){
|
||||||
//dont try to render anything if the resourceType isnt set.
|
//dont try to render anything if the resourceType isnt set.
|
||||||
return null
|
return null
|
||||||
|
@ -114,9 +117,9 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
||||||
case "DocumentReference": {
|
case "DocumentReference": {
|
||||||
return DocumentReferenceComponent;
|
return DocumentReferenceComponent;
|
||||||
}
|
}
|
||||||
// case "Encounter": {
|
case "Encounter": {
|
||||||
// return ListEncounterComponent;
|
return EncounterComponent;
|
||||||
// }
|
}
|
||||||
// case "Goal": {
|
// case "Goal": {
|
||||||
// return ListGoalComponent;
|
// return ListGoalComponent;
|
||||||
// }
|
// }
|
|
@ -1,5 +1,5 @@
|
||||||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
|
||||||
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
||||||
import {Router, RouterModule} from '@angular/router';
|
import {Router, RouterModule} from '@angular/router';
|
||||||
import {AllergyIntoleranceModel} from '../../../../../lib/models/resources/allergy-intolerance-model';
|
import {AllergyIntoleranceModel} from '../../../../../lib/models/resources/allergy-intolerance-model';
|
||||||
|
@ -15,11 +15,10 @@ import {TableComponent} from "../../common/table/table.component";
|
||||||
templateUrl: './allergy-intolerance.component.html',
|
templateUrl: './allergy-intolerance.component.html',
|
||||||
styleUrls: ['./allergy-intolerance.component.scss']
|
styleUrls: ['./allergy-intolerance.component.scss']
|
||||||
})
|
})
|
||||||
export class AllergyIntoleranceComponent implements OnInit, FhirResourceComponentInterface {
|
export class AllergyIntoleranceComponent implements OnInit, FhirCardComponentInterface {
|
||||||
@Input() displayModel: AllergyIntoleranceModel
|
@Input() displayModel: AllergyIntoleranceModel
|
||||||
@Input() showDetails: boolean = true
|
@Input() showDetails: boolean = true
|
||||||
|
@Input() isCollapsed: boolean = false
|
||||||
isCollapsed: boolean = false
|
|
||||||
|
|
||||||
tableData: TableRowItem[] = []
|
tableData: TableRowItem[] = []
|
||||||
|
|
|
@ -10,7 +10,7 @@ import R4Example3Json from "../../../../../lib/fixtures/r4/resources/allergyInto
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<AllergyIntoleranceComponent> = {
|
const meta: Meta<AllergyIntoleranceComponent> = {
|
||||||
title: 'Fhir/AllergyIntolerance',
|
title: 'Fhir Card/AllergyIntolerance',
|
||||||
component: AllergyIntoleranceComponent,
|
component: AllergyIntoleranceComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
// moduleMetadata({
|
// moduleMetadata({
|
||||||
|
@ -51,8 +51,8 @@ export const R4Example1: Story = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let aiDisplayModel2 = new AllergyIntoleranceModel(R4Example2Json, fhirVersions.R4)
|
let aiDisplayModel2 = new AllergyIntoleranceModel(R4Example2Json, fhirVersions.R4)
|
||||||
aiDisplayModel1.source_id = '123-456-789'
|
aiDisplayModel2.source_id = '123-456-789'
|
||||||
aiDisplayModel1.source_resource_id = '123-456-789'
|
aiDisplayModel2.source_resource_id = '123-456-789'
|
||||||
export const R4Example2: Story = {
|
export const R4Example2: Story = {
|
||||||
args: {
|
args: {
|
||||||
displayModel: aiDisplayModel2
|
displayModel: aiDisplayModel2
|
||||||
|
@ -60,8 +60,8 @@ export const R4Example2: Story = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let aiDisplayModel3 = new AllergyIntoleranceModel(R4Example3Json, fhirVersions.R4)
|
let aiDisplayModel3 = new AllergyIntoleranceModel(R4Example3Json, fhirVersions.R4)
|
||||||
aiDisplayModel1.source_id = '123-456-789'
|
aiDisplayModel3.source_id = '123-456-789'
|
||||||
aiDisplayModel1.source_resource_id = '123-456-789'
|
aiDisplayModel3.source_resource_id = '123-456-789'
|
||||||
export const R4Example3: Story = {
|
export const R4Example3: Story = {
|
||||||
args: {
|
args: {
|
||||||
displayModel: aiDisplayModel3
|
displayModel: aiDisplayModel3
|
|
@ -1,6 +1,6 @@
|
||||||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||||
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
||||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
|
||||||
import {Router, RouterModule} from '@angular/router';
|
import {Router, RouterModule} from '@angular/router';
|
||||||
import {AttachmentModel} from '../../../../../lib/models/datatypes/attachment-model';
|
import {AttachmentModel} from '../../../../../lib/models/datatypes/attachment-model';
|
||||||
import {FastenApiService} from '../../../../services/fasten-api.service';
|
import {FastenApiService} from '../../../../services/fasten-api.service';
|
||||||
|
@ -37,11 +37,12 @@ import {AuthService} from "../../../../services/auth.service";
|
||||||
templateUrl: './binary.component.html',
|
templateUrl: './binary.component.html',
|
||||||
styleUrls: ['./binary.component.scss']
|
styleUrls: ['./binary.component.scss']
|
||||||
})
|
})
|
||||||
export class BinaryComponent implements OnInit, FhirResourceComponentInterface {
|
export class BinaryComponent implements OnInit, FhirCardComponentInterface {
|
||||||
@Input() displayModel: BinaryModel
|
@Input() displayModel: BinaryModel
|
||||||
@Input() showDetails: boolean = true
|
@Input() showDetails: boolean = true
|
||||||
@Input() attachmentSourceId: string
|
@Input() attachmentSourceId: string
|
||||||
@Input() attachmentModel: AttachmentModel //can only have attachmentModel or binaryModel, not both.
|
@Input() attachmentModel: AttachmentModel //can only have attachmentModel or binaryModel, not both.
|
||||||
|
@Input() isCollapsed: boolean = false
|
||||||
|
|
||||||
loading: boolean = false
|
loading: boolean = false
|
||||||
constructor(public changeRef: ChangeDetectorRef, public router: Router, public fastenApi: FastenApiService) {}
|
constructor(public changeRef: ChangeDetectorRef, public router: Router, public fastenApi: FastenApiService) {}
|
|
@ -18,7 +18,7 @@ import {HTTP_CLIENT_TOKEN} from '../../../../dependency-injection';
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
const meta: Meta<BinaryComponent> = {
|
const meta: Meta<BinaryComponent> = {
|
||||||
title: 'Fhir/Binary',
|
title: 'Fhir Card/Binary',
|
||||||
component: BinaryComponent,
|
component: BinaryComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
|
@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DiagnosticReportComponent } from './diagnostic-report.component';
|
import { DiagnosticReportComponent } from './diagnostic-report.component';
|
||||||
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
|
||||||
describe('DiagnosticReportComponent', () => {
|
describe('DiagnosticReportComponent', () => {
|
||||||
let component: DiagnosticReportComponent;
|
let component: DiagnosticReportComponent;
|
||||||
|
@ -9,8 +10,7 @@ describe('DiagnosticReportComponent', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ DiagnosticReportComponent ],
|
imports: [NgbCollapseModule, DiagnosticReportComponent, RouterTestingModule]
|
||||||
imports: [NgbCollapseModule]
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
|
@ -1,22 +1,31 @@
|
||||||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
|
||||||
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
||||||
import {Router} from '@angular/router';
|
import {Router, RouterModule} from '@angular/router';
|
||||||
import {DiagnosticReportModel} from '../../../../../lib/models/resources/diagnostic-report-model';
|
import {DiagnosticReportModel} from '../../../../../lib/models/resources/diagnostic-report-model';
|
||||||
|
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {BadgeComponent} from '../../common/badge/badge.component';
|
||||||
|
import {TableComponent} from '../../common/table/table.component';
|
||||||
|
import {BinaryComponent} from '../binary/binary.component';
|
||||||
|
import {GlossaryLookupComponent} from '../../../glossary-lookup/glossary-lookup.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-diagnostic-report',
|
standalone: true,
|
||||||
|
imports: [NgbCollapseModule, CommonModule, BadgeComponent, TableComponent, RouterModule, BinaryComponent, GlossaryLookupComponent],
|
||||||
|
selector: 'fhir-diagnostic-report',
|
||||||
templateUrl: './diagnostic-report.component.html',
|
templateUrl: './diagnostic-report.component.html',
|
||||||
styleUrls: ['./diagnostic-report.component.scss']
|
styleUrls: ['./diagnostic-report.component.scss']
|
||||||
})
|
})
|
||||||
export class DiagnosticReportComponent implements OnInit, FhirResourceComponentInterface {
|
export class DiagnosticReportComponent implements OnInit, FhirCardComponentInterface {
|
||||||
@Input() displayModel: DiagnosticReportModel
|
@Input() displayModel: DiagnosticReportModel
|
||||||
@Input() showDetails: boolean = true
|
@Input() showDetails: boolean = true
|
||||||
|
@Input() isCollapsed: boolean = false
|
||||||
|
|
||||||
//these are used to populate the description of the resource. May not be available for all resources
|
//these are used to populate the description of the resource. May not be available for all resources
|
||||||
resourceCode?: string;
|
resourceCode?: string;
|
||||||
resourceCodeSystem?: string;
|
resourceCodeSystem?: string;
|
||||||
|
|
||||||
isCollapsed: boolean = false
|
|
||||||
tableData: TableRowItem[] = []
|
tableData: TableRowItem[] = []
|
||||||
|
|
||||||
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
|
@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DocumentReferenceComponent } from './document-reference.component';
|
import { DocumentReferenceComponent } from './document-reference.component';
|
||||||
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
|
||||||
describe('DocumentReferenceComponent', () => {
|
describe('DocumentReferenceComponent', () => {
|
||||||
let component: DocumentReferenceComponent;
|
let component: DocumentReferenceComponent;
|
||||||
|
@ -9,8 +10,8 @@ describe('DocumentReferenceComponent', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [NgbCollapseModule],
|
imports: [NgbCollapseModule, DocumentReferenceComponent, RouterTestingModule],
|
||||||
declarations: [ DocumentReferenceComponent ]
|
declarations: [ ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||||
import {DiagnosticReportModel} from '../../../../../lib/models/resources/diagnostic-report-model';
|
import {DiagnosticReportModel} from '../../../../../lib/models/resources/diagnostic-report-model';
|
||||||
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
||||||
import {Router} from '@angular/router';
|
import {Router, RouterModule} from '@angular/router';
|
||||||
import {DocumentReferenceModel} from '../../../../../lib/models/resources/document-reference-model';
|
import {DocumentReferenceModel} from '../../../../../lib/models/resources/document-reference-model';
|
||||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
|
||||||
|
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {BadgeComponent} from '../../common/badge/badge.component';
|
||||||
|
import {TableComponent} from '../../common/table/table.component';
|
||||||
|
import {BinaryComponent} from '../binary/binary.component';
|
||||||
|
import {GlossaryLookupComponent} from '../../../glossary-lookup/glossary-lookup.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-reference',
|
standalone: true,
|
||||||
|
imports: [NgbCollapseModule, CommonModule, BadgeComponent, TableComponent, RouterModule, BinaryComponent],
|
||||||
|
selector: 'fhir-document-reference',
|
||||||
templateUrl: './document-reference.component.html',
|
templateUrl: './document-reference.component.html',
|
||||||
styleUrls: ['./document-reference.component.scss']
|
styleUrls: ['./document-reference.component.scss']
|
||||||
})
|
})
|
||||||
export class DocumentReferenceComponent implements OnInit, FhirResourceComponentInterface {
|
export class DocumentReferenceComponent implements OnInit, FhirCardComponentInterface {
|
||||||
@Input() displayModel: DocumentReferenceModel
|
@Input() displayModel: DocumentReferenceModel
|
||||||
@Input() showDetails: boolean = true
|
@Input() showDetails: boolean = true
|
||||||
isCollapsed: boolean = false
|
@Input() isCollapsed: boolean = false
|
||||||
tableData: TableRowItem[] = []
|
tableData: TableRowItem[] = []
|
||||||
|
|
||||||
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<div class="card card-fhir-resource">
|
||||||
|
<div class="card-header" (click)="isCollapsed = ! isCollapsed">
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">{{displayModel?.sort_title || displayModel?.code?.text}}</h6>
|
||||||
|
<p class="card-text tx-gray-400" *ngIf="displayModel?.period_start"><strong>Start date</strong> {{displayModel?.period_start | date}}</p>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="btn-group">-->
|
||||||
|
<!-- <button class="btn active">Day</button>-->
|
||||||
|
<!-- <button class="btn">Week</button>-->
|
||||||
|
<!-- <button class="btn">Month</button>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" class="card-body">
|
||||||
|
<fhir-ui-table [displayModel]="displayModel" [tableData]="tableData"></fhir-ui-table>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showDetails" class="card-footer">
|
||||||
|
<a class="float-right" routerLink="/explore/{{displayModel?.source_id}}/resource/{{displayModel?.source_resource_id}}">details</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EncounterComponent } from './encounter.component';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
|
||||||
|
describe('EncounterComponent', () => {
|
||||||
|
let component: EncounterComponent;
|
||||||
|
let fixture: ComponentFixture<EncounterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ EncounterComponent, RouterTestingModule ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(EncounterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,50 @@
|
||||||
|
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {BadgeComponent} from '../../common/badge/badge.component';
|
||||||
|
import {TableComponent} from '../../common/table/table.component';
|
||||||
|
import {GlossaryLookupComponent} from '../../../glossary-lookup/glossary-lookup.component';
|
||||||
|
import {Router, RouterModule} from '@angular/router';
|
||||||
|
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
|
||||||
|
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
|
||||||
|
import {EncounterModel} from '../../../../../lib/models/resources/encounter-model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgbCollapseModule, CommonModule, BadgeComponent, TableComponent, GlossaryLookupComponent, RouterModule],
|
||||||
|
selector: 'fhir-encounter',
|
||||||
|
templateUrl: './encounter.component.html',
|
||||||
|
styleUrls: ['./encounter.component.scss']
|
||||||
|
})
|
||||||
|
export class EncounterComponent implements OnInit, FhirCardComponentInterface {
|
||||||
|
@Input() displayModel: EncounterModel | null
|
||||||
|
@Input() showDetails: boolean = true
|
||||||
|
@Input() isCollapsed: boolean = false
|
||||||
|
|
||||||
|
//these are used to populate the description of the resource. May not be available for all resources
|
||||||
|
resourceCode?: string;
|
||||||
|
resourceCodeSystem?: string;
|
||||||
|
|
||||||
|
tableData: TableRowItem[] = []
|
||||||
|
|
||||||
|
constructor(public changeRef: ChangeDetectorRef, public router: Router) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.tableData = [
|
||||||
|
{
|
||||||
|
label: 'Type',
|
||||||
|
data: this.displayModel?.encounter_type?.[0],
|
||||||
|
data_type: TableRowItemDataType.CodableConcept,
|
||||||
|
enabled: !!this.displayModel?.encounter_type?.[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Location',
|
||||||
|
data: this.displayModel?.location_display,
|
||||||
|
enabled: !!this.displayModel?.location_display,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
markForCheck(){
|
||||||
|
this.changeRef.markForCheck()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/angular';
|
||||||
|
import {EncounterComponent} from "./encounter.component";
|
||||||
|
import {EncounterModel} from "../../../../../lib/models/resources/encounter-model";
|
||||||
|
import {fhirVersions} from "../../../../../lib/models/constants";
|
||||||
|
import R4Example1Json from "../../../../../lib/fixtures/r4/resources/encounter/example1.json";
|
||||||
|
import R4Example2Json from "../../../../../lib/fixtures/r4/resources/encounter/example2.json";
|
||||||
|
import R4Example3Json from "../../../../../lib/fixtures/r4/resources/encounter/example3.json";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
|
||||||
|
const meta: Meta<EncounterComponent> = {
|
||||||
|
title: 'Fhir Card/Encounter',
|
||||||
|
component: EncounterComponent,
|
||||||
|
decorators: [
|
||||||
|
// moduleMetadata({
|
||||||
|
// imports: [AppModule]
|
||||||
|
// })
|
||||||
|
// applicationConfig({
|
||||||
|
// providers: [importProvidersFrom(AppModule)],
|
||||||
|
// }),
|
||||||
|
],
|
||||||
|
tags: ['autodocs'],
|
||||||
|
render: (args: EncounterComponent) => ({
|
||||||
|
props: {
|
||||||
|
backgroundColor: null,
|
||||||
|
...args,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
argTypes: {
|
||||||
|
displayModel: {
|
||||||
|
control: 'object',
|
||||||
|
},
|
||||||
|
showDetails: {
|
||||||
|
control: 'boolean',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<EncounterComponent>;
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/angular/writing-stories/args
|
||||||
|
let encounterDisplayModel1 = new EncounterModel(R4Example1Json, fhirVersions.R4)
|
||||||
|
encounterDisplayModel1.source_id = '123-456-789'
|
||||||
|
encounterDisplayModel1.source_resource_id = '123-456-789'
|
||||||
|
export const R4Example1: Story = {
|
||||||
|
args: {
|
||||||
|
displayModel: encounterDisplayModel1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let encounterDisplayModel2 = new EncounterModel(R4Example2Json, fhirVersions.R4)
|
||||||
|
encounterDisplayModel2.source_id = '123-456-789'
|
||||||
|
encounterDisplayModel2.source_resource_id = '123-456-789'
|
||||||
|
export const R4Example2: Story = {
|
||||||
|
args: {
|
||||||
|
displayModel: encounterDisplayModel2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let encounterDisplayModel3 = new EncounterModel(R4Example3Json, fhirVersions.R4)
|
||||||
|
encounterDisplayModel3.source_id = '123-456-789'
|
||||||
|
encounterDisplayModel3.source_resource_id = '123-456-789'
|
||||||
|
export const R4Example3: Story = {
|
||||||
|
args: {
|
||||||
|
displayModel: encounterDisplayModel3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe('FallbackComponent', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ FallbackComponent ]
|
imports: [ FallbackComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||||
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import {FhirResourceComponentInterface} from '../../fhir-resource/fhir-resource-component-interface';
|
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
|
||||||
|
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {HighlightModule} from 'ngx-highlightjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgbCollapseModule, HighlightModule, CommonModule],
|
||||||
selector: 'fhir-fallback',
|
selector: 'fhir-fallback',
|
||||||
templateUrl: './fallback.component.html',
|
templateUrl: './fallback.component.html',
|
||||||
styleUrls: ['./fallback.component.scss']
|
styleUrls: ['./fallback.component.scss']
|
||||||
})
|
})
|
||||||
export class FallbackComponent implements OnInit, FhirResourceComponentInterface {
|
export class FallbackComponent implements OnInit, FhirCardComponentInterface {
|
||||||
@Input() displayModel: BinaryModel
|
@Input() displayModel: BinaryModel
|
||||||
@Input() showDetails: boolean = true
|
@Input() showDetails: boolean = true
|
||||||
|
@Input() isCollapsed: boolean = false
|
||||||
|
|
||||||
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue