Skip to content

Commit

Permalink
copy of LoginT.Attrs into QuestionaireT.Attrs earlier - URL param 'v'…
Browse files Browse the repository at this point in the history
… can be set at login and is passed on during anonymous login
  • Loading branch information
pbberlin committed Dec 29, 2023
1 parent 818034f commit ccd895d
Show file tree
Hide file tree
Showing 15 changed files with 87 additions and 60 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ If you have created your survey `myquest` you need to restart the application.

* `page`=[0-9] - jump to page x

* `v=123`- show q.Version - setting the version at login. Later requests have no effect.
`LoginWithoutID()` and `LoginByHash()` pass the value of this param on
and store it into `LogintT.Attrs["version"]`

* `show-version=true` - show q.Version

* `full-dynamic-content=true` - compute dynamic content for all pages
Expand Down
6 changes: 4 additions & 2 deletions app-bucket/responses/kneb1-2023-06.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
]
},
"closing_time": "0001-01-01T00:00:00Z",
"md_5": "e9ffd4a2faade37f4a04377cb7861d2685529a76a28f60d304ad8725873953ff",
"md_5": "5de4df3a7fa18c435a437ba78b28fd5cbd291fb861b263beef6450b0864eb0d9",
"lang_codes": [
"de"
],
"lang_code": "de",
"allow_skip_forward": false,
"version_effective": 0,
"version_max": 4,
"assign_version": "version-from-login-url",
"version_effective": -2,
"max_groups": 6,
"pages": [
{
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/alexedwards/scs/redisstore v0.0.0-20231113091146-cef4b05350c8
github.com/alexedwards/scs/v2 v2.7.0
github.com/go-playground/form/v4 v4.2.1
github.com/go-stack/stack v1.8.1 // indirect
github.com/gomodule/redigo v1.8.9
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible // indirect
github.com/pbberlin/dbg v1.0.3
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,7 @@ github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
# Caroline Knebel Experiment - offen

* Integration mit Panel-Anbieter - anonymes Login? Login über vorbereitete IDs?
Wie verläuft die Rückmeldung für Erfolg/Misserfolg an denn Panelanbieter

* Treatment is not yet coded;
Wie werden die Treatment Gruppen festgelegt?
Via Panelanbieter?
Nach Sequenz (gerade ID => Gruppe 1, ungerade => 2)?

* Radio-Werte sind immer 1...5 oder 1...10 -
`Weiß nicht` ist dann bspw. 9 und `keine Antwort` ist 10
Sonst Fehlergefahr

* "Guided tour" vor Experiment - ist jetzt mit relativ unschönen Bildern aus Powerpoint.
Aktualisierung, wenn das Experiment-Layout final ist.

* Set `ah=30` and `ahV=25` in the debugging console, to change the chart

* Die Berechnung erfolgt in `echart-config.mjs`
Expand All @@ -35,28 +22,8 @@
Andererseits sollen sie nicht nach oben ausbrechen.
`Anlagehorizont` nicht in die Mitte, sondern mehr nach rechts? Bei 80%?

* Experiment does not yet work on Apple Browsers (Iphone, Mac)

* Mobile phone:
Layout des Experiments für Smartphones würde nicht-triviiale Umordnung erforden.
a.) aufwändig
b.) Verfälschungen zwischen Smartphone-Teilnehmern und PC-Teilnehmern durch Anordnung
=> Idee: Keine Anpassung des Layouts für Smartphones
=> Der Panelprovider sollte erzwingen, dass die Teilnehmer über einen Desktop-Computer (PC,Apple) antworten

## Test URLs

pbu
* <https://survey2.zew.de?u=9990&sid=kneb1&wid=2023-06&p=1&h=DNWNoIHHR8RIUiOq9quWU1V1TgHt2tLtv1xaP8nVjyk>
* <https://survey2.zew.de?u=9991&sid=kneb1&wid=2023-06&p=1&h=MY1DwxojlLBrci7k8XubmI9aW4IPrTFj6AOiAsO5xMs>

Knebel
* <https://survey2.zew.de?u=9992&sid=kneb1&wid=2023-06&p=1&h=S1LMUHYI8fpY2fHsaUsjiCHdSyckr7vsDnzcUnZyc0c>
* <https://survey2.zew.de?u=9993&sid=kneb1&wid=2023-06&p=1&h=06OApCzf6-CuvXgyXTDRfDZfJNOZ7TnSmpVSyctmtbM>



## MSCI world returns and sigma
# MSCI world returns and sigma

<https://investingintheweb.com/blog/msci-world-index-historical-data/>

Expand Down
10 changes: 9 additions & 1 deletion pkg/generators/kneb1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ func Create(s qst.SurveyT) (*qst.QuestionnaireT, error) {
"en": "Umfrage zum Thema Finanzentscheidungen",
"de": "Umfrage zum Thema Finanzentscheidungen",
}
// q.Variations = 1

// version stuff
q.VersionMax = 4
q.AssignVersion = "version-from-login-url"
q.VersionEffective = -2 // prevent 0 - 0 would be a valid version
if false {
// version is determined dynamically upon loading base fresh from file in loadQuestionnaire()
_ = q.Version()
}

// page 0
{
Expand Down
28 changes: 18 additions & 10 deletions pkg/handlers/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func loadQuestionnaire(w http.ResponseWriter, r *http.Request, l *lgn.LoginT) (*

q, ok, err := qst.FromSession(w, r)
if err != nil {
err = fmt.Errorf("Reading questionnaire from session caused error %w", err)
err = fmt.Errorf("reading questionnaire from session caused error %w", err)
return q, err
}
if ok {
Expand Down Expand Up @@ -69,9 +69,24 @@ func loadQuestionnaire(w http.ResponseWriter, r *http.Request, l *lgn.LoginT) (*

// no file containing previous answers
// => adopt qBase
qBase.UserID = l.User

log.Printf("No previous user questionnaire file %v found. Using base file.", pth)

// adapting the base
qBase.UserID = l.User

// we need q.Attrs already below for the dynamic pages
//
// login attributes are copied to questionaire attributes
if qBase.Attrs == nil {
// q.Attrs might already contain previous values
qBase.Attrs = map[string]string{}
}
for k, v := range l.Attrs {
qBase.Attrs[k] = v
}
qBase.Version() // set version initially and forever

// dynamic pages based on login user ID
err = qBase.DynamicPages(-1)
if err != nil {
Expand Down Expand Up @@ -319,14 +334,7 @@ func MainH(w http.ResponseWriter, r *http.Request) {
}
}

// Add login attributes to questionaire attributes
// q.Attrs might already contain other values
if q.Attrs == nil {
q.Attrs = map[string]string{}
}
for k, v := range l.Attrs {
q.Attrs[k] = v
}
// 2023-12: copy of login.Attrs to q.Attrs was moved to loadQuestionnaire()

prevPage := q.PrevPage() // remember before

Expand Down
6 changes: 6 additions & 0 deletions pkg/lgn/create-anonymous-id.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ func LoginWithoutID(w http.ResponseWriter, r *http.Request) {
// forward to LoginByHashID
url := cfg.Pref(fmt.Sprintf("/d/%v--%v", cfg.Get().AnonymousSurveyID, hashID))

// preserving exactly one parameter: 'v=xx'
version := r.FormValue("v")
if version != "" {
url += fmt.Sprintf("?v=%v", version)
}

if true {
// http.Redirect(w, r, url, http.StatusFound)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
Expand Down
10 changes: 7 additions & 3 deletions pkg/lgn/logins.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var exempted = map[string]interface{}{
"submit": nil,
"mobile": nil,
"lang_code": nil,
"v": nil, // the version - if we want to set it via direct link, compare q.Version() and 'version-from-login-url'
// the hash itself
"h": nil,
// "attrs": nil, // user attributes at login time - must be hashed to prevent tampering
Expand All @@ -50,15 +51,18 @@ var exempted = map[string]interface{}{
// we dont use wrap.paramPersister, because its too broad
}

// userAttrs contains URL params going into LoginT.Attrs
// upon login.
// userAttrs contains URL params which we want to be saved into user attributes.
// The are saved during login into
// - qst.QuestionaireT.Attrs
// - lgn.LoginT.Attrs
// They serve as a property bag session.
// Key is the short form - from the URL. Val is the long form to be saved as login attrs
// LoginT methods Query(), LoginURL() and partly QuestPath() tie into this logic.
var userAttrs = map[string]string{
"sid": "survey_id",
"wid": "wave_id",
"p": "profile", // user profile, replaces attrs
"v": "version", // version set via URL
// "a": "attrs", // general purpose - can occur several times - key:value - or a profile id
}

Expand Down Expand Up @@ -89,7 +93,7 @@ type LoginT struct {
Group string `json:"-"` // Derived from email domain - or LDAP org
Provider string `json:"-"` // twitter, facebook, ... or hash, anonymous/direct, JSON, LDAP
Roles map[string]string `json:"roles"` // i.e. admin: true, can only be set via JSON config; therefore safe
Attrs map[string]string `json:"attrs"` // i.e. country: Poland, gender: female, height: 188, can be overridden by URL params, therefore unsafe.
Attrs map[string]string `json:"attrs"` // i.e. country: Poland, gender: female, height: 188, a few keys can set via URL params, these are unsafe.

PassInitial string `json:"pass_initial"` // For first login - unencrypted - grants restricted access to change password only
IsInitPassword bool `json:"is_init_password"` // Indicates authentication against PassInitial
Expand Down
7 changes: 7 additions & 0 deletions pkg/lgn/questionnaire-specific.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ func LoginByHash(w http.ResponseWriter, r *http.Request) (bool, error) {
l.Attrs[pk] = pv
}

version := r.FormValue("v")
if version != "" {
l.Attrs["version"] = version
}

if sess.EffectiveStr("override_closure") == "true" {
sess.PutString("override_closure", "true")
}
Expand Down Expand Up @@ -185,6 +190,8 @@ func LoginByHash(w http.ResponseWriter, r *http.Request) (bool, error) {

//
// set attributes from configured profiles
// &sid=fmt&p=1 => config.json -> fmt1
// &sid=fmt&p=2 => config.json -> fmt2
for key := range r.Form {
// same key - multiple values
// attrs=country:Sweden&attrs=height:176
Expand Down
2 changes: 1 addition & 1 deletion pkg/qst/funcs-dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func ErrorProxy(q *QuestionnaireT, inp *inputT, paramSet string) (string, error)
// knebSlightlyDistinctLabel yields distinct labels depending on treatment
func knebSlightlyDistinctLabel(q *QuestionnaireT, inp *inputT, paramSet string) (string, error) {

if q.UserIDInt()%2 == 0 {
if q.Version()%2 == 0 {
return `Wie motiviert sind Sie, sich mit dem Thema „Sparen und investieren“ zu befassen?`, nil
} else {
return `Wie motiviert sind Sie, sich weiter mit dem Thema „Sparen und investieren“ zu befassen?`, nil
Expand Down
4 changes: 2 additions & 2 deletions pkg/qst/funcs-navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func pdsAssetClass(q *QuestionnaireT, pageIdx int, acIdx int) bool {
//
//
func knebTreatment1NeurtraVsFinance_A(q *QuestionnaireT, pageIdx int) bool {
if q.UserIDInt()%2 == 0 {
if q.Version()%2 == 0 {
return true
}
return false
Expand All @@ -141,7 +141,7 @@ func knebTreatment1NeurtraVsFinance_B(q *QuestionnaireT, pageIdx int) bool {
// 1000,1001 => 500 => 0 => false
// 1002,1003, => 501 => 1 => true
func knebTreatment2AdviceNoOrYes_A(q *QuestionnaireT, pageIdx int) bool {
id := q.UserIDInt()
id := q.Version()
id = id / 2
if id%2 == 0 {
return false
Expand Down
2 changes: 1 addition & 1 deletion pkg/qst/funcs-page-kneb-202306-guidedtour.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func kneb202306guidedtour(q *QuestionnaireT, page *pageT) error {
page.WidthMax("52rem")

// gr0
grIdx := q.UserIDInt() % 2
grIdx := q.Version() % 2
{
{
gr := page.AddGroup()
Expand Down
2 changes: 1 addition & 1 deletion pkg/qst/funcs-page-kneb-202306-simtool.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func kneb202306simtool(q *QuestionnaireT, page *pageT, iter int) error {
}

// gr1
grIdx := q.UserIDInt() % 2
grIdx := q.Version() % 2
{
gr := page.AddGroup()
gr.Cols = 1
Expand Down
29 changes: 24 additions & 5 deletions pkg/qst/questionnaire.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,8 @@ type QuestionnaireT struct {
// tpl.SiteCore() yields a common *style* identifier
// for completely different questionnaires
VersionMax int `json:"version_max,omitempty"` // total number of versions - usually permutations
AssignVersion string `json:"assign_version,omitempty"` // default is UserID modulo - other value is "round-robin"
VersionEffective int `json:"version_effective"` // result of q.Version()
AssignVersion string `json:"assign_version,omitempty"` // default is UserID modulo VerionMax - other value is "round-robin" based on in memory atomic counter
VersionEffective int `json:"version_effective"` // zero based, result of q.Version() - intended for read access - written on first call of q.Version() - can be reset with to -2

MaxGroups int `json:"max_groups,omitempty"` // Max number of groups - a helper value - computed during questionnaire creation - previously used for shuffing of groups.

Expand Down Expand Up @@ -1847,7 +1847,8 @@ func (q *QuestionnaireT) KeysValues(cleanse bool, getRangeInfo bool) (keys, vals
return
}

// UserIDInt retrieves the userID as int
// UserIDInt retrieves the userID as int.
// For dynamic questionaires - use q.Version().
func (q *QuestionnaireT) UserIDInt() int {

if q.UserID == "systemtest" {
Expand All @@ -1871,9 +1872,14 @@ func (q *QuestionnaireT) UserIDInt() int {

var ctrLogin = ctr.New()

// Version retrieves the questionnaire's version;
// Version sets and returns the questionnaire's version;
// default - version depends on user ID
// 'round-robin' - version depends on login order
// 'round-robin' - version depends on login order
// 'version-from-login-url' - version depends parameter v=[xx] during login
//
// Version() is idempotent - once it is set to greater zero, this value remains
// Version() called first when a new QuestionaireT is loaded from a JSON base file.
// Version() can be recomputed if q.VersionEffective has been set to -1 before
func (q *QuestionnaireT) Version() int {

if q == nil {
Expand All @@ -1886,6 +1892,19 @@ func (q *QuestionnaireT) Version() int {
// log.Printf("Assign version based on central counter: %v", q.VersionEffective)
} else if q.AssignVersion == "round-robin" {
q.VersionEffective = int(ctrLogin.Increment()) % q.VersionMax
} else if q.AssignVersion == "version-from-login-url" {
if val, ok := q.Attrs["version"]; ok {
log.Printf("version derived from url param 'v' via q.Attrs['version'] - %v", q.Attrs["version"])
q.VersionEffective = int(ctrLogin.Increment()) % q.VersionMax
valInt, _ := strconv.Atoi(val)
if valInt < q.VersionMax {
q.VersionEffective = valInt
}
} else {
log.Printf(`ERROR: scheme 'version-from-login-url' requires an URL parameter 'v' with a version number`)
// fallback:
q.VersionEffective = int(ctrLogin.Increment()) % q.VersionMax
}
} else {
q.VersionEffective = q.UserIDInt() % q.VersionMax
if q.UserIDInt() == -3216 {
Expand Down

0 comments on commit ccd895d

Please sign in to comment.