Timeline based Manual Entry form (#330)
This commit is contained in:
parent
7a77dcdcd6
commit
d23af018e7
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -39,6 +40,7 @@ func (gr *GormRepository) Close() error {
|
|||
// User
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// <editor-fold desc="User">
|
||||
func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) error {
|
||||
if err := user.HashPassword(user.Password); err != nil {
|
||||
return err
|
||||
|
@ -53,6 +55,17 @@ func (gr *GormRepository) CreateUser(ctx context.Context, user *models.User) err
|
|||
if err != nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Glossary
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// <editor-fold desc="Glossary">
|
||||
func (gr *GormRepository) CreateGlossaryEntry(ctx context.Context, glossaryEntry *models.Glossary) error {
|
||||
record := gr.GormClient.WithContext(ctx).Create(glossaryEntry)
|
||||
if record.Error != nil {
|
||||
|
@ -114,6 +130,8 @@ func (gr *GormRepository) GetGlossaryEntry(ctx context.Context, code string, cod
|
|||
return &foundGlossaryEntry, result.Error
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Summary
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -180,6 +198,8 @@ func (gr *GormRepository) GetSummary(ctx context.Context) (*models.Summary, erro
|
|||
// 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.
|
||||
// It will also create associations between fhir resources
|
||||
// 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)
|
||||
if rawResource.ReferencedResources != nil && len(rawResource.ReferencedResources) > 0 {
|
||||
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, "/")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
relatedResource := &models.ResourceBase{
|
||||
relatedResource = &models.ResourceBase{
|
||||
OriginBase: models.OriginBase{
|
||||
SourceID: source.ID,
|
||||
SourceResourceType: parts[0],
|
||||
|
@ -237,11 +280,50 @@ func (gr *GormRepository) UpsertRawResource(ctx context.Context, sourceCredentia
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// this method will upsert a resource, however it will not create associations.
|
||||
// UPSERT operation
|
||||
|
@ -448,10 +530,14 @@ func (gr *GormRepository) GetPatientForSources(ctx context.Context) ([]models.Re
|
|||
return wrappedResourceModels, results.Error
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
func (gr *GormRepository) verifyAssociationPermission(ctx context.Context, sourceUserID uuid.UUID, relatedSourceUserID uuid.UUID) error {
|
||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||
|
@ -542,11 +628,14 @@ func (gr *GormRepository) FindResourceAssociationsByTypeAndId(ctx context.Contex
|
|||
ResourceBaseSourceID: source.ID,
|
||||
ResourceBaseSourceResourceType: resourceType,
|
||||
ResourceBaseSourceResourceID: resourceId,
|
||||
RelatedResourceUserID: currentUser.ID,
|
||||
}).
|
||||
Find(&relatedResources)
|
||||
return relatedResources, result.Error
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Resource Composition (Grouping)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -565,6 +654,8 @@ func (gr *GormRepository) FindResourceAssociationsByTypeAndId(ctx context.Contex
|
|||
// - add AddResourceAssociation for all resources linked to the Composition resource
|
||||
// - store the 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 {
|
||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||
if currentUserErr != nil {
|
||||
|
@ -718,6 +809,8 @@ func (gr *GormRepository) AddResourceComposition(ctx context.Context, compositio
|
|||
// SourceCredential
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//<editor-fold desc="SourceCredential">
|
||||
|
||||
func (gr *GormRepository) CreateSource(ctx context.Context, sourceCreds *models.SourceCredential) error {
|
||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||
if currentUserErr != nil {
|
||||
|
@ -846,10 +939,10 @@ func (gr *GormRepository) GetSourceSummary(ctx context.Context, sourceId string)
|
|||
Table(patientTableName).
|
||||
First(&wrappedPatientResourceModel)
|
||||
|
||||
if patientResults.Error != nil {
|
||||
return nil, patientResults.Error
|
||||
}
|
||||
//some sources may not have a patient resource (including the Fasten source)
|
||||
if patientResults.Error == nil {
|
||||
sourceSummary.Patient = &wrappedPatientResourceModel
|
||||
}
|
||||
|
||||
return sourceSummary, nil
|
||||
}
|
||||
|
@ -925,10 +1018,13 @@ func (gr *GormRepository) DeleteSource(ctx context.Context, sourceId string) (in
|
|||
return rowsEffected, results.Error
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Background Job
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// <editor-fold desc="Background Job & Checkpoints">
|
||||
func (gr *GormRepository) CreateBackgroundJob(ctx context.Context, backgroundJob *models.BackgroundJob) error {
|
||||
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
|
||||
if currentUserErr != nil {
|
||||
|
@ -1117,6 +1213,8 @@ func (gr *GormRepository) CancelAllLockedBackgroundJobsAndFail() error {
|
|||
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utilities
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
|
||||
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
|
||||
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
@ -14,7 +17,7 @@ func (gr *GormRepository) Migrate() error {
|
|||
gormMigrateOptions := gormigrate.DefaultOptions
|
||||
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{
|
||||
{
|
||||
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)
|
||||
},
|
||||
},
|
||||
{
|
||||
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 {
|
||||
|
|
|
@ -1021,7 +1021,7 @@ func (suite *RepositoryTestSuite) TestGetSummary() {
|
|||
{"count": int64(16), "resource_type": "Procedure"},
|
||||
}, 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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
|
||||
// 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
|
||||
//UpsertProfile(context.Context, *models.Profile) error
|
||||
//UpsertOrganziation(context.Context, *models.Organization) error
|
||||
|
@ -55,6 +57,15 @@ type DatabaseRepository interface {
|
|||
PopulateDefaultUserSettings(ctx context.Context, userId uuid.UUID) error
|
||||
|
||||
//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{})
|
||||
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 {
|
||||
return s.SourceType
|
||||
}
|
||||
func (s *SourceCredential) GetSourceId() string {
|
||||
return s.ID.String()
|
||||
}
|
||||
|
||||
func (s *SourceCredential) GetClientId() string {
|
||||
return s.ClientId
|
||||
|
|
|
@ -17,14 +17,65 @@ import (
|
|||
"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 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 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: 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 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
|
||||
}
|
||||
|
||||
// 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
|
||||
defer func() {
|
||||
//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
|
||||
|
||||
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)
|
||||
var sourceClient sourceModels.SourceClient
|
||||
var summary sourceModels.UpsertSummary
|
||||
sourceClient, summary, resultErr = callbackFn(backgroundJobContext, logger, databaseRepo, sourceCred)
|
||||
if resultErr != nil {
|
||||
logger.Errorln("An error occurred while syncing resources, ignoring", resultErr)
|
||||
return summary, resultErr
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ func GetResourceFhir(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, gin.H{"success": true, "data": wrappedResourceModel})
|
||||
}
|
||||
|
||||
// deprecated - using Manual Resource Wizard instead
|
||||
func CreateResourceComposition(c *gin.Context) {
|
||||
|
||||
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
|
||||
|
||||
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/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
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})
|
||||
}
|
||||
|
||||
// mimics functionality in CreateRelatedResources
|
||||
// mimics functionality in SourceSync
|
||||
func CreateManualSource(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)
|
||||
|
||||
// single file
|
||||
file, err := c.FormFile("file")
|
||||
// store the bundle file locally
|
||||
bundleFile, err := storeFileLocally(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "could not extract file from form"})
|
||||
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"})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -178,23 +168,40 @@ func CreateManualSource(c *gin.Context) {
|
|||
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 {
|
||||
logger.Errorln("An error occurred while initializing hub client using manual source with credential", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
resultErr := fmt.Errorf("an error occurred while initializing hub client using manual source with credential: %w", err)
|
||||
logger.Errorln(resultErr)
|
||||
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 {
|
||||
logger.Errorln("An error occurred while processing bundle", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
//publish event
|
||||
currentUser, _ := databaseRepo.GetCurrentUser(c)
|
||||
|
||||
err = eventBus.PublishMessage(
|
||||
models.NewEventSourceComplete(
|
||||
currentUser.ID.String(),
|
||||
|
@ -260,3 +267,29 @@ func DeleteSource(c *gin.Context) {
|
|||
}
|
||||
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.POST("/resource/graph/:graphType", handler.GetResourceFhirGraph)
|
||||
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
|
||||
|
||||
secure.POST("/resource/composition", handler.CreateResourceComposition)
|
||||
secure.POST("/resource/related", handler.CreateRelatedResources)
|
||||
|
||||
secure.GET("/dashboards", handler.GetDashboard)
|
||||
secure.POST("/dashboards", handler.AddDashboardLocation)
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"ngx-highlightjs": "^7.0.1",
|
||||
"ngx-infinite-scroll": "^14.0.0",
|
||||
"ngx-moment": "^6.0.2",
|
||||
"parse-full-name": "^1.2.6",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^2.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import {NavigationEnd, Router} from '@angular/router';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {ToastService} from './services/toast.service';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -15,7 +16,11 @@ export class AppComponent implements OnInit {
|
|||
showHeader:boolean = false;
|
||||
showFooter:boolean = true;
|
||||
|
||||
constructor(private router: Router, private toastService: ToastService) {}
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toastService: ToastService,
|
||||
private modalService: NgbModal
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
|
@ -25,17 +30,21 @@ export class AppComponent implements OnInit {
|
|||
document.querySelector('body').appendChild(navbarBackdrop);
|
||||
|
||||
//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) {
|
||||
//modify header
|
||||
if (event.url?.startsWith('/auth') || event.url?.startsWith('/desktop')) {
|
||||
this.showHeader = false;
|
||||
} else {
|
||||
// console.log("NU")
|
||||
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 { DesktopCallbackComponent } from './pages/desktop-callback/desktop-callback.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({
|
||||
declarations: [
|
||||
|
@ -64,6 +66,8 @@ import { BackgroundJobsComponent } from './pages/background-jobs/background-jobs
|
|||
BrowserModule,
|
||||
FontAwesomeModule,
|
||||
SharedModule,
|
||||
FhirCardModule,
|
||||
FhirDatatableModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
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
|
||||
const meta: Meta<BadgeComponent> = {
|
||||
title: 'Fhir/Common/Badge',
|
||||
title: 'Fhir Card/Common/Badge',
|
||||
component: BadgeComponent,
|
||||
decorators: [
|
||||
// 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
|
||||
const meta: Meta<TableComponent> = {
|
||||
title: 'Fhir/Common/Table',
|
||||
title: 'Fhir Card/Common/Table',
|
||||
component: TableComponent,
|
||||
decorators: [
|
||||
// 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
|
||||
const meta: Meta<BinaryTextComponent> = {
|
||||
title: 'Fhir/Datatypes/BinaryText',
|
||||
title: 'Fhir Card/Datatypes/BinaryText',
|
||||
component: BinaryTextComponent,
|
||||
decorators: [
|
||||
// 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
|
||||
const meta: Meta<DicomComponent> = {
|
||||
title: 'Fhir/Datatypes/Dicom',
|
||||
title: 'Fhir Card/Datatypes/Dicom',
|
||||
component: DicomComponent,
|
||||
decorators: [
|
||||
// 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
|
||||
const meta: Meta<HtmlComponent> = {
|
||||
title: 'Fhir/Datatypes/Html',
|
||||
title: 'Fhir Card/Datatypes/Html',
|
||||
component: HtmlComponent,
|
||||
decorators: [
|
||||
// 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
|
||||
const meta: Meta<ImgComponent> = {
|
||||
title: 'Fhir/Datatypes/Img',
|
||||
title: 'Fhir Card/Datatypes/Img',
|
||||
component: ImgComponent,
|
||||
decorators: [
|
||||
// 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
|
||||
const meta: Meta<PdfComponent> = {
|
||||
title: 'Fhir/Datatypes/Pdf',
|
||||
title: 'Fhir Card/Datatypes/Pdf',
|
||||
component: PdfComponent,
|
||||
decorators: [
|
||||
// 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';
|
||||
|
||||
//all Fhir Resource components must implement this Interface
|
||||
export interface FhirResourceComponentInterface {
|
||||
export interface FhirCardComponentInterface {
|
||||
displayModel: FastenDisplayModel;
|
||||
showDetails: boolean;
|
||||
isCollapsed: boolean;
|
||||
|
||||
//these are used to populate the description of the resource. May not be available for all resources
|
||||
resourceCode?: string;
|
|
@ -1,4 +1,4 @@
|
|||
import { FhirResourceOutletDirective } from './fhir-resource-outlet.directive';
|
||||
import { FhirCardOutletDirective } from './fhir-card-outlet.directive';
|
||||
|
||||
import {
|
||||
ComponentFactory, ComponentRef,
|
||||
|
@ -66,11 +66,11 @@ class TestViewContainerRef extends ViewContainerRef {
|
|||
}
|
||||
|
||||
|
||||
describe('FhirResourceOutletDirective', () => {
|
||||
describe('FhirCardOutletDirective', () => {
|
||||
|
||||
|
||||
it('should create an instance', () => {
|
||||
const directive = new FhirResourceOutletDirective(new TestViewContainerRef());
|
||||
const directive = new FhirCardOutletDirective(new TestViewContainerRef());
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,9 +1,9 @@
|
|||
import {Directive, ViewContainerRef} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[resourceListOutlet]'
|
||||
selector: '[fhirCardOutlet]'
|
||||
})
|
||||
export class ResourceListOutletDirective {
|
||||
export class FhirCardOutletDirective {
|
||||
|
||||
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
|
||||
} from '@angular/core';
|
||||
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 {FallbackComponent} from '../resources/fallback/fallback.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 {AllergyIntoleranceComponent} from '../resources/allergy-intolerance/allergy-intolerance.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 {OrganizationComponent} from '../resources/organization/organization.component';
|
||||
import {ObservationComponent} from '../resources/observation/observation.component';
|
||||
import {EncounterComponent} from '../resources/encounter/encounter.component';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'fhir-resource',
|
||||
selector: 'fhir-card',
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
templateUrl: './fhir-resource.component.html',
|
||||
styleUrls: ['./fhir-resource.component.scss']
|
||||
templateUrl: './fhir-card.component.html',
|
||||
styleUrls: ['./fhir-card.component.scss']
|
||||
})
|
||||
export class FhirResourceComponent implements OnInit, OnChanges {
|
||||
export class FhirCardComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() displayModel: FastenDisplayModel
|
||||
@Input() showDetails: boolean = true
|
||||
@Input() isCollapsed: boolean = false
|
||||
|
||||
//location to dynamically load the displayModel
|
||||
@ViewChild(FhirResourceOutletDirective, {static: true}) fhirResourceOutlet!: FhirResourceOutletDirective;
|
||||
@ViewChild(FhirCardOutletDirective, {static: true}) fhirCardOutlet!: FhirCardOutletDirective;
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
@ -55,21 +57,22 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
|||
|
||||
loadComponent() {
|
||||
//clear the current outlet
|
||||
const viewContainerRef = this.fhirResourceOutlet.viewContainerRef;
|
||||
const viewContainerRef = this.fhirCardOutlet.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
let componentType = this.typeLookup(this.displayModel?.source_resource_type)
|
||||
if(componentType != null){
|
||||
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.showDetails = this.showDetails;
|
||||
componentRef.instance.isCollapsed = this.isCollapsed;
|
||||
componentRef.instance.markForCheck()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
typeLookup(resourceType: ResourceType): Type<FhirResourceComponentInterface> {
|
||||
typeLookup(resourceType: ResourceType): Type<FhirCardComponentInterface> {
|
||||
if(!resourceType){
|
||||
//dont try to render anything if the resourceType isnt set.
|
||||
return null
|
||||
|
@ -114,9 +117,9 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
|||
case "DocumentReference": {
|
||||
return DocumentReferenceComponent;
|
||||
}
|
||||
// case "Encounter": {
|
||||
// return ListEncounterComponent;
|
||||
// }
|
||||
case "Encounter": {
|
||||
return EncounterComponent;
|
||||
}
|
||||
// case "Goal": {
|
||||
// return ListGoalComponent;
|
||||
// }
|
|
@ -1,5 +1,5 @@
|
|||
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 {Router, RouterModule} from '@angular/router';
|
||||
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',
|
||||
styleUrls: ['./allergy-intolerance.component.scss']
|
||||
})
|
||||
export class AllergyIntoleranceComponent implements OnInit, FhirResourceComponentInterface {
|
||||
export class AllergyIntoleranceComponent implements OnInit, FhirCardComponentInterface {
|
||||
@Input() displayModel: AllergyIntoleranceModel
|
||||
@Input() showDetails: boolean = true
|
||||
|
||||
isCollapsed: boolean = false
|
||||
@Input() isCollapsed: boolean = false
|
||||
|
||||
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
|
||||
const meta: Meta<AllergyIntoleranceComponent> = {
|
||||
title: 'Fhir/AllergyIntolerance',
|
||||
title: 'Fhir Card/AllergyIntolerance',
|
||||
component: AllergyIntoleranceComponent,
|
||||
decorators: [
|
||||
// moduleMetadata({
|
||||
|
@ -51,8 +51,8 @@ export const R4Example1: Story = {
|
|||
};
|
||||
|
||||
let aiDisplayModel2 = new AllergyIntoleranceModel(R4Example2Json, fhirVersions.R4)
|
||||
aiDisplayModel1.source_id = '123-456-789'
|
||||
aiDisplayModel1.source_resource_id = '123-456-789'
|
||||
aiDisplayModel2.source_id = '123-456-789'
|
||||
aiDisplayModel2.source_resource_id = '123-456-789'
|
||||
export const R4Example2: Story = {
|
||||
args: {
|
||||
displayModel: aiDisplayModel2
|
||||
|
@ -60,8 +60,8 @@ export const R4Example2: Story = {
|
|||
};
|
||||
|
||||
let aiDisplayModel3 = new AllergyIntoleranceModel(R4Example3Json, fhirVersions.R4)
|
||||
aiDisplayModel1.source_id = '123-456-789'
|
||||
aiDisplayModel1.source_resource_id = '123-456-789'
|
||||
aiDisplayModel3.source_id = '123-456-789'
|
||||
aiDisplayModel3.source_resource_id = '123-456-789'
|
||||
export const R4Example3: Story = {
|
||||
args: {
|
||||
displayModel: aiDisplayModel3
|
|
@ -1,6 +1,6 @@
|
|||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
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 {AttachmentModel} from '../../../../../lib/models/datatypes/attachment-model';
|
||||
import {FastenApiService} from '../../../../services/fasten-api.service';
|
||||
|
@ -37,11 +37,12 @@ import {AuthService} from "../../../../services/auth.service";
|
|||
templateUrl: './binary.component.html',
|
||||
styleUrls: ['./binary.component.scss']
|
||||
})
|
||||
export class BinaryComponent implements OnInit, FhirResourceComponentInterface {
|
||||
export class BinaryComponent implements OnInit, FhirCardComponentInterface {
|
||||
@Input() displayModel: BinaryModel
|
||||
@Input() showDetails: boolean = true
|
||||
@Input() attachmentSourceId: string
|
||||
@Input() attachmentModel: AttachmentModel //can only have attachmentModel or binaryModel, not both.
|
||||
@Input() isCollapsed: boolean = false
|
||||
|
||||
loading: boolean = false
|
||||
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
|
||||
const meta: Meta<BinaryComponent> = {
|
||||
title: 'Fhir/Binary',
|
||||
title: 'Fhir Card/Binary',
|
||||
component: BinaryComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
|
@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
|
||||
import { DiagnosticReportComponent } from './diagnostic-report.component';
|
||||
import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
describe('DiagnosticReportComponent', () => {
|
||||
let component: DiagnosticReportComponent;
|
||||
|
@ -9,8 +10,7 @@ describe('DiagnosticReportComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DiagnosticReportComponent ],
|
||||
imports: [NgbCollapseModule]
|
||||
imports: [NgbCollapseModule, DiagnosticReportComponent, RouterTestingModule]
|
||||
|
||||
})
|
||||
.compileComponents();
|
|
@ -1,22 +1,31 @@
|
|||
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 {Router} from '@angular/router';
|
||||
import {Router, RouterModule} from '@angular/router';
|
||||
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({
|
||||
selector: 'app-diagnostic-report',
|
||||
standalone: true,
|
||||
imports: [NgbCollapseModule, CommonModule, BadgeComponent, TableComponent, RouterModule, BinaryComponent, GlossaryLookupComponent],
|
||||
selector: 'fhir-diagnostic-report',
|
||||
templateUrl: './diagnostic-report.component.html',
|
||||
styleUrls: ['./diagnostic-report.component.scss']
|
||||
})
|
||||
export class DiagnosticReportComponent implements OnInit, FhirResourceComponentInterface {
|
||||
export class DiagnosticReportComponent implements OnInit, FhirCardComponentInterface {
|
||||
@Input() displayModel: DiagnosticReportModel
|
||||
@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;
|
||||
|
||||
isCollapsed: boolean = false
|
||||
tableData: TableRowItem[] = []
|
||||
|
||||
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 {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
describe('DocumentReferenceComponent', () => {
|
||||
let component: DocumentReferenceComponent;
|
||||
|
@ -9,8 +10,8 @@ describe('DocumentReferenceComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NgbCollapseModule],
|
||||
declarations: [ DocumentReferenceComponent ]
|
||||
imports: [NgbCollapseModule, DocumentReferenceComponent, RouterTestingModule],
|
||||
declarations: [ ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
|
@ -1,19 +1,27 @@
|
|||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
import {DiagnosticReportModel} from '../../../../../lib/models/resources/diagnostic-report-model';
|
||||
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 {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({
|
||||
selector: 'app-document-reference',
|
||||
standalone: true,
|
||||
imports: [NgbCollapseModule, CommonModule, BadgeComponent, TableComponent, RouterModule, BinaryComponent],
|
||||
selector: 'fhir-document-reference',
|
||||
templateUrl: './document-reference.component.html',
|
||||
styleUrls: ['./document-reference.component.scss']
|
||||
})
|
||||
export class DocumentReferenceComponent implements OnInit, FhirResourceComponentInterface {
|
||||
export class DocumentReferenceComponent implements OnInit, FhirCardComponentInterface {
|
||||
@Input() displayModel: DocumentReferenceModel
|
||||
@Input() showDetails: boolean = true
|
||||
isCollapsed: boolean = false
|
||||
@Input() isCollapsed: boolean = false
|
||||
tableData: TableRowItem[] = []
|
||||
|
||||
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 () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ FallbackComponent ]
|
||||
imports: [ FallbackComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
|
@ -1,16 +1,22 @@
|
|||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
import {BinaryModel} from '../../../../../lib/models/resources/binary-model';
|
||||
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({
|
||||
standalone: true,
|
||||
imports: [NgbCollapseModule, HighlightModule, CommonModule],
|
||||
selector: 'fhir-fallback',
|
||||
templateUrl: './fallback.component.html',
|
||||
styleUrls: ['./fallback.component.scss']
|
||||
})
|
||||
export class FallbackComponent implements OnInit, FhirResourceComponentInterface {
|
||||
export class FallbackComponent implements OnInit, FhirCardComponentInterface {
|
||||
@Input() displayModel: BinaryModel
|
||||
@Input() showDetails: boolean = true
|
||||
@Input() isCollapsed: boolean = false
|
||||
|
||||
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